def get_metrics_base_data(json_data):
    """
    Function to parse the base metric data from JSON into a new dict.

    Args:
        json_data (dict): JSON dict containing the data from the app.

    Returns:
        dict: Dict in the format we can store in Redis.
    """
    metrics_dict = {
        APP_VERSION_KEY: json_data.get(APP_VERSION_KEY),
        CONNECTION_TYPE_KEY: json_data.get(CONNECTION_TYPE_KEY),
        DIRECTION_KEY: json_data.get(DIRECTION_KEY),
        NETWORK_KEY: json_data.get(NETWORK_KEY),
        OS_KEY: json_data.get(OS_KEY),
        OS_VERSION_KEY: json_data.get(OS_VERSION_KEY),
    }

    if json_data.get(NETWORK_KEY, '').lower() != 'wifi':
        metrics_dict[NETWORK_OPERATOR_KEY] = json_data.get(NETWORK_OPERATOR_KEY)

    return metrics_dict class RedisClusterCache(object):
    """
    Class used for accessing the redis cluster used for caching.
    """
    def __init__(self):
        self.client = self._create_client()

    def _create_client(self):
        """
        Function to connect to the redis cluster and init the client.
        """
        server_list = settings.REDIS_SERVER_LIST.replace(' ', '').split(',')

        nodes = []
        for server in server_list:
            if ':' not in server:
                continue
            host, port = server.split(':')
            nodes.append({'host': host, 'port': port})

        return StrictRedisCluster(startup_nodes=nodes, decode_responses=True)

    def get(self, key):
        return self.client.get(key)

    def exists(self, key):
        return self.client.exists(key)

    def set(self, key, value, timeout=DEFAULT_TIMEOUT):
        self.client.set(key, value, timeout) class App(models.Model):
    """
    Model that contains information about the supported apps by the middleware.
    """
    platform = models.CharField(choices=PLATFORM_CHOICES, max_length=10)
    app_id = models.CharField(max_length=255)

    push_key = models.CharField(max_length=255)
    logentries_token = models.CharField(max_length=255, blank=False, null=False, default='')
    partner_logentries_token = models.CharField(max_length=255, blank=True, null=True, default='')

    def __str__(self):
        return '{0} for {1}'.format(self.app_id, self.platform)

    class Meta:
        unique_together = ('app_id', 'platform') class Device(models.Model):
    """
    Model for all device who register at the middleware.
    """
    # FIXME: We need this to be backwards compatible for one release.
    id = models.CharField(max_length=255, unique=True)

    name = models.CharField(max_length=255, blank=True, null=True)
    sip_user_id = models.CharField(max_length=255, unique=True, primary_key=True)
    os_version = models.CharField(max_length=255, blank=True, null=True)
    client_version = models.CharField(max_length=255, blank=True, null=True)
    token = models.CharField(max_length=250)
    sandbox = models.BooleanField(default=False)
    last_seen = models.DateTimeField(blank=True, null=True)
    app = models.ForeignKey(App)
    remote_logging_id = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return '{0} - {1}'.format(self.sip_user_id, self.name)

    # FIXME: We need this to be backwards compatible for one release.
    def save(self, *args, **kwargs):
        """
        Make sure the id fields stays the same as sip_user_id so we are
        backwards compatible.
        """
        if self.sip_user_id:
            self.id = self.sip_user_id
            obj = super(Device, self).save(*args, **kwargs)
            return obj @threaded
def log_to_db(platform, roundtrip_time, available):
    """
    Log the info in a seperate thread to the DB to make sure the log write
    does not block the api requests.
    """
    ResponseLog.objects.create(
        platform=platform,
        roundtrip_time=roundtrip_time,
        available=available,
    ) @threaded
def task_incoming_call_notify(device, unique_key, phonenumber, caller_id, attempt):
    """
    Threaded task to send a call push notification.
    """
    send_call_message(device, unique_key, phonenumber, caller_id, attempt) @threaded
