Пример #1
0
def subscribe_sms_dr_endpoint(endpoint_url, silent_failure=False):
    sender_address = get_config('sender_address')
    payload = {
        'deliveryReceiptSubscription': {
            'callbackReference': {
                'notifyURL': endpoint_url
            }
        }
    }
    url = "{api}/outbound/{addr}/subscriptions".format(
        api=get_config('smsmt_url'),
        addr=quote(sender_address))
    headers = get_standard_header()

    req = requests.post(url, headers=headers, json=payload)

    try:
        assert req.status_code == 201
        resp = req.json()
    except AssertionError:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to subscribe an SMS-DR endpoint. {exp}"
                     .format(exp=exp))
        logger.exception(exp)
        if not silent_failure:
            raise exp
        return False

    subscription_id = resp['deliveryReceiptSubscription'] \
        .get('resourceURL', '').rsplit('/', 1)[-1]

    if subscription_id:
        update_config({'smsmtdr_subsription_id': subscription_id}, save=True)
    return bool(subscription_id)
Пример #2
0
def request_token(silent_failure=False):
    url = "{oauth_url}/token".format(oauth_url=get_config('oauth_url'))
    basic_header = b64encode(
        "{client_id}:{client_secret}".format(
            client_id=get_config('client_id'),
            client_secret=get_config('client_secret')))
    headers = {'Authorization': "Basic {b64}".format(b64=basic_header)}
    payload = {'grant_type': 'client_credentials'}
    req = requests.post(url, headers=headers, data=payload)
    resp = req.json()
    if "token_type" in resp:
        expire_in = int(resp['expires_in'])
        token_data = {
            'token': resp['access_token'],
            'token_expiry': datetime.datetime.now() + datetime.timedelta(
                days=expire_in / ONE_DAY,
                seconds=expire_in % ONE_DAY) - datetime.timedelta(
                days=0, seconds=60)}
        update_config(token_data, save=True)
        return token_data
    else:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to retrieve token. {}".format(exp))
        logger.exception(exp)
        if not silent_failure:
            raise exp
        return False
Пример #3
0
def submit_sms_mt(address, message,
                  sender_name=get_config('default_sender_name'),
                  callback_data=None):
    return submit_sms_mt_request(
        mt_payload(dest_addr=cleaned_msisdn(address),
                   message=message,
                   sender_address=get_config('sender_address'),
                   sender_name=sender_name))
Пример #4
0
def send_sms(to_addr, message,
             as_addr=get_config('default_sender_name'),
             db_save=get_config('use_db')):
    ''' SMS-MT shortcut function '''
    to_addr = cleaned_msisdn(to_addr)
    if not db_save:
        return submit_sms_mt(to_addr, message, as_addr)
    msg = SMSMessage.create_mt(to_addr, message,
                               as_addr, SMSMessage.PENDING)
    success = submit_sms_mt_request(msg.to_mt(), msg)
    msg.sending_status = msg.SENT if success else msg.FAILED_TO_SEND
    msg.save()
    return success, msg
Пример #5
0
    def record_dr_from_payload(cls, payload):
        # no DR support in non-DB mode
        if not get_config('use_db'):
            return

        uuid = payload.get('callbackData')
        msg = cls.get_or_none(uuid)
        if msg is None:
            raise ValueError(
                "SMS-DR reference unreachable SMS-MT `{uuid}`".format(
                    uuid=uuid))
        kwargs = {
            'sms_type':
            cls.DR,
            'delivery_status_on':
            aware_datetime_from_iso(
                payload.get('delivery_status_on',
                            datetime_to_iso(timezone.now()))),
            'status':
            cls.DELIVERY_STATUS_MATRIX.get(
                payload.get('deliveryInfo', {}).get('deliveryStatus',
                                                    cls.NOT_DELIVERED))
        }
        msg.update(**kwargs)
        msg.save()
        return msg
