Projet

Général

Profil

0001-ovh-support-jobs-API-endpoint-44313.patch

Valentin Deniaud, 29 juillet 2020 16:13

Télécharger (10,1 ko)

Voir les différences:

Subject: [PATCH] ovh: support /jobs/ API endpoint (#44313)

 .../ovh/migrations/0009_auto_20200729_1604.py |  40 +++++++
 passerelle/apps/ovh/models.py                 | 109 +++++++++++++++++-
 tests/test_sms.py                             |  44 +++++++
 tests/utils.py                                |   1 +
 4 files changed, 190 insertions(+), 4 deletions(-)
 create mode 100644 passerelle/apps/ovh/migrations/0009_auto_20200729_1604.py
passerelle/apps/ovh/migrations/0009_auto_20200729_1604.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-07-29 14:04
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('ovh', '0008_ovhsmsgateway_max_message_length'),
12
    ]
13

  
14
    operations = [
15
        migrations.AddField(
16
            model_name='ovhsmsgateway',
17
            name='application_key',
18
            field=models.CharField(blank=True, help_text='Random token obtained from OVH.', max_length=16, verbose_name='Application key'),
19
        ),
20
        migrations.AddField(
21
            model_name='ovhsmsgateway',
22
            name='application_secret',
23
            field=models.CharField(blank=True, help_text='Obtained at the same time as "Application secret".', max_length=32, verbose_name='Application secret'),
24
        ),
25
        migrations.AddField(
26
            model_name='ovhsmsgateway',
27
            name='consumer_key',
28
            field=models.CharField(blank=True, help_text='Obtained at the same time as "Application secret".', max_length=32, verbose_name='Consumer key'),
29
        ),
30
        migrations.AlterField(
31
            model_name='ovhsmsgateway',
32
            name='account',
33
            field=models.CharField(help_text='Account identifier, such as sms-XXXXXX-1.', max_length=64, verbose_name='Account'),
34
        ),
35
        migrations.AlterField(
36
            model_name='ovhsmsgateway',
37
            name='password',
38
            field=models.CharField(blank=True, help_text='Password for legacy API. This field is obsolete once keys and secret fields below are filled.', max_length=64, verbose_name='Password (deprecated)'),
39
        ),
40
    ]
passerelle/apps/ovh/models.py
1
import hashlib
2
import json
1 3
import requests
4
import time
2 5

  
3 6
from django.db import models
4 7
from django.utils.encoding import force_text
......
9 12

  
10 13

  
11 14
class OVHSMSGateway(SMSResource):
15
    API_URL = 'https://eu.api.ovh.com/1.0/sms/%(serviceName)s/users/%(login)s/jobs/'
12 16
    URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