def task_notify_old_token(device, app):
    """
    Threaded task to send a text push notification.
    """
    msg = 'A other device has registered for the same account. You won\'t be reachable on this device'
    send_text_message(device, app, msg) LOG_CALL_FROM = 'call_from' LOG_CALLER_ID = 'caller_id' def log_data_to_metrics_log(log_data, sip_user_id):
    """
    Function that handles logging data to the metrics log.

    Args:
        log_data (dict): The dict that is going to be logged.
        sip_user_id (str): The SIP user id linked to the log statement.
    """
    metrics_logger = logging.getLogger('metrics')
    metrics_logger.info('SIP_USER_ID: {} DATA: {}'.format(sip_user_id, log_data)) def log_middleware_information(log_statement, dict_with_variables, log_level, device=None):
    """
    Function that handles the logging for the middleware.

    Args:
        log_statement (str): The message to log.
        dict_with_variables (OrderedDict): OrderedDict that contains the
            variables we want to insert into the logging statement.
        log_level (int): The level on which to log.
        device (Device): The device for which we want to log to Logentries.
    """
    remote_logging_id = device.remote_logging_id if device and device.remote_logging_id else 'No remote logging ID'
    django_log_statement = fill_log_statement(log_statement, dict_with_variables)
    django_logger.log(log_level, '{0} - middleware - {1}'.format(remote_logging_id, django_log_statement))

    if device and device.remote_logging_id:
        log_statement = fill_log_statement(log_statement, dict_with_variables, anonymize=True)
        logentries_token = device.app.logentries_token
        log_to_logentries(log_statement, log_level, logentries_token, device, remote_logging_id)
        if device.app.partner_logentries_token:
            # Log to the Logentries environment of the partner with a different token.
            logentries_token = device.app.partner_logentries_token
            log_to_logentries(log_statement, log_level, logentries_token, device, remote_logging_id) LOG_SIP_USER_ID = 'sip_user_id' ACTION_KEY = 'action' CALL_SETUP_SUCCESSFUL_KEY = 'call_setup_successful' CODEC_KEY = 'codec' DIRECTION_KEY = 'direction' FAILED_REASON_KEY = 'failed_reason' HANGUP_REASON_KEY = 'hangup_reason' MOS_KEY = 'mos' OS_KEY = 'os' VIALER_CALL_FAILURE_TOTAL_KEY = 'vialer_call_failure_total' VIALER_CALL_SUCCESS_TOTAL_KEY = 'vialer_call_success_total' VIALER_HANGUP_REASON_TOTAL_KEY = 'vialer_hangup_reason_total' VIALER_MIDDLEWARE_INCOMING_CALL_SUCCESS_TOTAL_KEY = 'vialer_middleware_incoming_call_success_total' VIALER_MIDDLEWARE_INCOMING_CALL_FAILED_TOTAL_KEY = 'vialer_middleware_incoming_call_failed_total' VIALER_MIDDLEWARE_INCOMING_VALUE = 'Incoming' VIALER_MIDDLEWARE_PUSH_NOTIFICATION_FAILED_TOTAL_KEY = 'vialer_middleware_push_notification_failed_total' VIALER_MIDDLEWARE_PUSH_NOTIFICATION_SUCCESS_TOTAL_KEY = 'vialer_middleware_push_notification_success_total' class VoipgridAuthentication(BaseAuthentication):
    """
    Custom authentication.
    """
    def _check_status_code(self, status_code):
        """
        Function for checking the status code.

        Args:
            status_code(int): That status code of a response.
        """
        if status_code == 200:
            return
        elif status_code == 401:
            raise AuthenticationFailed(detail=None)
        elif status_code == 403:
            raise PermissionDenied(detail=None)
        else:
            # Temporarily unavailable.
            log_middleware_information(
                'Unsupported VG response code {0}',
                OrderedDict([
                    ('status_code', status_code),
                ]),
                logging.WARNING,
            )
            raise UnavailableException(detail=None)

    def authenticate(self, request):
        """
        Function for authentication against VoIPGRID api.
        """
        if settings.TESTING:
            return (AnonymousUser, None)

        # Get auth headers.
        auth = get_authorization_header(request)

        if not auth:
            # Raises 'Authentication credentials were not provided'.
            raise NotAuthenticated(detail=None)

        # Serialize data to check for sip_user_id.
        serializer = SipUserIdSerializer(data=request.data)
        if not serializer.is_valid(raise_exception=False):
            log_middleware_information(
                'BAD REQUEST! Authentication failed due to invalid sip_user_id in data:\n\n{0}',
                OrderedDict([
                    ('data', request.data),
                ]),
                logging.INFO,
            )
            # This raises a bad request response.
            raise ParseError(detail=None)

        # Get sip_user_id.
        sip_user_id = serializer.validated_data['sip_user_id']

        # Created new headers with old auth data.
        headers = {'Authorization': auth}

        # Get user profile.
        response = requests.get(settings.VG_API_USER_URL, headers=headers)
        # Check status code.
        self._check_status_code(response.status_code)

        # Parse to json.
        json_response = response.json()

        # Get app account reference on systemuser.
        app_account_url = json_response['app_account']

        if not app_account_url:
            # Has no app account and thus no access to api.
            log_middleware_information(
                'No app account for systemuser {0} - {1}',
                OrderedDict([
                    ('id', json_response['id']),
                    (LOG_EMAIL, json_response['email']),
                ]),
                logging.INFO,
                device=Device.objects.get(sip_user_id=sip_user_id),
            )
            raise PermissionDenied(detail=None)

        # Get url for app account.
        app_account_api_url = settings.VG_API_BASE_URL + app_account_url

        # Get app account.
        response = requests.get(app_account_api_url, headers=headers)
        # Check status code.
        self._check_status_code(response.status_code)
        # Get account id.
        account_id = response.json()['account_id']

        # Compare account id to sip user id the request is meant for.
        if str(sip_user_id) != str(account_id):
            # Raise permissions denied.
            raise PermissionDenied(detail=None)

        # All good.
        return (AnonymousUser, None)

    def authenticate_header(self, request):
        return 'Basic' class PlainTextRenderer(renderers.BaseRenderer):
    """
    Custom renderer to return a plain text response for asterisk.
    """
    media_type = 'text/plain'
    format = 'txt'

    def render(self, data, media_type=None, renderer_context=None):
        return data.encode(self.charset) class CallResponseSerializer(serializers.Serializer):
    """
    Serializer for the call response view.
    """
    unique_key = serializers.CharField(max_length=255)
    message_start_time = serializers.FloatField()
    available = serializers.BooleanField(default=True) class DeleteDeviceSerializer(TokenSerializer, SipUserIdSerializer):
    """
    Serializer for the device view delete.
    """
    app = serializers.CharField(max_length=255) class DeviceSerializer(TokenSerializer, SipUserIdSerializer):
    """
    Serializer for the device view post.
    """
    name = serializers.CharField(max_length=255, allow_blank=True, required=False)
    os_version = serializers.CharField(max_length=255, allow_blank=True, required=False)
    client_version = serializers.CharField(max_length=255, allow_blank=True, required=False)
    app = serializers.CharField(max_length=255)
    sandbox = serializers.BooleanField(default=False)
    remote_logging_id = serializers.CharField(max_length=255, allow_blank=True, required=False) class HangupReasonSerializer(SipUserIdSerializer):
    unique_key = serializers.CharField(max_length=255)
    reason = serializers.CharField(max_length=None, default=None, allow_blank=True) class IncomingCallSerializer(SipUserIdSerializer):
    """
    Serializer for the incoming call view.
    """
    caller_id = serializers.CharField(max_length=255, default='', allow_blank=True)
    phonenumber = serializers.CharField(max_length=32, validators=[phone_number_validator])
    call_id = serializers.CharField(max_length=255, default=None, allow_blank=True) class SipUserIdSerializer(serializers.Serializer):
    """
    Base serializer for the sip_user_id field.
    """
    sip_user_id = serializers.IntegerField(max_value=999999999, min_value=int(1e8))
