예제 #1
0
    def get_q(self, qualifier, value, invert, partial=''):
        self.check_qualifier(qualifier)

        # TODO: Try to make the splitting and cleaning more re-usable
        if qualifier in ('in', 'range'):
            values = value.split(',')
            if qualifier == 'range':
                if len(values) != 2:
                    raise BinderRequestError(
                        'Range requires exactly 2 values for {}.'.format(
                            self.field_description()))
        else:
            values = [value]

        if qualifier == 'isnull':
            cleaned_value = True
        elif qualifier in ('in', 'range'):
            cleaned_value = [self.clean_value(qualifier, v) for v in values]
        else:
            try:
                cleaned_value = self.clean_value(qualifier, values[0])
            except IndexError:
                raise ValidationError(
                    'Value for filter {{{}}}.{{{}}} may not be empty.'.format(
                        self.field.model.__name__, self.field.name))

        suffix = '__' + qualifier if qualifier else ''
        if invert:
            return ~Q(**{partial + self.field.name + suffix: cleaned_value})
        else:
            return Q(**{partial + self.field.name + suffix: cleaned_value})
예제 #2
0
    def change_password(self, request):
        """
		Change the password from an old password

		Request:

		POST user/change_password/
		{
			"old_password": str,
			"new_password": str
		}

		Response:
		Same as GET user/{id}/

		"""
        if request.method != 'PUT':
            raise BinderMethodNotAllowed()

        self._require_model_perm('change_own_password', request)

        decoded = request.body.decode()
        try:
            body = json.loads(decoded)
        except ValueError:
            raise BinderRequestError(
                _('Invalid request body: not a JSON document.'))

        user = request.user

        errors = {}
        for item in ['old_password', 'new_password']:
            if body.get(item) is None:
                errors[item] = ['missing']

        if not user.check_password(body.get('old_password')):
            errors['old_password'] = ['incorrect']

        if len(errors) != 0:
            raise BinderValidationError(errors)

        password = body.get('new_password')
        try:
            password_validation.validate_password(password, user)
        except ValidationError as ve:
            validation_errors = {'new_password': ve.messages}
            raise BinderValidationError(validation_errors)

        user.set_password(password)
        user.save()
        logger.info('password changed for {}/{}'.format(user.id, user))

        if user == request.user:
            """
			No need to change the password of an user that is not our own
			"""
            update_session_auth_hash(request, user)

        return self.respond_with_user(request, user.id)
예제 #3
0
def multi_request_view(request):
	if request.method == 'GET':
		allowed_methods = ['GET']
	elif request.method == 'POST':
		allowed_methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
	else:
		try:
			raise BinderMethodNotAllowed()
		except BinderException as e:
			e.log()
			return e.response(request)

	requests = jsonloads(request.body)
	responses = []
	key_responses = {}

	if not isinstance(requests, list):
		try:
			raise BinderRequestError('requests should be a list')
		except BinderException as e:
			e.log()
			return e.response(request)

	handler = RequestHandler()

	try:
		with transaction.atomic():
			for i, data in enumerate(requests):
				key = data.pop('key', i)

				# Get response from request
				try:
					req = parse_request(
						data, allowed_methods, key_responses, request,
					)
				except BinderException as e:
					e.log()
					res = e.response(request)
				else:
					res = handler.get_response(req)

				# Serialize and add to responses
				res_data = serialize_response(res)
				responses.append(res_data)

				# Add by key so that we can reference it in other requests
				key_responses[key] = res_data

				# Rollback the transaction if the request has a failing code
				if res.status_code >= 400:
					raise ErrorStatus(res.status_code)
	except ErrorStatus as e:
		status = e.status
	else:
		status = 200

	return JsonResponse(responses, safe=False, status=status)
def handle_exceptions_view(request):
    # We create a model so we can test transaction rollback on error
    Zoo.objects.create(name='Test zoo')

    try:
        res = request.GET['res']
    except KeyError:
        raise BinderRequestError('no res provided')
    else:
        return HttpResponse(res)
예제 #5
0
    def filter_deleted(self, queryset, pk, deleted, request=None):
        """
		Can be used to filter deleted users, or unfilter them.
		"""
        if pk or deleted == 'true':
            return queryset
        if deleted is None:
            return queryset.filter(is_active=True)
        if deleted == 'only':
            return queryset.filter(is_active=False)
        raise BinderRequestError(
            _('Invalid value: deleted=%s.') % request.GET.get('deleted'))