Пример #6
0
def home(request):
    context = {'page': 'home',
               'disable_tester': not get_config('enable_tester', False)}

    from django.core.urlresolvers import reverse

    url_mo = request.build_absolute_uri(reverse('oapisms_mo'))
    url_mtdr = request.build_absolute_uri(reverse('oapisms_dr'))
    try:
        smsmt_dr_endpoint = get_sms_dr_endpoint(False)
    except Exception as exp:
        messages.error(request, exp)
        smsmt_dr_endpoint = None

    context.update({
        'endpoints': [
            ("SMS-MO", url_mo, None),
            ("SMS-DR", url_mtdr,
             ('oapisms_register_smsdr_endpoint', "register")),
            ("Registered SMS-DR", smsmt_dr_endpoint,
             ('oapisms_unregister_smsdr_endpoint', "unregister"))
        ]
    })

    return render(request, 'orangeapisms/home.html', context)
Пример #7
0
def get_handler(slug):
    stub = 'orangeapisms.stub'
    mod = get_config('handler_module')
    if mod is None:
        mod = stub

    return import_path('handle_{}'.format(slug), module=mod, fallback=stub)
Пример #8
0
def get_sms_dr_subscriptions(silent_failure=False):
    subscription_id = get_config('smsmtdr_subsription_id')
    if not subscription_id:
        return {}
    url = "{api}/outbound/subscriptions/{subscription}".format(
        api=get_config('smsmt_url'),
        subscription=subscription_id)
    headers = get_standard_header()

    req = requests.get(url, headers=headers)
    try:
        assert req.status_code == 200
        return req.json()
    except AssertionError:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to retrieve contracts. {exp}".format(exp=exp))
        logger.exception(exp)
        if not silent_failure:
            raise exp
Пример #9
0
def get_contracts(silent_failure=False):
    url = "{api}/contracts".format(api=get_config('smsadmin_url'))
    headers = get_standard_header()

    req = requests.get(url, headers=headers)
    try:
        assert req.status_code == 200
        return req.json()
    except AssertionError:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to retrieve contracts. {exp}".format(exp=exp))
        logger.exception(exp)
        if not silent_failure:
            raise exp
Пример #10
0
def unsubscribe_sms_dr_endpoint(subscription_id, silent_failure=False):
    sender_address = get_config('sender_address')
    url = "{api}/outbound/{addr}/subscriptions/{sub}".format(
        api=get_config('smsmt_url'),
        addr=quote(sender_address),
        sub=subscription_id)
    headers = get_standard_header()

    req = requests.delete(url, headers=headers)

    try:
        assert req.status_code == 204
        update_config({'smsmtdr_subsription_id': None}, save=True)
    except AssertionError:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to unsubscribe an SMS-DR endpoint. {exp}"
                     .format(exp=exp))
        logger.exception(exp)
        if not silent_failure:
            raise exp
        return False

    return True
Пример #11
0
def cleaned_msisdn(to_addr):
    """ fixes common mistakes to make to_addr a MSISDN

        - removes extra chars
        - starts with a +
        - adds prefix if it seems missing """

    if not get_config('fix_msisdn'):
        return to_addr

    # harmonize intl. number format (00xxx) to +xxx
    to_addr = re.sub(r"^00", "+", to_addr)

    # if a suffix was supplied, fix chars only
    if to_addr.startswith('+'):
        return "+{addr}".format(addr=re.sub(r"\D", "", to_addr))

    # no prefix, make sure to remove default prefix if present and fix chars
    prefix = get_config('country_prefix')
    to_addr = re.sub(r"\D", "", to_addr)
    if to_addr.startswith(prefix):
        to_addr = re.sub(r"^{prefix}".format(prefix=prefix), "", to_addr)
    return "+{prefix}{addr}".format(prefix=prefix, addr=to_addr)
Пример #12
0
    def create_mt(cls, destination_address, content,
                  sender_address=None, sending_status=SENT):
        kwargs = {
            'direction': cls.OUTGOING,
            'sms_type': cls.MT,
            'created_on': timezone.now(),

            'sender_address': sender_address,
            'destination_address': destination_address,
            'content': content,
            'status': sending_status,
        }
        if not get_config('use_db'):
            return cls(**kwargs)
        return cls.objects.create(**kwargs)
