示例#1
0
    def authenticate(self, context, auth_payload, auth_context):
        """Turn a signed request with an access key into a keystone token."""
        if not self.oauth2_api:
            raise exception.Unauthorized(_('%s not supported') % self.method)

        access_token_id = auth_payload['access_token_id']
        if not access_token_id:
            raise exception.ValidationError(attribute='oauth2_token',
                                            target='request')

        headers = context['headers']
        uri = controller.V3Controller.base_url(context, context['path'])
        http_method = 'POST'
        required_scopes = ['all_info']
        request_validator = validator.OAuth2Validator()
        server = oauth2_core.Server(request_validator)
        body = {'access_token': access_token_id}
        valid, oauthlib_request = server.verify_request(
            uri, http_method, body, headers, required_scopes)
        # oauthlib_request has a few convenient attributes set such as
        # oauthlib_request.client = the client associated with the token
        # oauthlib_request.user = the user associated with the token
        # oauthlib_request.scopes = the scopes bound to this token
        if valid:
            auth_context['user_id'] = oauthlib_request.user
            #auth_context['access_token_id'] = access_token_id
            #auth_context['project_id'] = project_id
            return None
        else:
            msg = _('Could not validate the access token')
            raise exception.Unauthorized(msg)
    def create_authorization_code(self, context, user_auth):
        request_validator = validator.OAuth2Validator()
        server = core.Server(request_validator)
        # Validate request
        headers = context['headers']
        body = user_auth
        uri = self.base_url(context, context['path'])
        http_method = 'POST'

        # Fetch authorized scopes from the request
        scopes = body.get('scopes')
        if not scopes:
            raise exception.ValidationError(attribute='scopes',
                                            target='request')

        # Fetch the credentials saved in the pre authorization phase
        client_id = body.get('client_id')
        if not client_id:
            raise exception.ValidationError(attribute='client_id',
                                            target='request')

        user_id = body.get('user_id')
        if not user_id:
            # Try to extract the user_id from the token
            user_id = self._extract_user_id_from_token(context['token_id'])

        credentials = self.oauth2_api.get_consumer_credentials(
            client_id, user_id)

        try:

            headers, body, status = server.create_authorization_response(
                uri, http_method, body, headers, scopes, credentials)
            # headers = {'Location': 'https://foo.com/welcome_back?code=somera
            # ndomstring&state=xyz  '}, this might change to include suggested
            # headers related to cache best practices etc.
            # body = '', this might be set in future custom grant types
            # status = 302, suggested HTTP status code

            response = wsgi.render_response(body,
                                            status=(302, 'Found'),
                                            headers=headers.items())

            LOG.info(
                'OAUTH2: Created Authorization Code to consumer %(consumer)s \
                for user %(user)s with scope %(scope)s. Redirecting to %(uri)s',
                {
                    'consumer': client_id,
                    'user': user_id,
                    'scope': scopes,
                    'uri': headers['Location']
                })

            return response
        except FatalClientError as e:
            # NOTE(garcianavalon) form the OAuthLib documentation and comments:
            # Errors during authorization where user should not be redirected back.
            # If the request fails due to a missing, invalid, or mismatching
            # redirection URI, or if the client identifier is missing or invalid,
            # the authorization server SHOULD inform the resource owner of the
            # error and MUST NOT automatically redirect the user-agent to the
            # invalid redirection URI.
            # Instead the user should be informed of the error by the provider itself.
            # Fatal errors occur when the client_id or redirect_uri is invalid or
            # missing. These must be caught by the provider and handled, how this
            # is done is outside of the scope of OAuthLib but showing an error
            # page describing the issue is a good ideaself.
            msg = e.json
            LOG.warning('OAUTH2: FatalClientError %s' % msg)
            raise exception.ValidationError(message=msg)
    def create_access_token(self, context, token_request):
        request_validator = validator.OAuth2Validator()
        server = core.Server(request_validator)

        # Validate request
        headers = context['headers']
        # NOTE(garcianavalon) Work around the keystone limitation with content types
        # Keystone only accepts JSON bodies while OAuth2.0 (RFC 6749) requires
        # x-www-form-urlencoded
        # We leave it like this to support future versions where the use of
        # x-www-form-urlencoded is accepted
        if headers['Content-Type'] == 'application/x-www-form-urlencoded':
            body = context['query_string']
        elif headers['Content-Type'] == 'application/json':
            # TODO(garcianavalon) are these checks really necessary or
            # can we delegate them to oauthlib?
            grant_type = token_request.get('grant_type', None)
            if not grant_type:
                msg = _('grant_type missing in request body: {0}').format(
                    token_request)
                raise exception.ValidationError(message=msg)
            if (grant_type == 'authorization_code'
                    and not 'code' in token_request):

                msg = _('code missing in request body: %s') % token_request
                raise exception.ValidationError(message=msg)

            body = urllib.urlencode(token_request)
        else:
            msg = _(
                'Content-Type: %s is not supported') % headers['Content-Type']
            raise exception.ValidationError(message=msg)

        # check headers for authentication
        authmethod, auth = headers['Authorization'].split(' ', 1)
        if authmethod.lower() != 'basic':
            msg = _('Authorization error: %s. Only HTTP Basic is supported'
                    ) % headers['Authorization']
            raise exception.ValidationError(message=msg)

        uri = self.base_url(context, context['path'])
        http_method = 'POST'

        # Extra credentials you wish to include
        credentials = None  # TODO(garcianavalon)

        headers, body, status = server.create_token_response(
            uri, http_method, body, headers, credentials)

        # headers will contain some suggested headers to add to your response
        # {
        #     'Content-Type': 'application/json',
        #     'Cache-Control': 'no-store',
        #     'Pragma': 'no-cache',
        # }
        # body will contain the token in json format and expiration from now
        # in seconds.
        # {
        #     'access_token': 'sldafh309sdf',
        #     'refresh_token': 'alsounguessablerandomstring',
        #     'expires_in': 3600,
        #     'scope': 'https://example.com/userProfile https://example.com/pictures',
        #     'token_type': 'Bearer'
        # }
        # body will contain an error code and possibly an error description if
        # the request failed, also in json format.
        # {
        #     'error': 'invalid_grant_type',
        #     'description': 'athorizatoin_coed is not a valid grant type'
        # }
        # status will be a suggested status code, 200 on ok, 400 on bad request
        # and 401 if client is trying to use an invalid authorization code,
        # fail to authenticate etc.

        # NOTE(garcianavalon) oauthlib returns the body as a JSON string already,
        # and the Keystone base controlers expect a dictionary
        body = json.loads(body)
        # TODO(garcianavalon) body contains scope instead of scopes and is only a
        # space separated string instead of a list. We can wait for a change in
        # Oauthlib or implement our own TokenProvider
        if status == 200:
            response = wsgi.render_response(body,
                                            status=(status, 'OK'),
                                            headers=headers.items())
            LOG.info('OAUTH2: Created Access Token %s' % body['access_token'])
            return response
        # Build the error message and raise the corresponding error
        msg = _(body['error'])
        if hasattr(body, 'description'):
            msg = msg + ': ' + _(body['description'])
        LOG.warning('OAUTH2: Error creating Access Token %s' % msg)
        if status == 400:
            raise exception.ValidationError(message=msg)
        elif status == 401:
            # TODO(garcianavalon) custom exception class
            raise exception.Unauthorized(message=msg)
    def request_authorization_code(self, context):
        request_validator = validator.OAuth2Validator()
        server = core.Server(request_validator)
        # Validate request
        headers = context['headers']
        body = context['query_string']
        uri = self.base_url(context, context['path'])
        http_method = 'GET'

        response = {}
        try:
            scopes, credentials = server.validate_authorization_request(
                uri, http_method, body, headers)
            # scopes will hold default scopes for client, i.e.
            #['https://example.com/userProfile', 'https://example.com/pictures']

            # credentials is a dictionary of
            # {
            #     'client_id': 'foo',
            #     'redirect_uri': 'https://foo.com/welcome_back',
            #     'response_type': 'code',
            #     'state': 'randomstring',
            #     'request' : The request object created internally.
            # }
            # these credentials will be needed in the post authorization view and
            # should be persisted between. None of them are secret but take care
            # to ensure their integrity if embedding them in the form or cookies.

            # NOTE(garcianavalon) We are not storing this for now,
            # but might do it in the future
            request = credentials.pop('request')

            # get the user id to identify the credentials in later stages
            credentials['user_id'] = self._extract_user_id_from_token(
                context['token_id'])
            credentials_ref = self._assign_unique_id(
                self._normalize_dict(credentials))
            self.oauth2_api.store_consumer_credentials(credentials_ref)

            # Present user with a nice form where client (id foo) request access to
            # his default scopes (omitted from request), after which you will
            # redirect to his default redirect uri (omitted from request).

            # This JSON is to be used by the next layer (ie a Django server) to
            # populate the view
            response['data'] = {
                'consumer': {
                    'id': credentials['client_id']
                    # TODO(garcianavalon) add consumer description
                },
                'redirect_uri': credentials['redirect_uri'],
                'requested_scopes': request.scopes
            }
            LOG.info(
                'OAUTH2: Requested Authorization Code by consumer %(consumer)s \
                to user %(user)s, with scope %(scope)s and redirect uri %(uri)s',
                {
                    'consumer': credentials['client_id'],
                    'user': credentials['user_id'],
                    'scope': request.scopes,
                    'uri': credentials['redirect_uri']
                })

        except FatalClientError as e:
            # NOTE(garcianavalon) form the OAuthLib documentation and comments:
            # Errors during authorization where user should not be redirected back.
            # If the request fails due to a missing, invalid, or mismatching
            # redirection URI, or if the client identifier is missing or invalid,
            # the authorization server SHOULD inform the resource owner of the
            # error and MUST NOT automatically redirect the user-agent to the
            # invalid redirection URI.
            # Instead the user should be informed of the error by the provider itself.
            # Fatal errors occur when the client_id or redirect_uri is invalid or
            # missing. These must be caught by the provider and handled, how this
            # is done is outside of the scope of OAuthLib but showing an error
            # page describing the issue is a good idea.
            msg = e.json
            LOG.warning('OAUTH2: FatalClientError %s' % msg)
            raise exception.ValidationError(message=msg)

        except OAuth2Error as e:
            # NOTE(garcianavalon) form the OAuthLib documentation and comments:
            # A normal error could be a missing response_type parameter or the client
            # attempting to access scope it is not allowed to ask authorization for.
            # Normal errors can safely be included in the redirection URI and
            # sent back to the client.

            # We send back the errors in the response body
            response['error'] = json.loads(e.json)
            LOG.warning('OAUTH2: OAuth2Error %s' % response['error'])

        return response