from collections import OrderedDict
from django.conf import settings
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import views
from rest_framework.exceptions import ParseError
from rest_framework.response import Response
from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED,
                                   HTTP_202_ACCEPTED, HTTP_404_NOT_FOUND)
from api.utils import get_metrics_base_data
from app.cache import RedisClusterCache
from app.models import App, Device
from app.tasks import log_to_db, task_incoming_call_notify, task_notify_old_token
from app.utils import (
    LOG_CALL_FROM,
    LOG_CALLER_ID,
    log_data_to_metrics_log,
    log_middleware_information,
    LOG_SIP_USER_ID)
from main.prometheus.consts import (
    ACTION_KEY,
    CALL_SETUP_SUCCESSFUL_KEY,
    CODEC_KEY,
    DIRECTION_KEY,
    FAILED_REASON_KEY,
    HANGUP_REASON_KEY,
    MOS_KEY,
    OS_KEY,
    VIALER_CALL_FAILURE_TOTAL_KEY,
    VIALER_CALL_SUCCESS_TOTAL_KEY,
    VIALER_HANGUP_REASON_TOTAL_KEY,
    VIALER_MIDDLEWARE_INCOMING_CALL_SUCCESS_TOTAL_KEY,
    VIALER_MIDDLEWARE_INCOMING_CALL_FAILED_TOTAL_KEY,
    VIALER_MIDDLEWARE_INCOMING_VALUE,
    VIALER_MIDDLEWARE_PUSH_NOTIFICATION_FAILED_TOTAL_KEY,
    VIALER_MIDDLEWARE_PUSH_NOTIFICATION_SUCCESS_TOTAL_KEY)