Пример #13
0
    def create_mo_from_payload(cls, payload):
        kwargs = {
            'direction': cls.INCOMING,
            'sms_type': cls.MO,
            'status': cls.RECEIVED,

            'sender_address': cls.clean_address(payload.get('senderAddress')),
            'destination_address':
                cls.clean_address(payload.get('destinationAddress')),
            'message_id': payload.get('messageId'),
            'content': payload.get('message'),
            'created_on': aware_datetime_from_iso(payload.get('dateTime'))
        }
        if not get_config('use_db'):
            return cls(**kwargs)
        return cls.objects.create(**kwargs)
Пример #14
0
def home(request):
    context = {'page': 'home',
               'disable_tester': not get_config('enable_tester', False)}

    from django.core.urlresolvers import reverse

    url_mo = request.build_absolute_uri(reverse('oapisms_mo'))
    url_mt = request.build_absolute_uri(reverse('oapisms_dr'))
    context.update({
        'endpoints': [
            ('SMS-MO', url_mo),
            ('SMS-DR', url_mt),
        ]
    })

    return render(request, 'orangeapisms/home.html', context)
Пример #15
0
    def handle_request(active_form, form):
        if active_form == 'smsmt':
            success, msg = send_sms(
                to_addr=form.cleaned_data.get('destination_address'),
                message=form.cleaned_data.get('content'),
                as_addr=form.cleaned_data.get('sender_name')
                or get_config('default_sender_name'))
            handle_smsmt(msg)
            return success, "Sent {}".format(msg)

        elif active_form == 'fsmsmt':
            msg = SMSMessage.create_mt(
                destination_address=form.cleaned_data.get(
                    'destination_address'),
                content=form.cleaned_data.get('content'),
                sender_address=form.cleaned_data.get('sender_address'),
                sending_status=form.cleaned_data.get('status'))
            handle_smsmo(msg)
            return True, "Sent {}".format(msg)

        elif active_form == 'fsmsmo':
            msg = SMSMessage.create_mo_from_payload({
                'senderAddress': "tel:{}".format(
                    form.cleaned_data.get('sender_address')),
                'destinationAddress': form.cleaned_data.get(
                    'destination_address'),
                'messageId': form.cleaned_data.get('message_id') or None,
                'message': form.cleaned_data.get('content'),
                'dateTime': datetime_to_iso(
                    form.cleaned_data.get('created_on'))
            })
            handle_smsmo(msg)
            return True, "Received {}".format(msg)

        elif active_form == 'fsmsdr':
            msg = SMSMessage.record_dr_from_payload({
                'callbackData': form.cleaned_data.get('uuid'),
                'delivery_status_on': datetime_to_iso(
                    form.cleaned_data.get('delivery_on')),
                'deliveryInfo': {
                    'deliveryStatus': form.cleaned_data.get('status')
                }
            })
            return True, "Updated {}".format(msg)

        else:
            return False, "Unknown action `{}`".format(active_form)
Пример #16
0
    def handle_request(active_form, form):
        if active_form == 'smsmt':
            success, msg = send_sms(
                to_addr=form.cleaned_data.get('destination_address'),
                message=form.cleaned_data.get('content'),
                as_addr=form.cleaned_data.get('sender_name') or
                get_config('default_sender_name'))
            handle_smsmt(msg)
            return success, "Sent {}".format(msg)

        elif active_form == 'fsmsmt':
            msg = SMSMessage.create_mt(
                destination_address=form.cleaned_data.get(
                    'destination_address'),
                content=form.cleaned_data.get('content'),
                sender_address=form.cleaned_data.get('sender_address'),
                sending_status=form.cleaned_data.get('status'))
            handle_smsmo(msg)
            return True, "Sent {}".format(msg)

        elif active_form == 'fsmsmo':
            msg = SMSMessage.create_mo_from_payload({
                'senderAddress': "tel:{}".format(
                    form.cleaned_data.get('sender_address')),
                'destinationAddress': form.cleaned_data.get(
                    'destination_address'),
                'messageId': form.cleaned_data.get('message_id') or None,
                'message': form.cleaned_data.get('content'),
                'dateTime': datetime_to_iso(
                    form.cleaned_data.get('created_on'))
            })
            handle_smsmo(msg)
            return True, "Received {}".format(msg)

        elif active_form == 'fsmsdr':
            msg = SMSMessage.record_dr_from_payload({
                'callbackData': form.cleaned_data.get('uuid'),
                'delivery_status_on': datetime_to_iso(
                    form.cleaned_data.get('delivery_on')),
                'deliveryInfo': {
                    'deliveryStatus': form.cleaned_data.get('status')
                }
            })
            return True, "Updated {}".format(msg)

        else:
            return False, "Unknown action `{}`".format(active_form)
