0001-sms-filter-authorized-numbers-39650.patch
passerelle/apps/choosit/migrations/0010_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('choosit', '0009_choositsmsgateway_max_message_length'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='choositsmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='choositsmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
] |
passerelle/apps/mobyt/migrations/0009_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('mobyt', '0008_auto_20200310_1539'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='mobytsmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='mobytsmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
] |
passerelle/apps/orange/migrations/0009_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('orange', '0008_auto_20200412_1240'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='orangesmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='orangesmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
] |
passerelle/apps/ovh/migrations/0013_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('ovh', '0012_auto_20201027_1121'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='ovhsmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='ovhsmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
migrations.AlterField( |
|
27 |
model_name='ovhsmsgateway', |
|
28 |
name='alert_emails', |
|
29 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(blank=True, max_length=254), blank=True, null=True, size=None, verbose_name='Email addresses list to send credit alerts to, separated by comma'), |
|
30 |
), |
|
31 |
] |
passerelle/apps/oxyd/migrations/0009_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('oxyd', '0008_oxydsmsgateway_max_message_length'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='oxydsmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='oxydsmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
] |
passerelle/apps/twilio/migrations/0002_auto_20210202_1304.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-02-02 12:04 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('twilio', '0001_initial'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='twiliosmsgateway', |
|
18 |
name='allow_premium_rate', |
|
19 |
field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='twiliosmsgateway', |
|
23 |
name='authorized', |
|
24 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'), |
|
25 |
), |
|
26 |
] |
passerelle/sms/forms.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
1 | 16 |
from django import forms |
2 | 17 |
from django.utils.translation import ugettext_lazy as _ |
3 | 18 | |
19 |
from passerelle.forms import GenericConnectorForm |
|
20 | ||
21 | ||
4 | 22 |
class SmsTestSendForm(forms.Form): |
5 | 23 |
number = forms.CharField(label=_('To'), max_length=12) |
6 | 24 |
sender = forms.CharField(label=_('From'), max_length=12) |
7 | 25 |
message = forms.CharField(label=_('Message'), max_length=128) |
26 | ||
27 | ||
28 |
class SMSConnectorForm(GenericConnectorForm): |
|
29 |
def __init__(self, *args, **kwargs): |
|
30 |
from passerelle.sms.models import SMSResource |
|
31 | ||
32 |
super(SMSConnectorForm, self).__init__(*args, **kwargs) |
|
33 |
self.fields['authorized'] = forms.MultipleChoiceField( |
|
34 |
choices=SMSResource.AUTHORIZED, |
|
35 |
widget=forms.CheckboxSelectMultiple, |
|
36 |
initial=[SMSResource.ALL], |
|
37 |
label=_('Authorized Countries'), |
|
38 |
) |
passerelle/sms/models.py | ||
---|---|---|
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 |
import logging |
17 | 17 |
import re |
18 | 18 | |
19 |
from django.contrib.postgres.fields import ArrayField |
|
19 | 20 |
from django.db import models |
20 | 21 |
from django.utils import six |
21 | 22 |
from django.utils.module_loading import import_string |
22 | 23 |
from django.utils.translation import ugettext_lazy as _ |
23 | 24 | |
24 | 25 |
from passerelle.base.models import BaseResource |
25 | 26 |
from passerelle.compat import json_loads |
27 |
from passerelle.sms.forms import SMSConnectorForm |
|
26 | 28 |
from passerelle.utils.api import endpoint |
27 | 29 |
from passerelle.utils.jsonresponse import APIError |
28 | 30 | |
31 | ||
29 | 32 |
SEND_SCHEMA = { |
30 | 33 |
'$schema': 'http://json-schema.org/draft-04/schema#', |
31 | 34 |
"type": "object", |
32 | 35 |
'required': ['message', 'from', 'to'], |
33 | 36 |
'properties': { |
34 | 37 |
'message': { |
35 | 38 |
'description': 'String message', |
36 | 39 |
'type': 'string', |
... | ... | |
47 | 50 |
'pattern': r'^\+?[-.\s/\d]+$' |
48 | 51 |
}, |
49 | 52 |
}, |
50 | 53 |
} |
51 | 54 |
} |
52 | 55 | |
53 | 56 | |
54 | 57 |
class SMSResource(BaseResource): |
58 |
manager_form_base_class = SMSConnectorForm |
|
55 | 59 |
category = _('SMS Providers') |
56 | 60 |
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' |
57 | 61 | |
58 | 62 |
_can_send_messages_description = _('Sending messages is limited to the following API users:') |
59 | 63 | |
60 | 64 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
61 | 65 |
default=u'33') |
62 | 66 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
63 | 67 |
default=u'0') # Yeah France first ! |
64 |
# FIXME: add regexp field, to check destination and from format |
|
65 | 68 |
max_message_length = models.IntegerField(_('Maximum message length'), default=160) |
66 | 69 | |
67 | 70 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
68 | 71 | |
72 |
FR_METRO = 'fr-metro' |
|
73 |
FR_DOMTOM = 'fr-domtom' |
|
74 |
BE_ = 'be' |
|
75 |
ALL = 'all' |
|
76 |
AUTHORIZED = [ |
|
77 |
(FR_METRO, _('France mainland (+33 [67])')), |
|
78 |
(FR_DOMTOM, _('France DOM/TOM (+262, etc.)')), |
|
79 |
(BE_, _('Belgian (+32 4[5-9]) ')), |
|
80 |
(ALL, _('All')), |
|
81 |
] |
|
82 |
authorized = ArrayField( |
|
83 |
models.CharField(max_length=32, null=True, choices=AUTHORIZED), |
|
84 |
verbose_name=_('Authorized Countries'), |
|
85 |
default=[ALL]) |
|
86 | ||
87 |
allow_premium_rate = models.BooleanField( |
|
88 |
_('Allow premium rate numbers'), default=False, |
|
89 |
help_text=_('This option is only applyed to France mainland') |
|
90 |
) |
|
91 | ||
69 | 92 |
@classmethod |
70 | 93 |
def get_management_urls(cls): |
71 | 94 |
return import_string('passerelle.sms.urls.management_urlpatterns') |
72 | 95 | |
96 |
def _get_authorized_display(self): |
|
97 |
result = [] |
|
98 |
for key, value in self.AUTHORIZED: |
|
99 |
if key in self.authorized: |
|
100 |
result.append(str(value)) |
|
101 |
return ', '.join(result) |
|
102 | ||
103 |
def __init__(self, *args, **kwargs): |
|
104 |
super(SMSResource, self).__init__(*args, **kwargs) |
|
105 |
self.get_authorized_display = self._get_authorized_display |
|
106 | ||
73 | 107 |
def clean_numbers(self, destinations): |
74 | 108 |
numbers = [] |
75 | 109 |
for dest in destinations: |
76 | 110 |
# most gateways needs the number prefixed by the country code, this is |
77 | 111 |
# really unfortunate. |
78 | 112 |
dest = dest.strip() |
79 | 113 |
number = ''.join(re.findall('[0-9]', dest)) |
80 | 114 |
if dest.startswith('+'): |
... | ... | |
85 | 119 |
elif number.startswith(self.default_trunk_prefix): |
86 | 120 |
number = '00' + self.default_country_code + number[len(self.default_trunk_prefix):] |
87 | 121 |
else: |
88 | 122 |
raise APIError('phone number %r is unsupported (no international prefix, ' |
89 | 123 |
'no local trunk prefix)' % number) |
90 | 124 |
numbers.append(number) |
91 | 125 |
return numbers |
92 | 126 | |
127 |
def authorize_numbers(self, destinations): |
|
128 |
foreign_numbers = set() |
|
129 |
premium_numbers = set() |
|
130 |
premium_regex = re.compile(r'^0033[8]\d{8}$') |
|
131 |
regexes = [] |
|
132 |
if SMSResource.ALL not in self.authorized: |
|
133 |
if SMSResource.FR_METRO in self.authorized: |
|
134 |
regexes.append(r'^0033[67]\d{8}$') # France |
|
135 |
if SMSResource.FR_DOMTOM in self.authorized: |
|
136 |
regexes.append(r'^00262262\d{6}$') # Réunion, Mayotte, Terres australe/antarctiques |
|
137 |
regexes.append(r'^508508\d{6}$') # Saint-Pierre-et-Miquelon |
|
138 |
regexes.append(r'^590590\d{6}$') # Guadeloupe, Saint-Barthélemy, Saint-Martin |
|
139 |
regexes.append(r'^594594\d{6}$') # Guyane |
|
140 |
regexes.append(r'^596596\d{6}$') # Martinique |
|
141 |
regexes.append(r'^00687[67]\d{8}$') # Nouvelle-Calédonie |
|
142 |
if SMSResource.BE_ in self.authorized: |
|
143 |
regexes.append(r'^00324[5-9]\d{7}$') # Belgian |
|
144 |
country_regex = re.compile('|'.join(regexes)) |
|
145 | ||
146 |
if not self.allow_premium_rate: |
|
147 |
premium_numbers = set(dest for dest in destinations if premium_regex.match(dest)) |
|
148 |
if SMSResource.ALL not in self.authorized: |
|
149 |
foreign_numbers = set(dest for dest in destinations if not country_regex.match(dest)) |
|
150 |
for number in list(premium_numbers): |
|
151 |
logging.warning('unauthorized premium rate phone number: %s', number) |
|
152 |
for number in list(foreign_numbers - premium_numbers): |
|
153 |
logging.warning('unauthorized foreign phone number: %s', number) |
|
154 |
authorized_numbers = list(set(destinations) - foreign_numbers - premium_numbers) |
|
155 |
if len(authorized_numbers) == 0: |
|
156 |
raise APIError('no phone number was authorized: %s' % ', '.join(destinations)) |
|
157 |
return authorized_numbers |
|
158 | ||
93 | 159 |
@endpoint(perm='can_send_messages', methods=['post'], |
94 | 160 |
description=_('Send a SMS message'), |
95 | 161 |
parameters={'nostop': {'description': _('Do not send STOP instruction'), 'example_value': '1'}}, |
96 | 162 |
post={'request_body': {'schema': {'application/json': SEND_SCHEMA}}}) |
97 | 163 |
def send(self, request, post_data, nostop=None): |
98 | 164 |
post_data['message'] = post_data['message'][:self.max_message_length] |
99 | 165 |
post_data['to'] = self.clean_numbers(post_data['to']) |
166 |
post_data['to'] = self.authorize_numbers(post_data['to']) |
|
100 | 167 |
logging.info('sending SMS to %r from %r', post_data['to'], post_data['from']) |
101 | 168 |
stop = nostop is None # ?nostop in not in query string |
102 | 169 |
self.add_job('send_job', |
103 | 170 |
text=post_data['message'], sender=post_data['from'], destinations=post_data['to'], |
104 | 171 |
stop=stop) |
105 | 172 |
return {'err': 0} |
106 | 173 | |
107 | 174 |
def send_job(self, *args, **kwargs): |
passerelle/sms/views.py | ||
---|---|---|
24 | 24 | |
25 | 25 |
def form_valid(self, form): |
26 | 26 |
number = form.cleaned_data['number'] |
27 | 27 |
sender = form.cleaned_data['sender'] |
28 | 28 |
message = form.cleaned_data['message'] |
29 | 29 |
connector = self.get_object() |
30 | 30 |
try: |
31 | 31 |
number = connector.clean_numbers([number])[0] |
32 |
number = connector.authorize_numbers([number])[0] |
|
32 | 33 |
connector.send_msg( |
33 | 34 |
text=message, sender=sender, destinations=[number], stop=False) |
34 | 35 |
except APIError as exc: |
35 | 36 |
messages.error(self.request, _('Sending SMS fails: %s' % exc)) |
36 | 37 |
else: |
37 | 38 |
messages.success(self.request, _('An SMS was just sent')) |
38 | 39 |
return super(SmsTestSendView, self).form_valid(form) |
tests/test_sms.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
1 | 16 |
import isodate |
2 | 17 |
import json |
18 |
import logging |
|
3 | 19 |
import mock |
4 | 20 |
import pytest |
5 | 21 |
from requests import RequestException |
6 | 22 | |
7 | 23 |
from django.conf import settings |
8 | 24 |
from django.contrib.contenttypes.models import ContentType |
9 | 25 |
from django.urls import reverse |
26 |
from django.utils.translation import ugettext as _ |
|
10 | 27 | |
28 |
from passerelle.apps.choosit.models import ChoositSMSGateway |
|
11 | 29 |
from passerelle.apps.ovh.models import OVHSMSGateway |
12 |
from passerelle.base.models import ApiUser, AccessRight, Job |
|
30 |
from passerelle.base.models import ApiUser, AccessRight, Job, ResourceLog
|
|
13 | 31 |
from passerelle.sms.models import SMSResource, SMSLog |
14 | 32 |
from passerelle.utils.jsonresponse import APIError |
15 | 33 | |
16 | 34 |
from test_manager import login |
17 | 35 | |
18 | 36 |
import utils |
19 | 37 | |
20 | 38 |
pytestmark = pytest.mark.django_db |
... | ... | |
32 | 50 |
connector.save() |
33 | 51 |
assert connector.clean_numbers(['+ 33 12']) == ['003312'] |
34 | 52 |
assert connector.clean_numbers(['0 0 33 12']) == ['003312'] |
35 | 53 |
assert connector.clean_numbers(['1 12']) == ['003212'] |
36 | 54 |
with pytest.raises(APIError, match='phone number %r is unsupported' % '0123'): |
37 | 55 |
connector.clean_numbers(['0123']) |
38 | 56 | |
39 | 57 | |
58 |
def test_authorize_numbers(): |
|
59 |
connector = OVHSMSGateway() |
|
60 | ||
61 |
# premium-rate |
|
62 |
assert connector.allow_premium_rate == False |
|
63 |
number = '0033' + '8' + '12345678' |
|
64 |
with pytest.raises(APIError, match='no phone number was authorized: %s' % number): |
|
65 |
assert connector.authorize_numbers([number]) |
|
66 |
connector.allow_premium_rate = True |
|
67 |
connector.save() |
|
68 | ||
69 |
# All country |
|
70 |
assert connector.authorized == [SMSResource.ALL] |
|
71 |
number = '0033' + '1' + '12345678' |
|
72 |
assert connector.authorize_numbers([number]) == [number] |
|
73 |
connector.authorized = [SMSResource.FR_METRO] |
|
74 |
connector.save() |
|
75 |
with pytest.raises(APIError, match='no phone number was authorized: %s' % number): |
|
76 |
connector.authorize_numbers([number]) |
|
77 | ||
78 |
# France |
|
79 |
number = '0033' + '6' + '12345678' |
|
80 |
assert connector.authorize_numbers([number]) == [number] |
|
81 |
connector.authorized = [SMSResource.FR_DOMTOM] |
|
82 |
connector.save() |
|
83 |
with pytest.raises(APIError, match='no phone number was authorized: %s' % number): |
|
84 |
connector.authorize_numbers([number]) |
|
85 | ||
86 |
# Dom-Tom |
|
87 |
number = '596596' + '123456' |
|
88 |
assert connector.authorize_numbers([number]) == [number] |
|
89 |
connector.authorized = [SMSResource.BE_] |
|
90 |
connector.save() |
|
91 |
with pytest.raises(APIError, match='no phone number was authorized: %s' % number): |
|
92 |
connector.authorize_numbers([number]) |
|
93 | ||
94 |
# Belgian |
|
95 |
number = '0032' + '45' + '1234567' |
|
96 |
assert connector.authorize_numbers([number]) == [number] |
|
97 |
connector.authorized = [SMSResource.FR_METRO] |
|
98 |
connector.save() |
|
99 |
with pytest.raises(APIError, match='no phone number was authorized: %s' % number): |
|
100 |
connector.authorize_numbers([number]) |
|
101 | ||
102 |
# Don't raise if filtered destination is not empty |
|
103 |
numbers = ['0033' + '6' + '12345678', '0032' + '45' + '1234567'] |
|
104 |
assert len(connector.authorize_numbers(numbers)) == 1 |
|
105 | ||
106 | ||
40 | 107 |
@pytest.fixture(params=klasses) |
41 | 108 |
def connector(request, db): |
42 | 109 |
klass = request.param |
43 | 110 |
kwargs = getattr(klass, 'TEST_DEFAULTS', {}).get('create_kwargs', {}) |
44 | 111 |
kwargs.update({ |
45 | 112 |
'title': klass.__name__, |
46 | 113 |
'slug': klass.__name__.lower(), |
47 | 114 |
'description': klass.__name__, |
... | ... | |
393 | 460 |
body = json.loads(request.body.decode()) |
394 | 461 |
assert 'accessRules' in body |
395 | 462 |
redirect_url = body['redirection'][len('http://testserver'):] |
396 | 463 | |
397 | 464 |
resp = app.get(redirect_url).follow() |
398 | 465 |
assert 'Successfuly completed connector configuration' in resp.text |
399 | 466 |
connector.refresh_from_db() |
400 | 467 |
assert connector.consumer_key == 'xyz' |
468 | ||
469 | ||
470 |
@pytest.mark.parametrize('connector', [ChoositSMSGateway], indirect=True) |
|
471 |
def test_manager(admin_user, app, connector): |
|
472 |
app = login(app) |
|
473 |
path = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) |
|
474 |
resp = app.get(path) |
|
475 |
assert '33' in [ |
|
476 |
x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p') |
|
477 |
if x.text.startswith(_('Default country code'))][0] |
|
478 |
assert _('All') in [ |
|
479 |
x.text for x in resp.html.find_all('p') |
|
480 |
if x.text.startswith(_('Authorized Countries'))][0] |
|
481 |
assert _('no') in [ |
|
482 |
x.text for x in resp.html.find_all('p') |
|
483 |
if x.text.startswith(_('Allow premium rate numbers'))][0] |
|
484 | ||
485 |
path = '/manage/%s/%s/edit' % (connector.get_connector_slug(), connector.slug) |
|
486 |
resp = app.get(path) |
|
487 |
resp.form['authorized'] = [] |
|
488 |
resp = resp.form.submit() |
|
489 |
assert resp.html.find('div', {'class': 'errornotice'}).p.text == \ |
|
490 |
'There were errors processing your form.' |
|
491 |
assert resp.html.find('div', {'class': 'error'}).text.strip() == 'This field is required.' |
|
492 |
resp.form['authorized'] = [SMSResource.FR_METRO, SMSResource.FR_DOMTOM] |
|
493 |
resp = resp.form.submit() |
|
494 |
resp = resp.follow() |
|
495 |
assert _('France mainland (+33 [67])') in [ |
|
496 |
x.text for x in resp.html.find_all('p') |
|
497 |
if x.text.startswith(_('Authorized Countries'))][0] |
|
498 | ||
499 |
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug) |
|
500 |
payload = { |
|
501 |
'message': 'plop', |
|
502 |
'from': '+33699999999', |
|
503 |
'to': ['+33688888888'], |
|
504 |
} |
|
505 |
app.post_json(path, params=payload) |
|
506 |
with mock.patch.object(type(connector), 'send_msg') as send_function: |
|
507 |
send_function.return_value = {} |
|
508 |
connector.jobs() |
|
509 |
assert SMSLog.objects.count() == 1 |
|
510 | ||
511 |
payload['to'][0] = '+33188888888' |
|
512 |
SMSLog.objects.all().delete() |
|
513 |
app.post_json(path, params=payload) |
|
514 |
with mock.patch.object(type(connector), 'send_msg') as send_function: |
|
515 |
send_function.return_value = {} |
|
516 |
connector.jobs() |
|
517 |
assert not SMSLog.objects.count() |
|
518 |
assert ResourceLog.objects.filter(levelno=logging.WARNING).count() == 1 |
|
519 |
assert ResourceLog.objects.filter(levelno=30)[0].extra['exception'] == \ |
|
520 |
'no phone number was authorized: 0033188888888' |
|
401 |
- |