Exemple #1
0
 def post(self, request, *args, **kwargs):
     try:
         college_serializer = ApiCollegeSerializer(data=request.data)
         if college_serializer.is_valid():
             college_serializer.save()
             return ApiResponse(college_serializer.data, status=status.HTTP_200_OK)
         else:
             return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #2
0
 def post(self, request, *args, **kwargs):
     try:
         student = ApiStudentDetailsSerializer(
             data=request.data, context={'college_id': kwargs.get('pk')})
         if student.is_valid():
             student.save()
             return ApiResponse(student.data,
                                status=status.HTTP_201_CREATED)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #3
0
 def put(self, request, *args, **kwargs):
     try:
         college = College.objects.get(id=kwargs.get('pk'))
         college_serializer = ApiCollegeSerializer(data=request.data, instance=college)
         if college_serializer.is_valid():
             college_serializer.save()
             return ApiResponse(college_serializer.data, status=status.HTTP_200_OK)
         else:
             return ApiResponse(college_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #4
0
 def get(self, request, *args, **kwargs):
     try:
         if kwargs:
             college = College.objects.get(id=kwargs.get('pk'))
             college_serializer = ApiCollegeSerializer(college)
             return ApiResponse(college_serializer.data, status=status.HTTP_200_OK)
         else:
             colleges = College.objects.all()
             college_serializer = ApiCollegeSerializer(colleges, many=True)
             return ApiResponse(college_serializer.data, status=status.HTTP_200_OK)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #5
0
 def get(self, request, *args, **kwargs):
     try:
         college = College.objects.get(id=kwargs.get('pk'))
         if not kwargs.get('sk'):
             student = Student.objects.filter(college_id=college.id).all()
             serializer = ApiStudentSerializer(student, many=True)
         else:
             # student = Student.objects.filter(college_id=college.id).get(id=kwargs.get('sk'))
             student = get_object_or_404(Student, id=kwargs.get('sk'))
             serializer = ApiStudentSerializer(student)
         return ApiResponse(serializer.data)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #6
0
    def delete(self, request, jobid):
        """
        Delete a capture job belonging to the authenticated user.

        Given:
        >>> capture_job_data, client, mock_delete_capture = [getfixture(f) for f in ['capture_job_data', 'client', 'mock_delete_capture']]
        >>> url = reverse('delete_capture', args=[capture_job_data['jobid']])
        >>> user = User.objects.get(id=capture_job_data['userid'])

        We call out to the capture service using the authenticated user's ID.
        >>> response = client.delete(url, as_user=user)
        >>> check_response(response, status_code=204)
        >>> assert mock_delete_capture.call_args[1]['params'].get('userid') == user.id

        Capture job IDs must be valid UUIDs. This application validates and returns
        404 if passed an invalid job ID; if we pass it on to the capture service, we
        should expect a 400.

        If the job doesn't exist, or has already been deleted, we should expect a 404.

        If the job doesn't belong to the user, we should expect a 403.

        (If we wish to delete a job with admin-level privileges, we should omit the
        userid param from our API call.)
        """
        logger.info(f"Deleting job {jobid} for user {request.user.id}")
        response, data = query_capture_service(
            method='delete',
            path=f"/capture/{jobid}",
            params={'userid': request.user.id},
            valid_if=lambda code, data: code in [204, 403, 404]
        )
        return ApiResponse(data or None, status=response.status_code)
Exemple #7
0
    def get(self, request):
        """
        List the authenticated user's webhook subscriptions.

        Given:
        >>> client, webhook_subscription = [getfixture(f) for f in ['client', 'webhook_subscription']]

        Simple get:
        >>> response = client.get(reverse('webhooks'), as_user=webhook_subscription.user)
        >>> check_response(response)

        Sample response:
        [{
            "id": 1,
            "created_at": "2020-09-25T20:41:15.774373Z",
            "updated_at": "2020-09-25T20:41:15.774414Z",
            "event_type": "ARCHIVE_CREATED",
            "callback_url": "https://webhookservice.com?hookid=1234",
            "signing_key": "128-byte-key",
            "signing_key_algorithm": "sha256",
            "user": 1
        }]
        >>> [subscription] = response.data
        >>> assert subscription['id'] == webhook_subscription.id
        >>> assert subscription['user'] == webhook_subscription.user.id
        >>> assert subscription['event_type'] == webhook_subscription.event_type == WebhookSubscription.EventType.ARCHIVE_CREATED
        >>> assert subscription['callback_url'] == webhook_subscription.callback_url
        >>> for key in ['created_at', 'updated_at', 'signing_key', 'signing_key_algorithm']:
        ...     assert subscription[key]
        """
        items = WebhookSubscription.objects.filter(user=request.user)
        return ApiResponse(
            WebhookSubscriptionSerializer(items, many=True).data)
Exemple #8
0
 def list(self, request):
     queryset = self.get_queryset()
     questions = QuestionSerializer(queryset, many=True).data
     grouped_questions = defaultdict(list)
     for q in questions:
         group = q['group']
         grouped_questions[group].append(q)
     return ApiResponse(grouped_questions)
Exemple #9
0
 def put(self, request, *args, **kwargs):
     try:
         student = get_object_or_404(Student, id=kwargs.get('sk'))
         serializer = ApiStudentDetailsSerializer(student,
                                                  data=request.data,
                                                  context={
                                                      'college_id':
                                                      kwargs.get('pk'),
                                                      'sk':
                                                      kwargs.get('sk')
                                                  })
         if serializer.is_valid():
             serializer.save()
             return ApiResponse(serializer.data)
         return ApiResponse(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
     except Exception:
         return ApiResponse(status=status.HTTP_400_BAD_REQUEST)
Exemple #10
0
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        question = QuestionSerializer(instance).data

        # ---- gather possible Demographies for this question ---- #
        demogs_with_years = AvailableDemographyByQuestion.objects.filter(
            question=instance).values('demography__code', 'year')

        # all possible demographies for this particular question
        unq_demog_codes = set(
            [d['demography__code'] for d in demogs_with_years])
        question['demographies'] = list(unq_demog_codes)

        # dictionary of demog codes by year for this question
        demogs_grouped_by_year = defaultdict(list)
        for d in demogs_with_years:
            demogs_grouped_by_year[d['year']].append(d['demography__code'])

        # ---- gather Responses ---- #
        # construct a DRY query
        responsesQuery = ResponseModel.objects.filter(question_id=instance.pk)

        # check if legal demography filter requested
        demog = request.query_params.get('demog', None)
        if demog and demog in unq_demog_codes:
            question['demog'] = demog
            responsesQuery = responsesQuery.annotate(
                demog=RawSQL("demographics -> %s", (demog, ))).values(
                    'demog', 'value', 'year')
        else:
            question['demog'] = 'any'
            responsesQuery = responsesQuery.values('value', 'year')

        responses = responsesQuery.annotate(Count('value'))
        grouped_responses = defaultdict(list)
        for r in responses:
            if r.get('demog', True):
                grouped_responses[r['year']].append({
                    'count': r['value__count'],
                    'value': str(r['value']),  # strings to accomodate JSON
                    'demog': r.get('demog', 0)  # 0 indicates 'any' demography 
                })

        # -- Hash by year the response values and the possible demography variables for that year -- #
        question['responses'] = {}
        for year in grouped_responses.keys():
            question['responses'][year] = {
                'demographies': demogs_grouped_by_year[year],
                'values': sorted(grouped_responses[year],
                                 key=lambda k: k['value'])
            }

        return ApiResponse(question)
Exemple #11
0
def reset_token(request, format=None):
    """
    Get a new API token.

    Given:
    >>> client, user = [getfixture(i) for i in ['client', 'user']]
    >>> original_token = user.auth_token.key

    >>> response = client.post(reverse('token_reset'), as_user=user)
    >>> user.refresh_from_db()
    >>> check_response(response)
    >>> assert original_token != user.auth_token.key
    >>> assert response.data['token'] == user.auth_token.key
    """
    token = request.user.get_new_token()
    return ApiResponse({'token': token.key})
Exemple #12
0
    def delete(self, request, pk):
        """
        Unsubscribe from a webhook.

        Given:
        >>> client, webhook_subscription = [getfixture(f) for f in ['client', 'webhook_subscription']]
        >>> user = webhook_subscription.user
        >>> assert user.webhook_subscriptions.count() == 1

        Simple delete:
        >>> response = client.delete(reverse('webhook', args=[webhook_subscription.id]), as_user=user)
        >>> check_response(response, status_code=204)
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 0
        """
        target = self.get_subscription_for_user(request.user, pk)
        target.delete()
        return ApiResponse(status=status.HTTP_204_NO_CONTENT)
Exemple #13
0
    def get(self, request):
        """
        List capture jobs for the authenticated user.

        Given:
        >>> user, client, mock_list_captures, django_settings = [getfixture(f) for f in ['user', 'client', 'mock_list_captures', 'settings']]
        >>> url = reverse('captures')

        We call out to the capture service using the authenticated user's ID.
        >>> response = client.get(url, as_user=user)
        >>> check_response(response)
        >>> assert mock_list_captures.call_args[1]['params'].get('userid') == user.id

        If this application is configured to override and correct the netloc of the WACZ files (see settings_base.py),
        it is corrected before the response is returned to the user.
        >>> django_settings.OVERRIDE_ACCESS_URL_NETLOC = {'internal': 'host.docker.internal:9000', 'external': 'localhost:9000'}
        >>> overridden_response = client.get(url, as_user=user)
        >>> check_response(overridden_response)
        >>> for job in overridden_response.data['jobs']:
        ...     if job['status'] == 'Complete':
        ...         assert re.compile(f"https?://{django_settings.OVERRIDE_ACCESS_URL_NETLOC['external']}").match(job['access_url'])
        ...     else:
        ...         assert job['access_url'] is None
        >>> for job in response.data['jobs']:
        ...     if job['status'] == 'Complete':
        ...         assert not re.compile(f"https?://{django_settings.OVERRIDE_ACCESS_URL_NETLOC['external']}").match(job['access_url'])
        ...     else:
        ...         assert job['access_url'] is None
        """
        response, data = query_capture_service(
            method='get',
            path='/captures',
            params={'userid': request.user.id},
            valid_if=lambda code, data: code == 200 and 'jobs' in data
        )
        if settings.OVERRIDE_ACCESS_URL_NETLOC:
            for job in data['jobs']:
                if job['access_url']:
                    job['access_url'] = override_access_url_netloc(job['access_url'])
        return ApiResponse(data)
Exemple #14
0
    def get(self, request, pk):
        """
        Retrieve details of a webhook subscription.

        Given:
        >>> client, webhook_subscription = [getfixture(f) for f in ['client', 'webhook_subscription']]

        Simple get:
        >>> response = client.get(reverse('webhook', args=[webhook_subscription.id]), as_user=webhook_subscription.user)
        >>> check_response(response)

        Sample response:
        {'id': 1, 'created_at': '2020-09-24T19:16:36.238012Z', 'updated_at': '2020-09-24T19:16:36.238026Z', 'event_type': 'ARCHIVE_CREATED', 'callback_url': 'https://webhookservice.com?hookid=1234', 'user': 1}
        >>> subscription = response.data
        >>> assert subscription['id'] == webhook_subscription.id
        >>> assert subscription['user'] == webhook_subscription.user.id
        >>> assert subscription['event_type'] == webhook_subscription.event_type == WebhookSubscription.EventType.ARCHIVE_CREATED
        >>> assert subscription['callback_url'] == webhook_subscription.callback_url
        >>> for key in ['created_at', 'updated_at']:
        ...     assert subscription[key]
        """
        target = self.get_subscription_for_user(request.user, pk)
        serializer = WebhookSubscriptionSerializer(target)
        return ApiResponse(serializer.data)
Exemple #15
0
    def post(self, request):
        """
        Subscribe to a webhook.

        Given:
        >>> client, user = [getfixture(f) for f in ['client', 'user']]
        >>> assert user.webhook_subscriptions.count() == 0
        >>> url = reverse('webhooks')
        >>> data = {'callback_url': 'https://webhookservice.com?hookid=1234', 'event_type': 'ARCHIVE_CREATED'}

        Post the required data as JSON to subscribe:
        >>> response = client.post(url, data, content_type="application/json",  as_user=user)
        >>> check_response(response, status_code=201)
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 1

        Sample response:
        {
            "id": 1,
            "created_at": "2020-09-25T20:41:15.774373Z",
            "updated_at": "2020-09-25T20:41:15.774414Z",
            "event_type": "ARCHIVE_CREATED",
            "callback_url": "https://webhookservice.com?hookid=1234",
            "signing_key": "128-byte-key",
            "signing_key_algorithm": "sha256",
            "user": 1
        }
        >>> assert response.data['callback_url'] == data['callback_url']
        >>> assert response.data['event_type'] == data['event_type']
        >>> for key in ['created_at', 'updated_at','signing_key', 'signing_key_algorithm']:
        ...     assert key in response.data

        You can subscribe to the same event an arbitrary number of times, even with the same callback URL.
        >>> response = client.post(url, data, content_type="application/json",  as_user=user)
        >>> check_response(response, status_code=201)
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 2

        At present, the only available event type is 'ARCHIVE_CREATED':
        >>> for invalid_event in ['archive_created', 'UNSUPPORTED_EVENT']:
        ...     payload = {**data, **{'event_type': invalid_event}}
        ...     response = client.post(url, payload, content_type="application/json", as_user=user)
        ...     check_response(response, status_code=400, content_includes="not a valid choice")
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 2

        You may not specify `user`, `id`, `signing_key`, or `signing_key_algorithm`;
        they are populated automatically:
        >>> disallowed_keys = {'id': 1, 'user': 1000, 'signing_key': 'foo', 'signing_key_algorithm': 'bar'}
        >>> response = client.post(url, {**data, **disallowed_keys}, content_type="application/json",  as_user=user)
        >>> check_response(response, status_code=201)
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 3
        >>> assert response.data['id'] != disallowed_keys['id']
        >>> assert response.data['user'] == user.id != disallowed_keys['user']
        >>> assert response.data['signing_key'] != disallowed_keys['signing_key']
        >>> assert response.data['signing_key_algorithm'] != disallowed_keys['signing_key_algorithm']

        If you omit any required data, a subscription is not created:
        >>> for key in ['callback_url', 'event_type']:
        ...     payload = {k:v for k,v in data.items() if k != key}
        ...     check_response(client.post(url, payload, content_type="application/json", as_user=user), status_code=400)
        >>> user.refresh_from_db()
        >>> assert user.webhook_subscriptions.count() == 3
        """
        serializer = WebhookSubscriptionSerializer(
            data={
                'event_type': request.data.get('event_type'),
                'callback_url': request.data.get('callback_url')
            })
        if serializer.is_valid():
            serializer.save(user=request.user)
            return ApiResponse(serializer.data, status=status.HTTP_201_CREATED)
        return ApiResponse(serializer.errors,
                           status=status.HTTP_400_BAD_REQUEST)
Exemple #16
0
    def post(self, request):
        """
        Launch capture jobs for the authenticated user.

        Given:
        >>> user, webhook_subscription, client, mock_create_captures, django_settings = [getfixture(f) for f in ['user', 'webhook_subscription', 'client', 'mock_create_captures', 'settings']]
        >>> url = reverse('captures')

        POST a list of URLs to capture...
        >>> check_response(client.post(url, as_user=user), status_code=400, content_includes="'urls' is required")
        >>> check_response(client.post(url, {'urls': 'http://example.com'}, content_type='application/json', as_user=user), status_code=400, content_includes="must be a list")

        and...receive back the count of launched capture jobs ('urls') and a list of their IDs ('jobids').
        >>> response = client.post(url, {'urls': ['http://example.com']}, content_type='application/json', as_user=user)
        >>> check_response(response, status_code=201)
        >>> assert all(key in response.data for key in {'urls', 'jobids'})

        We request a callback when the capture is complete:
        >>> [hook] =  mock_create_captures.call_args[1]['json']['webhooks']
        >>> assert hook['signingKeyAlgorithm'] is None
        >>> assert not hook['signingKey']
        >>> assert hook['callbackUrl'] and hook['userDataField']
        >>> assert isinstance(hook['signingKey'], str) and isinstance(hook['callbackUrl'], str) and isinstance(hook['userDataField'], str)

        and...include callback info for any user webhook subscriptions...
        >>> response = client.post(url, {'urls': ['http://example.com']}, content_type='application/json', as_user=webhook_subscription.user)
        >>> assert len(mock_create_captures.call_args[1]['json']['webhooks']) == 2

        and... include the user_data_field as a string, if supplied.
        >>> response = client.post(url, {'urls': ['http://example.com'], 'user_data_field': 'slack_message=141414'}, content_type='application/json', as_user=webhook_subscription.user)
        >>> assert len(mock_create_captures.call_args[1]['json']['webhooks']) == 2
        >>> assert 'slack_message=141414' in mock_create_captures.call_args[1]['json']['webhooks'][1].values()
        >>> response = client.post(url, {'urls': ['http://example.com'], 'user_data_field': 141414}, content_type='application/json', as_user=webhook_subscription.user)
        >>> assert len(mock_create_captures.call_args[1]['json']['webhooks']) == 2
        >>> assert '141414' in mock_create_captures.call_args[1]['json']['webhooks'][1].values()

        The capture service accepts other parameters: 'tag' and 'embeds'. See our API docs for details.
        We ensure these optional values are cast to the expected types and pass them along, if they are supplied.
        >>> response = client.post(url, {'urls': ['http://example.com'], 'tag': 9, 'embeds': 'yes'}, content_type='application/json', as_user=user)
        >>> assert mock_create_captures.call_args[1]['json']['tag'] == '9'
        >>> assert mock_create_captures.call_args[1]['json']['embeds'] is True
        """

        try:
            data = {
                'userid': request.user.id,
                'urls': request.data['urls'],
                'tag': str(request.data.get('tag', '')),
                'embeds': bool(request.data.get('embeds')) or False
            }
        except KeyError:
            raise serializers.ValidationError("Key 'urls' is required.")
        if not isinstance(data['urls'], list):
            raise serializers.ValidationError("'urls' must be a list.")

        if settings.SEND_WEBHOOK_DATA_TO_CAPTURE_SERVICE:
            # our callback
            if settings.CALLBACK_PREFIX:
                url = f"{settings.CALLBACK_PREFIX}{reverse('archived_callback')}"
            else:
                url = request.build_absolute_uri(reverse('archived_callback'))
            data['webhooks'] = [{
                'callback_url': url,
                'signing_key': settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY,
                'signing_key_algorithm': settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM,
                'user_data_field': str(timezone.now().timestamp())
            }]
            # user callbacks
            webhook_subscriptions = WebhookSubscription.objects.filter(
                user=request.user,
                event_type=WebhookSubscription.EventType.ARCHIVE_CREATED
            )
            if webhook_subscriptions:
                for subscription in webhook_subscriptions:
                    data['webhooks'].append({
                        'callback_url': subscription.callback_url,
                        'signing_key': subscription.signing_key,
                        'signing_key_algorithm': subscription.signing_key_algorithm,
                        'user_data_field': str(request.data.get('user_data_field', ''))
                    })

        response, data = query_capture_service(
            method='post',
            path='/captures',
            json=data,
            valid_if=lambda code, data: code == 201 and all(key in data for key in {'urls', 'jobids'})
        )
        return ApiResponse(data, status=response.status_code)
Exemple #17
0
 def post(self, request):
     """
     Launch capture jobs for the authenticated user.
     """
     return ApiResponse(COMING_SOON, status=status.HTTP_410_GONE)
Exemple #18
0
def archived_callback(request, format=None):
    """
    Respond upon receiving a notification from the capture service that an archive is complete.

    Given:
    >>> client, callback_data, django_settings, _ = [getfixture(f) for f in ['client', 'webhook_callback', 'settings', 'mock_download']]
    >>> url = reverse('archived_callback')
    >>> user = User.objects.get(id=callback_data['userid'])
    >>> assert user.archives.count() == 0

    By default, we do not expect the data to be signed.
    >>> response = client.post(url, callback_data, content_type='application/json')
    >>> check_response(response)
    >>> user.refresh_from_db()
    >>> assert user.archives.count() == 1

    Signature verification can be enabled via Django settings.
    >>> django_settings.VERIFY_WEBHOOK_SIGNATURE = True
    >>> django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM = generate_hmac_signing_key()
    >>> response = client.post(url, callback_data, content_type='application/json')
    >>> check_response(response, status_code=400, content_includes='Invalid signature')
    >>> response = client.post(url, callback_data, content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE='foo'
    ... )
    >>> check_response(response, status_code=400, content_includes='Invalid signature')
    >>> response = client.post(url, callback_data, content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE=sign_data(humps.camelize(callback_data), django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM)
    ... )
    >>> check_response(response, content_includes='ok')
    >>> user.refresh_from_db()
    >>> assert user.archives.count() == 2

    Hashes are calculated if not supplied by the POSTed data.
    >>> assert all(key not in callback_data for key in ['hash', 'hash_algorithm'])
    >>> assert all(archive.hash and archive.hash_algorithm for archive in user.archives.all())

    If we send a timestamp with our initial request and receive it back, we store that value:
    >>> assert str(user.archives.last().requested_at.timestamp()) == callback_data['user_data_field']

    If we do not send a timestamp with our initial request, or if the webhook
    payload does not include it, we default to 00:00:00 UTC 1 January 1970.
    >>> del callback_data['user_data_field']
    >>> response = client.post(url, callback_data, content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE=sign_data(humps.camelize(callback_data), django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM)
    ... )
    >>> check_response(response)
    >>> assert user.archives.last().requested_at.timestamp() == 0.000000

    The POSTed `userid` must match the id of a registered user.
    >>> callback_data['userid'] = User.objects.last().id + 1
    >>> assert not User.objects.filter(id=callback_data['userid']).exists()
    >>> response = client.post(url, callback_data, content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE=sign_data(humps.camelize(callback_data), django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM)
    ... )
    >>> check_response(response, status_code=400, content_includes=['user', 'Invalid', 'does not exist'])

    Note: though jobid and hash should be unique, it is not enforced by this application
    (as is clear from the examples above).

    Finally: let's demonstrate that DRF is indeed handling camelcase conversion for us.
    >>> response_from_snake_case_post = client.post(url, callback_data, content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE=sign_data(humps.camelize(callback_data), django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM)
    ... )
    >>> response_from_camel_case_post = client.post(url, humps.camelize(callback_data), content_type='application/json',
    ...     HTTP_X_HOOK_SIGNATURE=sign_data(humps.camelize(callback_data), django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY, django_settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM)
    ... )
    >>> assert response_from_snake_case_post.data == response_from_camel_case_post.data
    >>> assert humps.camelize(response_from_snake_case_post.data) == response_from_snake_case_post.data
    """
    if settings.VERIFY_WEBHOOK_SIGNATURE:
        # DRF will have deserialized the request data and decamelized all the keys...
        # which messes up the signature check. We recamelize here, just for that check.
        camelcase_data = humps.camelize(request.data)
        if not is_valid_signature(
                request.headers.get('x-hook-signature', ''), camelcase_data,
                settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY,
                settings.CAPTURE_SERVICE_WEBHOOK_SIGNING_KEY_ALGORITHM):
            raise serializers.ValidationError('Invalid signature.')

    # for now, calculate file hash, if not included in POST
    # (hashing is not yet a feature of the capture service)
    hash = request.data.get('hash')
    hash_algorithm = request.data.get('hash_algorithm')
    if request.data.get('access_url') and (not hash or not hash_algorithm):
        if settings.OVERRIDE_ACCESS_URL_NETLOC:
            url = override_access_url_netloc(request.data['access_url'],
                                             internal=True)
        else:
            url = request.data['access_url']
        hash, hash_algorithm = get_file_hash(url)

    # retrieve the datetime from our user_data_field
    ts = float(request.data.get('user_data_field', '0.000000'))
    requested_at = datetime.datetime.fromtimestamp(ts, tz(settings.TIME_ZONE))

    # validate and save
    serializer = ArchiveSerializer(
        data={
            'user': request.data.get('userid'),
            'jobid': request.data.get('jobid'),
            'requested_at': requested_at,
            'hash': hash,
            'hash_algorithm': hash_algorithm
        })
    if serializer.is_valid():
        serializer.save()
        if not ts:
            logger.warning(
                f'No requested_at timestamp received for archive {serializer.instance.id}; defaulting to Unix Epoch.'
            )
        return ApiResponse({'status': 'ok'}, status=status.HTTP_200_OK)
    return ApiResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Exemple #19
0
def search(request):
    claims = Claim.objects.all()
    serializer = ClaimSerializer(claims, many=True)
    return ApiResponse({'results': serializer.data})
Exemple #20
0
 def delete(self, request, jobid):
     """
     Delete a capture job belonging to the authenticated user.
     """
     return ApiResponse(COMING_SOON, status=status.HTTP_410_GONE)