From 3e638fe202d3c9e3036e81755b5033cadb1675ec Mon Sep 17 00:00:00 2001 From: bashmak Date: Sat, 14 Apr 2018 16:26:08 +0300 Subject: [PATCH] Change sms management. Move it to views --- abonapp/views.py | 4 +- devapp/views.py | 2 +- dialing.py | 96 ++++++++++++++++++++++++++------------ dialing_app/urls.py | 3 +- dialing_app/views.py | 58 ++++++++++++++++++++++- djing/__init__.py | 9 ++++ djing/global_base_views.py | 4 ++ 7 files changed, 140 insertions(+), 36 deletions(-) diff --git a/abonapp/views.py b/abonapp/views.py index e89de0d..06dce8b 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -28,7 +28,7 @@ from statistics.models import getModel from group_app.models import Group from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.decorators import permission_required_or_403 as permission_required -from djing.global_base_views import OrderingMixin, BaseListWithFiltering, HashAuthView, AllowedSubnetMixin +from djing.global_base_views import OrderingMixin, BaseListWithFiltering, SecureApiView PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @@ -1121,7 +1121,7 @@ def search_abon(request): return results -class DhcpLever(AllowedSubnetMixin, HashAuthView): +class DhcpLever(SecureApiView): # # Api view for dhcp event # diff --git a/devapp/views.py b/devapp/views.py index 49204c3..f79a896 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -514,7 +514,7 @@ def fix_port_conflict(request, group_id, device_id, port_id): }) -class OnDeviceMonitoringEvent(global_base_views.AllowedSubnetMixin, global_base_views.HashAuthView): +class OnDeviceMonitoringEvent(global_base_views.SecureApiView): # # Api view for monitoring devices # diff --git a/dialing.py b/dialing.py index a6fabbd..ab636f7 100755 --- a/dialing.py +++ b/dialing.py @@ -1,26 +1,53 @@ #!/usr/bin/env python3 -import os, signal -from pid.decorator import pidfile -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") -django.setup() -from messaging.sms import SmsSubmit, SmsDeliver +from typing import Dict, Optional, AnyStr import re -import asterisk.manager +import signal from time import sleep -from dialing_app.models import SMSModel, SMSOut -from django.conf import settings +from urllib.parse import urlencode +from urllib.request import urlopen +from urllib.error import HTTPError +from hashlib import sha256 + +from pid.decorator import pidfile +from messaging.sms import SmsSubmit, SmsDeliver +from asterisk import manager as ast_mngr + -ASTERISK_MANAGER_AUTH = getattr(settings, 'ASTERISK_MANAGER_AUTH', { +ASTERISK_MANAGER_AUTH = { 'username': 'admin', - 'password': 'admin', + 'password': 'password', 'host': '127.0.0.1' -}) +} + +API_AUTH_SECRET = 'your api secret' +SERVER_DOMAIN = 'http://localhost:8000' outbox_messages = False +def calc_hash(data): + if type(data) is str: + result_data = data.encode('utf-8') + else: + result_data = bytes(data) + return sha256(result_data).hexdigest() + + +def secure_request(data: Dict) -> Optional[AnyStr]: + vars_to_hash = [str(v) for v in data.values()] + vars_to_hash.sort() + vars_to_hash.append(API_AUTH_SECRET) + sign = calc_hash('_'.join(vars_to_hash)) + data.update({'sign': sign}) + try: + with urlopen("%s/dialing/api/sms?%s" % (SERVER_DOMAIN, urlencode(data))) as r: + return r.read() + except ConnectionRefusedError: + print('ERROR: connection refused') + except HTTPError as e: + print('ERROR:', e) + + class SMS(object): def __init__(self, text, who, dev): self.text = text @@ -45,7 +72,7 @@ class ChunkedMsg(object): self.sms = sms -class MyAstManager(asterisk.manager.Manager): +class MyAstManager(ast_mngr.Manager): sms_chunks = list() def new_chunked_sms(self, count, ref, sms): @@ -57,11 +84,14 @@ class MyAstManager(asterisk.manager.Manager): print('Inbox %s:' % sms.who, sms.text) if not isinstance(sms, SMS): raise TypeError - SMSModel.objects.create( - who=sms.who, - dev=sms.dev, - text=sms.text - ) + response = secure_request({ + 'who': sms.who, + 'dev': sms.dev, + 'text': sms.text, + 'cmd': 'save_sms' + }) + if response is not None: + print(response) def send_sms(self, dev, recipient, utext): if not validate_tel(recipient): @@ -88,13 +118,19 @@ class MyAstManager(asterisk.manager.Manager): self.new_chunked_sms(cnt, ref, sms) def send_from_outbox(self): - messages = SMSOut.objects.filter(status='nw') + messages = secure_request({'cmd': 'get_new'}) for msg in messages: if self.send_sms(dev='sim_8318999', recipient=msg.dst, utext=msg.text): - msg.status = 'st' + msg_status = 'st' else: - msg.status = 'fd' - msg.save(update_fields=['status']) + msg_status = 'fd' + result = secure_request({ + 'cmd': 'update_status', + 'mid': msg.pk, + 'status': msg_status + }) + if result is not None: + print(result) manager = MyAstManager() @@ -104,10 +140,10 @@ def validate_tel(tel, reg=re.compile(r'^\+7978\d{7}$')): return bool(re.match(reg, tel)) -def handle_shutdown(event, manager): - print("Recieved shutdown event") - manager.close() - # we could analize the event and reconnect here +def handle_shutdown(event, mngr): + print("Received shutdown event") + mngr.close() + # we could analyze the event and reconnect here def signal_handler(signum, frame): @@ -159,11 +195,11 @@ def main(): manager.send_from_outbox() sleep(5) - except asterisk.manager.ManagerSocketException as e: + except ast_mngr.ManagerSocketException as e: print("Error connecting to the manager: ", e) - except asterisk.manager.ManagerAuthException as e: + except ast_mngr.ManagerAuthException as e: print("Error logging in to the manager: ", e) - except asterisk.manager.ManagerException as e: + except ast_mngr.ManagerException as e: print("Error: ", e) finally: manager.logoff() diff --git a/dialing_app/urls.py b/dialing_app/urls.py index 3a1024f..e99d226 100644 --- a/dialing_app/urls.py +++ b/dialing_app/urls.py @@ -10,5 +10,6 @@ urlpatterns = [ url(r'^requests$', views.VoiceMailRequestsListView.as_view(), name='vmail_request'), url(r'^reports$', views.VoiceMailReportsListView.as_view(), name='vmail_report'), url(r'^sms/in$', views.InboxSMSListView.as_view(), name='inbox_sms'), - url(r'^sms/send$', views.send_sms, name='send_sms') + url(r'^sms/send$', views.send_sms, name='send_sms'), + url(r'^api/sms$', views.SmsManager.as_view()) ] diff --git a/dialing_app/views.py b/dialing_app/views.py index eb1c37a..cb369a6 100644 --- a/dialing_app/views.py +++ b/dialing_app/views.py @@ -11,10 +11,13 @@ from django.views.generic import ListView from guardian.decorators import permission_required_or_403 as permission_required from django.db.models import Q from django.conf import settings +from jsonview.decorators import json_view from abonapp.models import Abon -from mydefs import only_admins -from .models import AsteriskCDR, SMSModel +from djing.global_base_views import SecureApiView +from djing import JSONType +from mydefs import only_admins, safe_int +from .models import AsteriskCDR, SMSModel, SMSOut from .forms import SMSOutForm @@ -151,3 +154,54 @@ def send_sms(request): 'form': frm, 'path': path }, request=request) + + +class SmsManager(SecureApiView): + # + # Api view for management sms from dongle + # + http_method_names = ['get'] + + @staticmethod + def bad_cmd(**kwargs) -> JSONType: + return {'text': 'Command is not allowed'} + + @method_decorator(json_view) + def get(self, request, *args, **kwargs): + cmd = request.GET.get('cmd') + data = request.GET.dict() + handler = getattr(self, cmd.lower(), self.bad_cmd) + del data['cmd'] + del data['sign'] + return handler(**data) + + @staticmethod + def save_sms(**kwargs) -> JSONType: + sms = SMSModel.objects.create( + who=kwargs.get('who'), + dev=kwargs.get('dev'), + text=kwargs.get('text') + ) + return {'status': 'ok', 'sms_id': sms.pk} + + @staticmethod + def update_status(**kwargs) -> JSONType: + msg_id = safe_int(kwargs.get('mid')) + if msg_id != 0: + status = kwargs.get('status') + update_count = SMSOut.objects.filter(pk=msg_id).update(status=status) + return { + 'text': 'Status updated', + 'update_count': update_count + } + return {'text': 'Bad mid parameter'} + + @staticmethod + def get_new(**kwargs) -> JSONType: + msgs = SMSOut.objects.filter(status='nw').defer('status') + res = [{ + 'when': round(m.timestamp), + 'dst': m.dst, + 'text': m.text + } for m in msgs] + return res diff --git a/djing/__init__.py b/djing/__init__.py index aa661cd..68af668 100644 --- a/djing/__init__.py +++ b/djing/__init__.py @@ -1,6 +1,7 @@ import os import re import importlib +import typing as t from django.shortcuts import _get_queryset from netaddr import mac_unix, mac_eui48 @@ -67,3 +68,11 @@ def get_object_or_None(klass, *args, **kwargs): ) except queryset.model.DoesNotExist: return + + +# Type for all objects who can convertable to json +_JSONType_0 = t.Union[str, int, float, bool, None, t.Dict[str, t.Any], t.List[t.Any]] +_JSONType_1 = t.Union[str, int, float, bool, None, t.Dict[str, _JSONType_0], t.List[_JSONType_0]] +_JSONType_2 = t.Union[str, int, float, bool, None, t.Dict[str, _JSONType_1], t.List[_JSONType_1]] +_JSONType_3 = t.Union[str, int, float, bool, None, t.Dict[str, _JSONType_2], t.List[_JSONType_2]] +JSONType = t.Union[str, int, float, bool, None, t.Dict[str, _JSONType_3], t.List[_JSONType_3]] diff --git a/djing/global_base_views.py b/djing/global_base_views.py index 1383f3f..d639cdb 100644 --- a/djing/global_base_views.py +++ b/djing/global_base_views.py @@ -92,6 +92,10 @@ class AllowedSubnetMixin(object): return HttpResponseForbidden('Access Denied') +class SecureApiView(AllowedSubnetMixin, HashAuthView): + pass + + class OrderingMixin(object): """ Ordering result object list by @order_by variable in get request.