예제 #6
0
    def activate(self, request, pk=None):
        """
		Adds an endpoint to activate an user. Also logs in the user

		Request:

		PUT user/{id}/activate/
		{
			"activation_code": string
		}

		Response:

		Same as GET user/{id}/
		"""
        if request.method != 'PUT':
            raise BinderMethodNotAllowed()

        self._require_model_perm('activate', request)

        decoded = request.body.decode()
        try:
            body = json.loads(decoded)
        except ValueError:
            raise BinderRequestError(
                _('Invalid request body: not a JSON document.'))

        errors = {}
        for item in ['activation_code']:
            if body.get(item) is None:
                errors[item] = ['missing']
        if len(errors) != 0:
            raise BinderValidationError(errors)

        try:
            user = self.model._default_manager.get(pk=pk)
        except (TypeError, ValueError, OverflowError, self.model.DoesNotExist):
            user = None
        if user is None or not self.token_generator.check_token(
                user, body.get('activation_code')):
            raise BinderNotFound()

        logger.info('login for {}/{} via successful activation'.format(
            user.id, user))

        user.is_active = True
        user.save()
        self.auth_login(request, user)
        return self.respond_with_user(request, user.id)
예제 #7
0
    def clean_qualifier(self, qualifier, value):
        if qualifier == 'isnull':
            cleaned_value = value not in ['0', 'false', 'False']

        elif qualifier in ('in', 'range'):
            values = value.split(',')
            if qualifier == 'range' and len(values) != 2:
                raise BinderRequestError(
                    'Range requires exactly 2 values for {}.'.format(
                        self.field_description()))
            cleaned_value = [
                self.clean_value(qualifier, value) for value in values
            ]

        else:
            cleaned_value = self.clean_value(qualifier, value)

        return qualifier, cleaned_value
예제 #8
0
    def reset_request(self, request):
        """
		Adds an endpoint to do a reset request. Generates a token, and calls the _send_reset_mail callback if the reset
		request is successful

		Request:

		POST user/reset_request/
		{
			'username': '******'
		}

		Response:
		204
		{
		}

		"""
        if request.method != 'POST':
            raise BinderMethodNotAllowed()

        self._require_model_perm('reset_password', request)

        decoded = request.body.decode()
        try:
            body = json.loads(decoded)
        except ValueError:
            raise BinderRequestError(
                _('Invalid request body: not a JSON document.'))

        logger.info('password reset attempt for {}'.format(
            body.get(self.model.USERNAME_FIELD, '')))

        for user in self.get_users(
                request,
                body.get(self.model.USERNAME_FIELD, '').lower()):
            token = self.token_generator.make_token(user)
            self._send_reset_mail(request, user, token)

        return HttpResponse(status=204)
예제 #9
0
    def reset_password(self, request, pk=None):
        """
		Resets the password from an reset code

		Request:

		POST user/reset_password/
		{
			"reset_code": str,
			"password": str
		}

		Response:

		Same as GET user/{id}/

		"""

        self._require_model_perm('reset_password', request)

        decoded = request.body.decode()
        try:
            body = json.loads(decoded)
        except ValueError:
            raise BinderRequestError(
                _('Invalid request body: not a JSON document.'))

        errors = {
            item: 'missing'
            for item in ['reset_code', 'password'] if item not in body
        }
        if errors:
            raise BinderValidationError(errors)

        return self._reset_pass_for_user(request, int(pk), body['reset_code'],
                                         body['password'])
예제 #10
0
 def check_qualifier(self, qualifier):
     if qualifier not in self.allowed_qualifiers:
         raise BinderRequestError(
             'Qualifier {} not supported for type {} ({}).'.format(
                 qualifier, self.__class__.__name__,
                 self.field_description()))