Пример #17
0
 def create_mt(cls,
               destination_address,
               content,
               sender_address=None,
               sending_status=SENT):
     kwargs = {
         'direction': cls.OUTGOING,
         'sms_type': cls.MT,
         'created_on': timezone.now(),
         'sender_address': sender_address,
         'destination_address': destination_address,
         'content': content,
         'status': sending_status,
     }
     if not get_config('use_db'):
         return cls(**kwargs)
     return cls.objects.create(**kwargs)
Пример #18
0
def register_smsdr_endpoint(request):
    from django.core.urlresolvers import reverse
    endpoint_url = request.build_absolute_uri(reverse('oapisms_dr'))
    try:
        assert subscribe_sms_dr_endpoint(endpoint_url) is True
        feedback = "Successfuly set {url} as SMS-DR endpoint. " \
                   "Subscription ID: {sub}" \
                   .format(url=endpoint_url,
                           sub=get_config('smsmtdr_subsription_id'))
        lvl = messages.SUCCESS
    except AssertionError:
        feedback = "Unable to subscribe SMS-DR endpoint."
        lvl = messages.WARNING
    except Exception as e:
        feedback = e.__str__()
        lvl = messages.WARNING
    messages.add_message(request, lvl, feedback)
    return redirect('oapisms_home')
Пример #19
0
def check_balance(request):
    now = timezone.now()
    balance, expiry = get_sms_balance()

    if expiry <= now:
        balance_msg = "{balance} remaining SMS expired on {date}. Top-up " \
                      "account to extend expiry date ({country})"
    else:
        balance_msg = "{balance} SMS remaining until {date} ({country})"
    try:
        feedback = balance_msg.format(balance=balance,
            country=get_config('country'), date=expiry.strftime('%c'))
        lvl = messages.INFO
    except Exception as e:
        feedback = e.__str__()
        lvl = messages.WARNING
    messages.add_message(request, lvl, feedback)
    return redirect('oapisms_tester')
Пример #20
0
def unregister_smsdr_endpoint(request):
    subscription_id = get_config('smsmtdr_subsription_id')
    if subscription_id is not None:
        try:
            assert unsubscribe_sms_dr_endpoint(subscription_id) is True
            feedback = "Successfuly unsubscribed SMS-DR endpoint."
            lvl = messages.SUCCESS
        except AssertionError:
            feedback = "Unable to unsubscribe SMS-DR endpoint."
            lvl = messages.WARNING
        except Exception as e:
            feedback = e.__str__()
            lvl = messages.WARNING
    else:
        lvl = messages.WARNING
        feedback = "No registered SMS-DR endpoint to unsubscribe."
    messages.add_message(request, lvl, feedback)
    return redirect('oapisms_home')
Пример #21
0
class SMSMTForm(forms.Form):
    destination_address = forms.CharField(
        widget=forms.TextInput(attrs={'required': True,
                                      'max_length': 255}))
    sender_name = forms.CharField(
        required=False,
        widget=forms.TextInput(attrs={
            'placeholder': get_config('default_sender_name'),
            'max_length': 255}))
    content = forms.CharField(widget=forms.Textarea(
        attrs={'rows': 3, 'max_length': 1600, 'required': True}))

    @classmethod
    def get_initial(cls):
        return {}

    def clean_destination_address(self):
        return cleaned_msisdn(self.cleaned_data.get('destination_address'))