13 17
    MESSAGES_CLASSES = (
14 18
        (0, _('Message are directly shown to users on phone screen '
......
21 25
        (3, _('Messages are stored in external storage like a PDA or '
22 26
              'a PC.')),
23 27
    )
24
    account = models.CharField(verbose_name=_('Account'), max_length=64)
25
    username = models.CharField(verbose_name=_('Username'), max_length=64)
26
    password = models.CharField(verbose_name=_('Password'), max_length=64)
28
    NEW_MESSAGES_CLASSES = ['flash', 'phoneDisplay', 'sim', 'toolkit']
29

  
30
    account = models.CharField(
31
        verbose_name=_('Account'), max_length=64, help_text=_('Account identifier, such as sms-XXXXXX-1.')
32
    )
33

  
34
    application_key = models.CharField(
35
        verbose_name=_('Application key'),
36
        max_length=16,
37
        blank=True,
38
        help_text=_('Random token obtained from OVH.'),
39
    )
40
    application_secret = models.CharField(
41
        verbose_name=_('Application secret'),
42
        max_length=32,
43
        blank=True,
44
        help_text=_('Obtained at the same time as "Application key".'),
45
    )
46
    consumer_key = models.CharField(
47
        verbose_name=_('Consumer key'),
48
        max_length=32,
49
        blank=True,
50
        help_text=_('Obtained at the same time as "Application key".'),
51
    )
52

  
53
    username = models.CharField(
54
        verbose_name=_('Username'),
55
        max_length=64,
56
        help_text=_('API user created on the SMS account.'),
57
    )
58
    password = models.CharField(
59
        verbose_name=_('Password (deprecated)'),
60
        max_length=64,
61
        blank=True,
62
        help_text=_(
63
            'Password for legacy API. This field is obsolete once keys and secret fields below are filled.'
64
        ),
65
    )
27 66
    msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1,
28 67
                                    verbose_name=_('Message class'))
29 68
    credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'),
......
76 115
        db_table = 'sms_ovh'
77 116

  
78 117
    def send_msg(self, text, sender, destinations, **kwargs):
79
        """Send a SMS using the OVH provider"""
118
        if not (self.application_key and self.consumer_key and self.application_secret):
119
            return self.send_msg_legacy(text, sender, destinations, **kwargs)
120

  
121
        url = self.API_URL % {'serviceName': self.account, 'login': self.username}
122
        body = {
123
            'sender': sender,
124
            'receivers': destinations,
125
            'message': text,
126
            'class': self.NEW_MESSAGES_CLASSES[self.msg_class],
127
        }
128
        if not kwargs['stop']:
129
            body.update({'noStopClause': 1})
130

  
131
        # sign request
132
        now = str(int(time.time()))
133
        signature = hashlib.sha1()
134
        to_sign = "+".join((self.application_secret, self.consumer_key, 'POST', url, json.dumps(body), now))
135
        signature.update(to_sign.encode())
136

  
137
        headers = {
138
            'X-Ovh-Application': self.application_key,
139
            'X-Ovh-Consumer': self.consumer_key,
140
            'X-Ovh-Timestamp': now,
141
            'X-Ovh-Signature': "$1$" + signature.hexdigest(),
142
        }
143

  
144
        try:
145
            response = self.requests.post(url, headers=headers, json=body)
146
        except requests.RequestException as e:
147
            raise APIError('OVH error: POST failed, %s' % e)
148
        else:
149
            try:
150
                result = response.json()
151
            except ValueError as e:
152
                raise APIError('OVH error: bad JSON response')
153
        try:
154
            response.raise_for_status()
155
        except requests.RequestException as e:
156
            raise APIError('OVH error: %s "%s"' % (e, result))
157

  
158
        ret = {}
159
        credits_removed = result['totalCreditsRemoved']
160
        # update credit left
161
        self.credit_left -= credits_removed
162
        if self.credit_left < 0:
163
            self.credit_left = 0
164
        self.save()
165
        if self.credit_left < self.credit_threshold_alert:
166
            ret['warning'] = 'credit level too low for %s: %s (threshold %s)' % (
167
                self.slug,
168
                self.credit_left,
169
                self.credit_threshold_alert,
170
            )
171
        ret['credit_left'] = self.credit_left
172
        ret['ovh_result'] = result
173
        ret['sms_ids'] = result.get('ids', [])
174

  
175
        return ret
176

  
177
    def send_msg_legacy(self, text, sender, destinations, **kwargs):
178
        """Send a SMS using the HTTP2 endpoint"""
179
        if not self.password:
180
            raise APIError('Improperly configured, empty keys or password fields.')
80 181

  
81 182
        text = force_text(text).encode('utf-8')
82 183
        to = ','.join(destinations)
tests/test_sms.py
138 138
        result = app.post_json(path, params=payload)
139 139
        connector.jobs()
140 140
        assert SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
141

  
142

  
143
def test_ovh_new_api(app, freezer):
144
    connector = OVHSMSGateway.objects.create(
145
        slug='ovh', account='sms-test42', username='john',
146
        application_key='RHrTdU2oTsrVC0pu',
147
        application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
148
        consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz'
149
    )
150
    api = ApiUser.objects.create(username='apiuser')
151
    obj_type = ContentType.objects.get_for_model(connector)
152
    # no access check
153
    AccessRight.objects.create(codename='can_send_messages', apiuser=api, resource_type=obj_type,
154
                               resource_pk=connector.pk)
155

  
156
    payload = {
157
        'message': 'hello',
158
        'from': '+33699999999',
159
        'to': ['+33688888888', '+33677777777'],
160
    }
161

  
162
    # register job
163
    freezer.move_to('2019-01-01 00:00:00')
164
    path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
165
    result = app.post_json(path, params=payload)
166
    assert result.json['err'] == 0
167
    job_id = Job.objects.get(status='registered').id
168

  
169
    # perform job
170
    freezer.move_to('2019-01-01 01:00:03')
171
    resp = {
172
        'validReceivers': ['+33688888888', '+33677777777'],
173
        'totalCreditsRemoved': 1,
174
        'ids': [241615100],
175
        'invalidReceivers': []
176
    }
177
    url = connector.API_URL % {'serviceName': 'sms-test42', 'login': 'john'}
178
    with utils.mock_url(url, resp, 200) as mocked:
179
        connector.jobs()
180
    job = Job.objects.get(id=job_id)
181
    assert job.status == 'completed'
182

  
183
    request = mocked.handlers[0].call['requests'][0]
184
    assert 'X-Ovh-Signature' in request.headers
tests/utils.py
44 44
    if not isinstance(response, str):
45 45
        response = json.dumps(response)
46 46

  
47
    @httmock.remember_called
47 48
    @httmock.urlmatch(**urlmatch_kwargs)
48 49
    def mocked(url, request):
49 50
        if exception:
50
-