from .authentication import VoipgridAuthentication
from .renderers import PlainTextRenderer
from .serializers import (
    CallResponseSerializer,
    DeleteDeviceSerializer,
    DeviceSerializer,
    HangupReasonSerializer,
    IncomingCallSerializer,
    SipUserIdSerializer)
import datetime
import logging
import random
import time
            '{0} {1} device:{2} registered for SIP_USER_ID: {3}. Status: {4}.',
            OrderedDict([
                ('app_id', app_id),
                ('platform', device.app.platform.upper()),
                ('device_token', device.token),
                (LOG_SIP_USER_ID, sip_user_id),
                ('status', status),
            ]),
            logging.INFO,
            device=device,
        )
        return Response('', status=status_code)

    def delete(self, request, platform):
        """
        Function for deleting a Device.
        """
        serialized_data = self._serialize_request(request, serializer_class=DeleteDeviceSerializer)

        token = serialized_data['token']
        sip_user_id = serialized_data['sip_user_id']
        app_id = serialized_data['app']

        try:
            device = get_object_or_404(
                Device,
                sip_user_id=sip_user_id,
                token=token,
                app__app_id=app_id,
                app__platform=platform,
            )
            device.delete()
        except Http404:
            log_middleware_information(
                'Could not unregister device {0} for SIP_USER_ID {1}',
                OrderedDict([
                    ('token', token),
                    (LOG_SIP_USER_ID, sip_user_id),
                ]),
                logging.WARNING,
            )
            raise
        except Exception:
            log_middleware_information(
                'EXCEPTION WHILE UNREGISTERING DEVICE {0} FOR SIP_USER_ID : {1}',
                OrderedDict([
                    ('token', token),
                    (LOG_SIP_USER_ID, sip_user_id),
                ]),
                logging.CRITICAL,
            )
            raise
        log_middleware_information(
            'Unregistered device {0} for SIP_USER_ID {1}',
            OrderedDict([
                ('token', token),
                (LOG_SIP_USER_ID, sip_user_id),
            ]),
            logging.INFO,
            device=device,
        )
        return Response('', status=HTTP_200_OK)


class HangupReasonView(VialerAPIView):
    """
    View to log a reason why a device did not answer a call.
    """
    serializer_class = HangupReasonSerializer
    authentication_classes = (VoipgridAuthentication, )

    def post(self, request):
        """
        Function to log the reason for a device.
        """
        serialized_data = self._serialize_request(request)

        reason = serialized_data['reason']
        unique_key = serialized_data['unique_key']
        sip_user_id = serialized_data['sip_user_id']

        try:
            # Check if there is a registered device for given sip_user_id.
            device = get_object_or_404(Device, sip_user_id=sip_user_id)
        except Http404:
            log_middleware_information(
                '{0} | Failed to find a device for SIP_user_ID : {1}',
                OrderedDict([
                    ('unique_key', unique_key),
                    (LOG_SIP_USER_ID, sip_user_id),
                ]),
                logging.WARNING,
            )
            raise

        log_middleware_information(
            '{0} | {1} Device not available because: {2} on {3}',
            OrderedDict([
                ('unique_key', unique_key),
                ('platform', device.app.platform.upper()),
                ('reason', reason),
                ('timestamp', datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S.%f')),
            ]),
            logging.INFO,
            device=device,
        )
        return Response(status=HTTP_200_OK)


class LogMetricsView(VialerAPIView):
    serializer_class = None
    authentication_classes = (VoipgridAuthentication,)

    def post(self, request):
        redis_cache = RedisClusterCache()
        json_data = request.data
        metric_data = get_metrics_base_data(json_data)

        if HANGUP_REASON_KEY in json_data:
            metric_data[HANGUP_REASON_KEY] = json_data.get(HANGUP_REASON_KEY)

            redis_cache.client.rpush(VIALER_HANGUP_REASON_TOTAL_KEY, metric_data)