Пример #22
0
def check_balance(request):
    now = timezone.now()
    balance, expiry = get_sms_balance()

    if expiry <= now:
        balance_msg = "{balance} remaining SMS expired on {date}. Top-up " \
                      "account to extend expiry date ({country})"
    else:
        balance_msg = "{balance} SMS remaining until {date} ({country})"
    try:
        feedback = balance_msg.format(
            balance=balance,
            country=get_config('country'), date=expiry.strftime('%c'))
        lvl = messages.INFO
    except Exception as e:
        feedback = e.__str__()
        lvl = messages.WARNING
    messages.add_message(request, lvl, feedback)
    return redirect('oapisms_tester')
Пример #23
0
    def record_dr_from_payload(cls, payload):
        # no DR support in non-DB mode
        if not get_config('use_db'):
            return

        uuid = payload.get('callbackData')
        msg = cls.get_or_none(uuid)
        if msg is None:
            raise ValueError("SMS-DR reference unreachable SMS-MT `{uuid}`"
                             .format(uuid=uuid))
        kwargs = {
            'sms_type': cls.DR,
            'delivery_status_on': aware_datetime_from_iso(
                payload.get('delivery_status_on',
                            datetime_to_iso(timezone.now()))),
            'status': cls.DELIVERY_STATUS_MATRIX.get(
                payload.get('deliveryInfo', {})
                       .get('deliveryStatus', cls.NOT_DELIVERED))
        }
        msg.update(**kwargs)
        msg.save()
        return msg
Пример #24
0
def get_sms_balance(country=get_config('country')):
    contracts = get_contracts()
    expiry = None
    balance = 0
    for contract in contracts.get('partnerContracts', {}).get('contracts', []):
        if not contract['service'] == SMS_SERVICE:
            continue

        for sc in contract.get('serviceContracts', []):
            if not sc['country'] == country:
                continue

            if not sc['service'] == SMS_SERVICE:
                continue

            balance += sc['availableUnits']
            expires = datetime_from_iso(sc['expires'])
            if expiry is None or expiry < expires:
                expiry = expires
    if expiry is not None:
        expiry = API_TZ.localize(expiry)
    return balance, expiry
Пример #25
0
 def create_mo_from_payload(cls, payload):
     kwargs = {
         'direction':
         cls.INCOMING,
         'sms_type':
         cls.MO,
         'status':
         cls.RECEIVED,
         'sender_address':
         cls.clean_address(payload.get('senderAddress')),
         'destination_address':
         cls.clean_address(payload.get('destinationAddress')),
         'message_id':
         payload.get('messageId'),
         'content':
         payload.get('message'),
         'created_on':
         aware_datetime_from_iso(payload.get('dateTime'))
     }
     if not get_config('use_db'):
         return cls(**kwargs)
     return cls.objects.create(**kwargs)
Пример #26
0
class FSMSMOForm(forms.Form):
    sender_address = forms.CharField(
        widget=forms.TextInput(attrs={'required': True,
                                      'max_length': 255}))
    created_on = forms.DateTimeField()
    destination_address = forms.CharField(
        required=False,
        widget=forms.TextInput(attrs={
            'placeholder': get_config('sender_address'),
            'max_length': 255}))
    message_id = forms.CharField(
        required=False,
        widget=forms.TextInput(attrs={'max_length': 64,
                                      'required': False}))
    content = forms.CharField(widget=forms.Textarea(
        attrs={'rows': 3, 'max_length': 1600, 'required': True}))

    @classmethod
    def get_initial(cls):
        return {'created_on': timezone.now()}

    def clean_destination_address(self):
        return cleaned_msisdn(self.cleaned_data.get('destination_address'))
