Пример #1
0
    def _validate_creds_and_get_user(request):
        """
        Validate one of token or api_key provided either in headers or query parameters.
        Will returnt the User

        :rtype: :class:`UserDB`
        """

        headers = request.headers
        query_string = request.query_string
        query_params = dict(urlparse.parse_qsl(query_string))

        token_in_headers = headers.get(HEADER_ATTRIBUTE_NAME, None)
        token_in_query_params = query_params.get(QUERY_PARAM_ATTRIBUTE_NAME,
                                                 None)

        api_key_in_headers = headers.get(HEADER_API_KEY_ATTRIBUTE_NAME, None)
        api_key_in_query_params = query_params.get(
            QUERY_PARAM_API_KEY_ATTRIBUTE_NAME, None)

        if ((token_in_headers or token_in_query_params)
                and (api_key_in_headers or api_key_in_query_params)):
            raise auth_exceptions.MultipleAuthSourcesError(
                'Only one of Token or API key expected.')

        user = None

        if token_in_headers or token_in_query_params:
            token_db = auth_utils.validate_token_and_source(
                token_in_headers=token_in_headers,
                token_in_query_params=token_in_query_params)
            user = token_db.user
        elif api_key_in_headers or api_key_in_query_params:
            api_key_db = auth_utils.validate_api_key_and_source(
                api_key_in_headers=api_key_in_headers,
                api_key_query_params=api_key_in_query_params)
            user = api_key_db.user
        else:
            raise auth_exceptions.NoAuthSourceProvidedError(
                'One of Token or API key required.')

        if not user:
            LOG.warn('User not found for supplied token or api-key.')
            return None

        try:
            return User.get(user)
        except StackStormDBObjectNotFoundError:
            # User doesn't exist - we should probably also invalidate token/apikey if
            # this happens.
            LOG.warn('User %s not found.', user)
            return None
