From 3a39a98ec8419dbe1ed4a1bd9f4931282fd2eb22 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Mon, 30 Mar 2020 14:38:42 +0200
Subject: [PATCH] orange_sms: initial version (#41092)

copied from https://github.com/departement-loire-atlantique/passerelle-orangesms
---
 passerelle/apps/orange_sms/__init__.py        |   0
 .../orange_sms/migrations/0001_initial.py     |  37 +++++++
 .../apps/orange_sms/migrations/__init__.py    |   0
 passerelle/apps/orange_sms/models.py          |  59 +++++++++++
 passerelle/apps/orange_sms/orange_api.py      | 100 ++++++++++++++++++
 passerelle/settings.py                        |   1 +
 tests/test_sms.py                             |   9 +-
 7 files changed, 202 insertions(+), 4 deletions(-)
 create mode 100644 passerelle/apps/orange_sms/__init__.py
 create mode 100644 passerelle/apps/orange_sms/migrations/0001_initial.py
 create mode 100644 passerelle/apps/orange_sms/migrations/__init__.py
 create mode 100644 passerelle/apps/orange_sms/models.py
 create mode 100644 passerelle/apps/orange_sms/orange_api.py

diff --git a/passerelle/apps/orange_sms/__init__.py b/passerelle/apps/orange_sms/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/passerelle/apps/orange_sms/migrations/0001_initial.py b/passerelle/apps/orange_sms/migrations/0001_initial.py
new file mode 100644
index 00000000..e1c278ce
--- /dev/null
+++ b/passerelle/apps/orange_sms/migrations/0001_initial.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-03-30 12:33
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('base', '0018_smslog'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OrangeRestSMSGateway',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=50, verbose_name='Title')),
+                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
+                ('description', models.TextField(verbose_name='Description')),
+                ('max_message_length', models.IntegerField(default=160, verbose_name='Maximum message length')),
+                ('username', models.CharField(max_length=64, verbose_name='Identifiant')),
+                ('password', models.CharField(max_length=64, verbose_name='Mot de passe')),
+                ('groupname', models.CharField(max_length=64, verbose_name='Groupe')),
+                ('default_country_code', models.CharField(default='33', max_length=3, verbose_name='Préfixe pays')),
+                ('default_trunk_prefix', models.CharField(default='0', max_length=2, verbose_name='Préfixe supprimé par défaut')),
+                ('users', models.ManyToManyField(blank=True, related_name='_orangerestsmsgateway_users_+', related_query_name='+', to='base.ApiUser')),
+            ],
+            options={
+                'verbose_name': 'Orange REST SMS',
+                'db_table': 'sms_orangerest',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/orange_sms/migrations/__init__.py b/passerelle/apps/orange_sms/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/passerelle/apps/orange_sms/models.py b/passerelle/apps/orange_sms/models.py
new file mode 100644
index 00000000..f413ca98
--- /dev/null
+++ b/passerelle/apps/orange_sms/models.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# MIT License
+# Copyright (c) 2020 departement-loire-atlantique
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from django.db import models
+
+from . import orange_api
+from requests import RequestException
+
+from passerelle.utils.jsonresponse import APIError
+from passerelle.base.models import SMSResource
+
+
+class OrangeRestSMSGateway(SMSResource):
+
+    username = models.CharField(verbose_name='Identifiant', max_length=64)
+    password = models.CharField(verbose_name='Mot de passe', max_length=64)
+    groupname = models.CharField(verbose_name='Groupe', max_length=64)
+
+    default_country_code = models.CharField(
+        verbose_name='Préfixe pays', max_length=3, default=u'33')
+    default_trunk_prefix = models.CharField(
+        verbose_name='Préfixe supprimé par défaut', max_length=2, default=u'0')
+
+    manager_view_template_name = 'passerelle/manage/messages_service_view.html'
+
+    class Meta:
+        verbose_name = 'Orange REST SMS'
+        db_table = 'sms_orangerest'
+
+    def send_msg(self, text, sender, destinations, **kwargs):
+        """Send a SMS using the Orange REST"""
+
+        destinations = self.clean_numbers(
+            destinations, self.default_country_code, self.default_trunk_prefix)
+
+        try:
+            api = orange_api.OrangeAPI(self.username, self.password)
+            api.send(self.groupname, destinations, text)
+        except RequestException as e:
+            raise APIError('Orange REST SMS API error: %s' % e)
diff --git a/passerelle/apps/orange_sms/orange_api.py b/passerelle/apps/orange_sms/orange_api.py
new file mode 100644
index 00000000..930be7f4
--- /dev/null
+++ b/passerelle/apps/orange_sms/orange_api.py
@@ -0,0 +1,100 @@
+# MIT License
+# Copyright (c) 2020 departement-loire-atlantique
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import requests
+import json
+
+
+class OrangeAPI:
+
+    BASE_API = "https://contact-everyone.orange-business.com/api/v1.2/"
+    URL_TOKEN = BASE_API + "oauth/token"
+    URL_GROUPS = BASE_API + "groups"
+    URL_DIFFUSION = BASE_API + "groups/%s/diffusion-requests"
+
+    def __init__(self, username, password):
+        self.username = username
+        self.password = password
+        self.authorization_header = {}
+
+    def login(self):
+        headers = {'content-type': 'application/x-www-form-urlencoded'}
+        params = {'username': self.username, 'password': self.password}
+
+        rq = requests.post(self.URL_TOKEN, data=params, headers=headers)
+        if rq.status_code == 200:
+            response_json = json.loads(rq.text)
+            if 'access_token' in response_json:
+                self.authorization_header = {
+                    'authorization': "Bearer %s" % response_json['access_token']
+                }
+            else:
+                raise requests.RequestException('Orange API: Access token not found')
+        else:
+            raise requests.RequestException(
+                'Orange API: Bad username or password (%s)', rq.status_code)
+
+    def group_id_from_name(self, group_name):
+        rq = requests.get(self.URL_GROUPS, headers=self.authorization_header)
+        if rq.status_code == 200:
+            groups = json.loads(rq.text)
+            for group in groups:
+                if group["name"] == group_name:
+                    return group["id"]
+
+        raise requests.RequestException("Group name not found: " + group_name)
+
+    def send(self, group_name, destinations, message):
+        self.login()
+        group_id = self.group_id_from_name(group_name)
+
+        headers = {"content-type": "application/json"}
+        headers.update(self.authorization_header)
+
+        payload = json.dumps(
+            {
+                "name": "Send a SMS from passerelle",
+                "msisdns": destinations,
+                "smsParam": {
+                    "encoding": "GSM7",
+                    "body": message
+                }
+            })
+
+        rq = requests.post(self.URL_DIFFUSION % group_id, data=payload, headers=headers)
+        if rq.status_code == 201:
+            return json.loads(rq.text)
+
+        raise Exception("Send SMS failed %s, %s" % (rq.status_code, rq.text))
+
+
+if __name__ == '__main__':
+    import argparse
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--username")
+    parser.add_argument("--password")
+    parser.add_argument("--groupname")
+    parser.add_argument("--mobile")
+    parser.add_argument("--message")
+    args = parser.parse_args()
+
+    api = OrangeAPI(args.username, args.password)
+    api.send(args.groupname, [args.mobile], args.message)
diff --git a/passerelle/settings.py b/passerelle/settings.py
index 04173804..43695f20 100644
--- a/passerelle/settings.py
+++ b/passerelle/settings.py
@@ -149,16 +149,17 @@ INSTALLED_APPS = (
     'passerelle.apps.opengis',
     'passerelle.apps.orange',
     'passerelle.apps.ovh',
     'passerelle.apps.oxyd',
     'passerelle.apps.pastell',
     'passerelle.apps.phonecalls',
     'passerelle.apps.solis',
     'passerelle.apps.vivaticket',
+    'passerelle.apps.orange_sms',
     # backoffice templates and static
     'gadjo',
 )
 
 # disable some applications for now
 PASSERELLE_APP_BDP_ENABLED = False
 PASSERELLE_APP_GDC_ENABLED = False
 PASSERELLE_APP_PASTELL_ENABLED = False
diff --git a/tests/test_sms.py b/tests/test_sms.py
index c4b9d5c9..b8357b02 100644
--- a/tests/test_sms.py
+++ b/tests/test_sms.py
@@ -1,13 +1,14 @@
 import mock
 import pytest
 
 from django.contrib.contenttypes.models import ContentType
 
+from passerelle.apps.orange_sms.models import OrangeRestSMSGateway
 from passerelle.apps.ovh.models import OVHSMSGateway
 from passerelle.base.models import ApiUser, AccessRight, SMSResource, SMSLog
 
 from test_manager import login, admin_user
 
 import utils
 
 pytestmark = pytest.mark.django_db
@@ -69,38 +70,38 @@ def test_manage_views(admin_user, app, connector):
     assert 'Endpoints' in resp.text
     assert not 'accessright/add' in resp.text
     app = login(app)
     resp = app.get(url)
     assert 'Endpoints' in resp.text
     assert 'accessright/add' in resp.text
 
 
-@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
+@pytest.mark.parametrize('connector', [OVHSMSGateway, OrangeRestSMSGateway], indirect=True)
 def test_sms_max_message_length(app, connector):
     path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
 
     message_above_limit = 'a' * (connector.max_message_length + 1)
     payload = {
         'message': message_above_limit,
         'from': '+33699999999',
         'to': ['+33688888888'],
     }
-    with mock.patch.object(OVHSMSGateway, 'send_msg') as send_function:
+    with mock.patch.object(type(connector), 'send_msg') as send_function:
         send_function.return_value = {}
         result = app.post_json(path, params=payload)
         assert send_function.call_args[0][0] == 'a' * connector.max_message_length
 
 
-@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
+@pytest.mark.parametrize('connector', [OVHSMSGateway, OrangeRestSMSGateway], indirect=True)
 def test_sms_log(app, connector):
     path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
     assert not SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
 
     payload = {
         'message': 'plop',
         'from': '+33699999999',
         'to': ['+33688888888'],
     }
-    with mock.patch.object(OVHSMSGateway, 'send_msg') as send_function:
+    with mock.patch.object(type(connector), 'send_msg') as send_function:
         send_function.return_value = {}
         result = app.post_json(path, params=payload)
         assert SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
-- 
2.25.1