Пример #27
0
def do_submit_sms_mt_request(payload, message=None, silent_failure=False):
    ''' Use submit_sms_mt_request

    actual submission of API request for SMS-MT '''
    def update_status(msg, success):
        if msg is None:
            return
        msg.update_status(msg.SENT if success else msg.FAILED_TO_SEND)
        return success

    sender_address = payload['outboundSMSMessageRequest']['senderAddress']
    url = "{api}/outbound/{addr}/requests".format(
        api=get_config('smsmt_url'),
        addr=quote(sender_address))
    headers = get_standard_header()

    req = requests.post(url, headers=headers, json=payload)

    try:
        assert req.status_code == 201
        resp = req.json()
    except AssertionError:
        exp = OrangeAPIError.from_request(req)
        logger.error("Unable to transmit SMS-MT. {exp}".format(exp=exp))
        logger.exception(exp)
        update_status(message, False)
        if not silent_failure:
            raise exp
        return False

    rurl = resp['outboundSMSMessageRequest'] \
        .get('resourceURL', '').rsplit('/', 1)[-1]
    if message is not None and rurl:
        message.update_reference(rurl)
        return update_status(message, True)
    return bool(rurl)
Пример #28
0
 def to_mt(self):
     from orangeapisms.utils import mt_payload
     return mt_payload(dest_addr=self.destination_address,
                       message=self.content,
                       sender_address=get_config('sender_address'),
                       sender_name=self.sender_address)
Пример #29
0
 def update_status(self, status):
     self.status = status
     if not get_config('use_db'):
         return
     self.save()
Пример #30
0
 def update_status(self, status):
     self.status = status
     if not get_config('use_db'):
         return
     self.save()
Пример #31
0
 def update_reference(self, reference_code):
     self.reference_code = reference_code
     if not get_config('use_db'):
         return
     self.save()
Пример #32
0
 def to_mt(self):
     from orangeapisms.utils import mt_payload
     return mt_payload(dest_addr=self.destination_address,
                       message=self.content,
                       sender_address=get_config('sender_address'),
                       sender_name=self.sender_address)
Пример #33
0
 def _decorated(*args, **kwargs):
     if not get_config('enable_tester', False):
         raise PermissionDenied
     return aview(*args, **kwargs)
Пример #34
0
 def update_reference(self, reference_code):
     self.reference_code = reference_code
     if not get_config('use_db'):
         return
     self.save()
Пример #35
0
 def _decorated(*args, **kwargs):
     if not get_config('enable_tester', False):
         raise PermissionDenied
     return aview(*args, **kwargs)
Пример #36
0
        modname, __, attr = name.rpartition('.')
        if not modname:
            # single module name
            return __import__(attr)
        m = __import__(modname, fromlist=[attr])
        return getattr(m, attr)

    def ret(mod, call):
        return do_import('{module}.{callable}'
                         .format(module=mod, callable=call))

    try:
        return ret(module, callable_name)
    except (ImportError, AttributeError):
        if fallback is None:
            return None
        return ret(fallback, callable_name)

SEND_ASYNC = get_config('send_async')
CELERY_TASK = import_path('submit_sms_mt_request_task',
                          get_config('celery_module'))


def async_check(func):
    ''' decorator to route API-call request to celery depending on config '''
    def _decorated(*args, **kwargs):
        if SEND_ASYNC and CELERY_TASK:
            return CELERY_TASK.apply_async(args)
        return func(*args, **kwargs)
    return _decorated
Пример #37
0
def get_token():
    token_expiry = get_config('token_expiry')
    if token_expiry is None or token_expiry < datetime.datetime.now() \
            + datetime.timedelta(days=0, seconds=60):
        assert request_token()
    return get_config('token')
Пример #38
0
            # single module name
            return __import__(attr)
        m = __import__(modname, fromlist=[attr])
        return getattr(m, attr)

    def ret(mod, call):
        return do_import('{module}.{callable}'.format(module=mod,
                                                      callable=call))

    try:
        return ret(module, callable_name)
    except (ImportError, AttributeError):
        if fallback is None:
            return None
        return ret(fallback, callable_name)


SEND_ASYNC = get_config('send_async')
CELERY_TASK = import_path('submit_sms_mt_request_task',
                          get_config('celery_module'))


def async_check(func):
    ''' decorator to route API-call request to celery depending on config '''
    def _decorated(*args, **kwargs):
        if SEND_ASYNC and CELERY_TASK:
            return CELERY_TASK.apply_async(args)
        return func(*args, **kwargs)

    return _decorated