Пример #2
0
    def __call__(self, req):
        """
        The method is invoked on every request and shows the lifecycle of the request received from
        the middleware.

        Although some middleware may use parts of the API spec, it is safe to assume that if you're
        looking for the particular spec property handler, it's most  likely a part of this method.

        At the time of writing, the only property being utilized by middleware was `x-log-result`.
        """
        endpoint, path_vars = self.match(req)

        context = copy.copy(getattr(self, 'mock_context', {}))

        # Handle security
        if 'security' in endpoint:
            security = endpoint.get('security')
        else:
            security = self.spec.get('security', [])

        if self.auth and security:
            try:
                auth_resp = None
                security_definitions = self.spec.get('securityDefinitions', {})
                for statement in security:
                    declaration, options = statement.copy().popitem()
                    definition = security_definitions[declaration]

                    if definition['type'] == 'apiKey':
                        if definition['in'] == 'header':
                            token = req.headers.get(definition['name'])
                        elif definition['in'] == 'query':
                            token = req.GET.get(definition['name'])
                        else:
                            token = None

                        if token:
                            if auth_resp:
                                raise auth_exc.MultipleAuthSourcesError(
                                    'Only one of Token or API key expected.')

                            auth_func = op_resolver(
                                definition['x-operationId'])
                            auth_resp = auth_func(token)

                            context['user'] = User.get_by_name(auth_resp.user)

                if 'user' not in context:
                    raise auth_exc.NoAuthSourceProvidedError(
                        'One of Token or API key required.')
            except (auth_exc.NoAuthSourceProvidedError,
                    auth_exc.MultipleAuthSourcesError) as e:
                LOG.error(str(e))
                return abort_unauthorized(str(e))
            except auth_exc.TokenNotProvidedError as e:
                LOG.exception('Token is not provided.')
                return abort_unauthorized(str(e))
            except auth_exc.TokenNotFoundError as e:
                LOG.exception('Token is not found.')
                return abort_unauthorized(str(e))
            except auth_exc.TokenExpiredError as e:
                LOG.exception('Token has expired.')
                return abort_unauthorized(str(e))
            except auth_exc.ApiKeyNotProvidedError as e:
                LOG.exception('API key is not provided.')
                return abort_unauthorized(str(e))
            except auth_exc.ApiKeyNotFoundError as e:
                LOG.exception('API key is not found.')
                return abort_unauthorized(str(e))
            except auth_exc.ApiKeyDisabledError as e:
                LOG.exception('API key is disabled.')
                return abort_unauthorized(str(e))

            if cfg.CONF.rbac.enable:
                user_db = context['user']

                permission_type = endpoint.get('x-permissions', None)
                if permission_type:
                    resolver = resolvers.get_resolver_for_permission_type(
                        permission_type)
                    has_permission = resolver.user_has_permission(
                        user_db, permission_type)

                    if not has_permission:
                        raise rbac_exc.ResourceTypeAccessDeniedError(
                            user_db, permission_type)

        # Collect parameters
        kw = {}
        for param in endpoint.get('parameters', []) + endpoint.get(
                'x-parameters', []):
            name = param['name']
            argument_name = param.get('x-as', None) or name
            source = param['in']
            default = param.get('default', None)

            # Collecting params from different sources
            if source == 'query':
                kw[argument_name] = req.GET.get(name, default)
            elif source == 'path':
                kw[argument_name] = path_vars[name]
            elif source == 'header':
                kw[argument_name] = req.headers.get(name, default)
            elif source == 'formData':
                kw[argument_name] = req.POST.get(name, default)
            elif source == 'environ':
                kw[argument_name] = req.environ.get(name.upper(), default)
            elif source == 'context':
                kw[argument_name] = context.get(name, default)
            elif source == 'request':
                kw[argument_name] = getattr(req, name)
            elif source == 'body':
                if req.body:
                    content_type = req.headers.get('Content-Type',
                                                   'application/json')
                    content_type = parse_content_type_header(
                        content_type=content_type)[0]
                    schema = param['schema']

                    try:
                        if content_type == 'application/json':
                            data = req.json
                        elif content_type == 'text/plain':
                            data = req.body
                        elif content_type in [
                                'application/x-www-form-urlencoded',
                                'multipart/form-data'
                        ]:
                            data = urlparse.parse_qs(req.body)
                        else:
                            raise ValueError('Unsupported Content-Type: "%s"' %
                                             (content_type))
                    except Exception as e:
                        detail = 'Failed to parse request body: %s' % str(e)
                        raise exc.HTTPBadRequest(detail=detail)

                    try:
                        CustomValidator(
                            schema, resolver=self.spec_resolver).validate(data)
                    except (jsonschema.ValidationError, ValueError) as e:
                        raise exc.HTTPBadRequest(
                            detail=e.message, comment=traceback.format_exc())

                    if content_type == 'text/plain':
                        kw[argument_name] = data
                    else:

                        class Body(object):
                            def __init__(self, **entries):
                                self.__dict__.update(entries)

                        ref = schema.get('$ref', None)
                        if ref:
                            with self.spec_resolver.resolving(ref) as resolved:
                                schema = resolved

                        if 'x-api-model' in schema:
                            Model = op_resolver(schema['x-api-model'])
                        else:
                            Model = Body

                        kw[argument_name] = Model(**data)
                else:
                    kw[argument_name] = None

            # Making sure all required params are present
            required = param.get('required', False)
            if required and kw[argument_name] is None:
                detail = 'Required parameter "%s" is missing' % name
                raise exc.HTTPBadRequest(detail=detail)

            # Validating and casting param types
            type = param.get('type', None)
            if kw[argument_name] is not None:
                if type == 'boolean':
                    positive = ('true', '1', 'yes', 'y')
                    negative = ('false', '0', 'no', 'n')

                    if str(kw[argument_name]).lower(
                    ) not in positive + negative:
                        detail = 'Parameter "%s" is not of type boolean' % argument_name
                        raise exc.HTTPBadRequest(detail=detail)

                    kw[argument_name] = str(
                        kw[argument_name]).lower() in positive
                elif type == 'integer':
                    regex = r'^-?[0-9]+$'

                    if not re.search(regex, str(kw[argument_name])):
                        detail = 'Parameter "%s" is not of type integer' % argument_name
                        raise exc.HTTPBadRequest(detail=detail)

                    kw[argument_name] = int(kw[argument_name])
                elif type == 'number':
                    regex = r'^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$'

                    if not re.search(regex, str(kw[argument_name])):
                        detail = 'Parameter "%s" is not of type float' % argument_name
                        raise exc.HTTPBadRequest(detail=detail)

                    kw[argument_name] = float(kw[argument_name])

        # Call the controller
        func = op_resolver(endpoint['operationId'])
        resp = func(**kw)

        # Handle response
        if resp is None:
            resp = Response()

        if not hasattr(resp, '__call__'):
            resp = Response(json=resp)

        responses = endpoint.get('responses', {})
        response_spec = responses.get(str(resp.status_code),
                                      responses.get('default', None))

        if response_spec and 'schema' in response_spec:
            try:
                validator = CustomValidator(response_spec['schema'],
                                            resolver=self.spec_resolver)
                validator.validate(resp.json)
            except (jsonschema.ValidationError, ValueError):
                LOG.exception('Response validation failed.')
                resp.headers.add('Warning',
                                 '199 OpenAPI "Response validation failed"')

        return resp