예제 #11
0
def parse_request(data, allowed_methods, responses, request):
    if not isinstance(data, dict):
        raise BinderRequestError('requests should be dicts')

    # Transform data
    transforms = data.pop('transforms', [])
    str_params = defaultdict(dict)

    if not isinstance(transforms, list):
        raise BinderRequestError('transforms should be a list')

    for transform in transforms:
        if 'source' not in transform:
            raise BinderRequestError('transforms require the field source')
        if 'target' not in transform:
            raise BinderRequestError('transforms require the field target')
        if (not isinstance(transform['source'], list)
                or len(transform['source']) < 1):
            raise BinderRequestError('source must be a non empty list')
        if (not isinstance(transform['target'], list)
                or len(transform['target']) < 2):
            raise BinderRequestError('target must be a non empty list')

        # Get value through source
        value = responses
        for key in transform['source']:
            if not isinstance(value, (list, dict)):
                raise BinderRequestError(
                    'source can only iterate through lists and dicts')
            try:
                value = value[key]
            except (KeyError, IndexError):
                raise BinderRequestError(
                    'invalid source {}, error at key {}'.format(
                        transform['source'], key))

        # Set value according to target
        target = data
        target_key = transform['target'][0]
        for i, key in enumerate(transform['target'][1:]):
            if isinstance(target, (list, dict)):
                try:
                    target = target[target_key]
                except (KeyError, IndexError):
                    raise BinderRequestError(
                        'invalid target {}, error at key {}'.format(
                            transform['target'], target_key))
                target_key = key
            else:
                raise BinderRequestError(
                    'target can only iterate through lists and dicts')

        if isinstance(target, (list, dict)):
            try:
                target[target_key] = value
            except IndexError:
                raise BinderRequestError(
                    'invalid target {}, error at key {}'.format(
                        transform['target'], target_key))
        elif isinstance(target, str):
            str_params[tuple(
                transform['target'][:-1])][transform['target'][-1]] = value
        else:
            raise BinderRequestError(
                'target can only modify lists, dicts and strs')

    try:
        for keys, params in str_params.items():
            target = data
            target_key = keys[0]
            for key in keys[1:]:
                target = target[target_key]
                target_key = key
            s = target[target_key]
            try:
                s = s.format(**params)
            except KeyError as e:
                raise BinderRequestError(
                    'str formatting at {}, missing key: {}'.format(
                        keys, e.args[0]))
            target[target_key] = target[target_key].format(**params)
    except Exception:
        # All kind of things can go wrong when the data is altered through
        # a transform that occured after the str_params where determined
        # causing the target not to exist anymore or not be a str
        raise BinderRequestError(
            'transforms altered data in such a way that str params became '
            'invalid')

    # Validate request
    if 'method' not in data:
        raise BinderRequestError('requests require the field method')
    if 'path' not in data:
        raise BinderRequestError('requests require the field path')

    # Validate method is allowed
    if data['method'] not in allowed_methods:
        raise BinderMethodNotAllowed()

    # Create request
    req = HttpRequest()
    req.method = data['method']
    req.path = data['path']
    req.path_info = req.path
    req.COOKIES = request.COOKIES
    req.META = request.META
    req.content_type = 'application/json'

    if 'body' in data:
        req._body = jsondumps(data['body']).encode()
    else:
        req._body = b''

    return req
예제 #12
0
def multi_request_view(request):
    if request.method == 'GET':
        allowed_methods = ['GET']
    elif request.method == 'POST':
        allowed_methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
    else:
        try:
            raise BinderMethodNotAllowed()
        except BinderException as e:
            e.log()
            return e.response(request)

    requests = jsonloads(request.body)
    responses = []
    key_responses = {}

    if not isinstance(requests, list):
        try:
            raise BinderRequestError('requests should be a list')
        except BinderException as e:
            e.log()
            return e.response(request)

    handler = RequestHandler()

    try:
        with transaction.atomic():
            for i, data in enumerate(requests):
                key = data.pop('key', i)

                # Get response from request
                try:
                    req = parse_request(
                        data,
                        allowed_methods,
                        key_responses,
                        request,
                    )
                except BinderException as e:
                    e.log()
                    res = e.response(request)
                else:
                    # There's a "sneaky little hack" in Django test client
                    # to avoid csrf checks in tests. This needs to be passed
                    # down to make sure tests for multi request are also skipping
                    # csrf checks. See also: https://github.com/django/django/blob/ebb08d19424c314c75908bc6048ff57c2f872269/django/test/client.py#L142
                    req._dont_enforce_csrf_checks = request._dont_enforce_csrf_checks
                    res = handler.get_response(req)

                # Serialize and add to responses
                res_data = serialize_response(res)
                responses.append(res_data)

                # Add by key so that we can reference it in other requests
                key_responses[key] = res_data

                # Rollback the transaction if the request has a failing code
                if res.status_code >= 400:
                    raise ErrorStatus(res.status_code)
    except ErrorStatus as e:
        status = e.status
    else:
        status = 200

    return JsonResponse(responses, safe=False, status=status)
예제 #13
0
    def send_activation_email(self, request):
        """
		Endpoint that can be used to send an activation mail for an user.
		Calls the _send_activation_email callback if the user is succesfully activated

		Request:

		POST
		{
			"email": "email"
		}

		Response:
		{
			"code": code
		}

		Possible codes:

		sent			Mail is send sucessfully
		already active 	User is already active, no mail was send
		blacklisted		User was not activated

		"""
        if request.method != 'PUT':
            raise BinderMethodNotAllowed()

        # For lack of a better check
        self._require_model_perm('reset_password', request)

        decoded = request.body.decode()
        try:
            body = json.loads(decoded)
        except ValueError:
            raise BinderRequestError(
                _('Invalid request body: not a JSON document.'))

        logger.info('activation email attempt for {}'.format(
            body.get('email', '')))

        if body.get('email') is None:
            raise BinderValidationError({'email': ['missing']})

        try:
            user = self.model._default_manager.get(email=body.get('email'))
        except self.model.DoesNotExist:
            raise BinderNotFound()

        if user.is_active:
            if user.last_login is None:
                # TODO: Figure out a way to make this customisable without
                # allowing injection of arbitrary URLs (phishing!)
                self._send_activation_email(request, user)
                response = JsonResponse({'code': 'sent'})
                response.status_code = 201
            else:
                response = JsonResponse({'code': 'already active'})
        else:
            response = JsonResponse({'code': 'blacklisted'})
            response.status_code = 400

        return response