def validate_token_request(self, request): # We need to set these at None by default otherwise # we are going to get some AttributeError later request._params.setdefault("backend", None) request._params.setdefault("client_secret", None) if request.grant_type != 'convert_token': raise errors.UnsupportedGrantTypeError(request=request) # We check that a token parameter is present. # It should contain the social token to be used with the backend if request.token is None: raise errors.InvalidRequestError( description='Missing token parameter.', request=request) # We check that a provider parameter is present. # It should contain the name of the social provider to be used if request.provider is None: raise errors.InvalidRequestError( description='Missing provider parameter.', request=request) if not request.client_id: raise errors.MissingClientIdError(request=request) if not self.request_validator.validate_client_id( request.client_id, request): raise errors.InvalidClientIdError(request=request) # Existing code to retrieve the application instance from the client id if self.request_validator.client_authentication_required(request): logger.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): logger.debug('Invalid client (%r), denying access.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id( request.client_id, request): logger.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) # Ensure client is authorized use of this grant type # We chose refresh_token as a grant_type # as we don't want to modify all the codebase. # It is also the most permissive and logical grant for our needs. request.grant_type = "refresh_token" self.validate_grant_type(request) self.validate_scopes(request) # TODO: Implement logic to decide whether or not to grant the access_token # Decoded body is a list of tuple, dict is better body = {} for t in request.decoded_body: body[t[0]] = t[1] user = get_user_by_access_token(token=body['token'], provider=body['provider'], nonce=body['nonce']) if user is None: raise errors.InvalidClientError( "Authentication token for the given provider is invalid") request.user = user
def create_token_response(self, request, token_handler): """ Follows the same routine as password token grant in oauthlib """ headers = self._get_default_headers() try: if self.request_validator.client_authentication_required(request): log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id( request.client_id, request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) log.debug('Validating access token request, %r.', request) self.validate_token_request(request) except errors.OAuth2Error as e: log.debug('Client error in token request, %s.', e) headers.update(e.headers) return headers, e.json, e.status_code token = token_handler.create_token(request, self.refresh_token) for modifier in self._token_modifiers: token = modifier(token) self.request_validator.save_token(token, request) log.debug('Issuing new token to client id %r (%r), %r.', request.client_id, request.client, token) return headers, json.dumps(token), 200
def validate_token_request(self, request): """ Same as original method (of AuthorizationCodeGrant) except for letting out the request_uri and request_uri validations :param request: :return: """ if request.grant_type != 'authorization_code_push': raise errors.UnsupportedGrantTypeError(request=request) if request.code is None: raise errors.InvalidRequestError( description='Missing code parameter.', request=request) for param in ('client_id', 'grant_type'): if param in request.duplicate_params: raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request) if self.request_validator.client_authentication_required(request): if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id(request.client_id, request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) if not hasattr(request.client, 'client_id'): raise NotImplementedError('Authenticate client must set the ' 'request.client.client_id attribute ' 'in authenticate_client.') self.validate_grant_type(request) if not self.request_validator.validate_code(request.client_id, request.code, request.client, request): log.debug('Client, %r (%r), is not allowed access to scopes %r.', request.client_id, request.client, request.scopes) raise errors.InvalidGrantError(request=request) for attr in ('user', 'scopes'): if getattr(request, attr, None) is None: log.debug('request.%s was not set on code validation.', attr)
def authenticate_client(self, request): log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) else: if not hasattr(request.client, 'client_id'): raise NotImplementedError('Authenticate client must set the ' 'request.client.client_id attribute ' 'in authenticate_client.')
def create_token_response(self, request, token_handler): """Return token or error in json format. If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in `Section 5.1`_. If the request failed client authentication or is invalid, the authorization server returns an error response as described in `Section 5.2`_. .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1 .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2 """ headers = { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', 'Pragma': 'no-cache', } try: if self.request_validator.client_authentication_required(request): log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id( request.client_id, request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) log.debug('Validating access token request, %r.', request) self.validate_token_request(request) except errors.OAuth2Error as e: log.debug('Client error in token request, %s.', e) return headers, e.json, e.status_code token = token_handler.create_token(request, self.refresh_token) log.debug('Issuing token %r to client id %r (%r) and username %s.', token, request.client_id, request.client, request.username) return headers, json.dumps(token), 200
def validate_token_request(self, request): """ Validates a token request. Sets the ``client_id`` property on the passed-in request to the JWT issuer, and finds the user based on the JWT subject and sets it as the ``user`` property. Raises subclasses of ``oauthlib.oauth2.rfc6749.OAuth2Error`` when validation fails. :param request: the oauthlib request :type request: oauthlib.common.Request """ try: assertion = request.assertion except AttributeError: raise errors.InvalidRequestFatalError("Missing assertion.") token = JWTGrantToken(assertion) # Update client_id in oauthlib request request.client_id = token.issuer if not self.request_validator.authenticate_client_id( request.client_id, request ): raise errors.InvalidClientError(request=request) # Ensure client is authorized use of this grant type self.validate_grant_type(request) authclient = request.client.authclient verified_token = token.verified(key=authclient.secret, audience=self.domain) user = self.user_svc.fetch(verified_token.subject) if user is None: raise errors.InvalidGrantError( "Grant token subject (sub) could not be found." ) if user.authority != authclient.authority: raise errors.InvalidGrantError( "Grant token subject (sub) does not match issuer (iss)." ) request.user = user
def validate_token_request(self, request): if request.grant_type != JWT_BEARER: raise errors.UnsupportedGrantTypeError(request=request) if request.assertion is None: raise errors.InvalidRequestError('Missing assertion parameter.', request=request) for param in ('grant_type', 'scope'): if param in request.duplicate_params: raise errors.InvalidRequestError('Duplicate %s parameter.' % param, request=request) # Since the JSON Web Token is signed by its issuer client # authentication is not strictly required when the token is used as # an authorization grant. However, if client credentials are provided # they should be validated as describe in Section 3.1. # https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-3.1 if self.request_validator.client_authentication_required(request): log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Invalid client (%r), denying access.', request) raise errors.InvalidClientError(request=request) # REQUIRED. The web token issued by the client. log.debug('Validating assertion %s.', request.assertion) if not self.request_validator.validate_bearer_token( request.assertion, request.scopes, request): log.debug('Invalid assertion, %s, for client %r.', request.assertion, request.client) raise errors.InvalidGrantError('Invalid assertion.', request=request) original_scopes = utils.scope_to_list( self.request_validator.get_original_scopes(request.assertion, request)) if request.scope: request.scopes = utils.scope_to_list(request.scope) if (not all((s in original_scopes for s in request.scopes)) and not self.request_validator.is_within_original_scope( request.scopes, request.refresh_token, request)): log.debug('Refresh token %s lack requested scopes, %r.', request.refresh_token, request.scopes) raise errors.InvalidScopeError(request=request) else: request.scopes = original_scopes
def validate_token_request(self, request): # This method's code is based on the parent method's code # We removed the original comments to replace with ours # explaining our modifications. # We need to set these at None by default otherwise # we are going to get some AttributeError later request._params.setdefault("backend", None) request._params.setdefault("client_secret", None) if request.grant_type != 'convert_token': raise errors.UnsupportedGrantTypeError(request=request) # We check that a token parameter is present. # It should contain the social token to be used with the backend if request.token is None: raise errors.InvalidRequestError( description='Missing token parameter.', request=request) # We check that a backend parameter is present. # It should contain the name of the social backend to be used if request.backend is None: raise errors.InvalidRequestError( description='Missing backend parameter.', request=request) if not request.client_id: raise errors.MissingClientIdError(request=request) if not self.request_validator.validate_client_id( request.client_id, request): raise errors.InvalidClientIdError(request=request) # Existing code to retrieve the application instance from the client id if self.request_validator.client_authentication_required(request): log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Invalid client (%r), denying access.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id( request.client_id, request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) # Ensure client is authorized use of this grant type # We chose refresh_token as a grant_type # as we don't want to modify all the codebase. # It is also the most permissive and logical grant for our needs. request.grant_type = "refresh_token" self.validate_grant_type(request) self.validate_scopes(request) # TODO: Find a better way to pass the django request object strategy = load_strategy(request=request.django_request) try: backend = load_backend( strategy, request.backend, reverse(NAMESPACE + ":complete", args=(request.backend, ))) except MissingBackend: raise errors.InvalidRequestError( description='Invalid backend parameter.', request=request) try: user = backend.do_auth(access_token=request.token) except requests.HTTPError as e: raise errors.InvalidRequestError( description="Backend responded with HTTP{0}: {1}.".format( e.response.status_code, e.response.text), request=request) except SocialAuthBaseException as e: raise errors.AccessDeniedError(description=str(e), request=request) if not user: raise errors.InvalidGrantError('Invalid credentials given.', request=request) if not user.is_active: raise errors.InvalidGrantError('User inactive or deleted.', request=request) request.user = user log.debug('Authorizing access to user %r.', request.user)
def validate_token_request(self, request): """ :param request: OAuthlib request. :type request: oauthlib.common.Request """ # REQUIRED. Value MUST be set to "authorization_code". if request.grant_type not in ('authorization_code', 'openid'): raise errors.UnsupportedGrantTypeError(request=request) for validator in self.custom_validators.pre_token: validator(request) if request.code is None: raise errors.InvalidRequestError( description='Missing code parameter.', request=request) for param in ('client_id', 'grant_type', 'redirect_uri'): if param in request.duplicate_params: raise errors.InvalidRequestError( description='Duplicate %s parameter.' % param, request=request) if self.request_validator.client_authentication_required(request): # If the client type is confidential or the client was issued client # credentials (or assigned other authentication requirements), the # client MUST authenticate with the authorization server as described # in Section 3.2.1. # https://tools.ietf.org/html/rfc6749#section-3.2.1 if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) elif not self.request_validator.authenticate_client_id( request.client_id, request): # REQUIRED, if the client is not authenticating with the # authorization server as described in Section 3.2.1. # https://tools.ietf.org/html/rfc6749#section-3.2.1 log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) if not hasattr(request.client, 'client_id'): raise NotImplementedError('Authenticate client must set the ' 'request.client.client_id attribute ' 'in authenticate_client.') request.client_id = request.client_id or request.client.client_id # Ensure client is authorized use of this grant type self.validate_grant_type(request) # REQUIRED. The authorization code received from the # authorization server. if not self.request_validator.validate_code( request.client_id, request.code, request.client, request): log.debug('Client, %r (%r), is not allowed access to scopes %r.', request.client_id, request.client, request.scopes) raise errors.InvalidGrantError(request=request) # OPTIONAL. Validate PKCE code_verifier challenge = self.request_validator.get_code_challenge( request.code, request) if challenge is not None: if request.code_verifier is None: raise errors.MissingCodeVerifierError(request=request) challenge_method = self.request_validator.get_code_challenge_method( request.code, request) if challenge_method is None: raise errors.InvalidGrantError( request=request, description="Challenge method not found") if challenge_method not in self._code_challenge_methods: raise errors.ServerError( description="code_challenge_method {} is not supported.". format(challenge_method), request=request) if not self.validate_code_challenge(challenge, challenge_method, request.code_verifier): log.debug('request provided a invalid code_verifier.') raise errors.InvalidGrantError(request=request) elif self.request_validator.is_pkce_required(request.client_id, request) is True: if request.code_verifier is None: raise errors.MissingCodeVerifierError(request=request) raise errors.InvalidGrantError(request=request, description="Challenge not found") for attr in ('user', 'scopes'): if getattr(request, attr, None) is None: log.debug('request.%s was not set on code validation.', attr) # REQUIRED, if the "redirect_uri" parameter was included in the # authorization request as described in Section 4.1.1, and their # values MUST be identical. if request.redirect_uri is None: request.using_default_redirect_uri = True request.redirect_uri = self.request_validator.get_default_redirect_uri( request.client_id, request) log.debug('Using default redirect_uri %s.', request.redirect_uri) if not request.redirect_uri: raise errors.MissingRedirectURIError(request=request) else: request.using_default_redirect_uri = False log.debug('Using provided redirect_uri %s', request.redirect_uri) if not self.request_validator.confirm_redirect_uri( request.client_id, request.code, request.redirect_uri, request.client, request): log.debug('Redirect_uri (%r) invalid for client %r (%r).', request.redirect_uri, request.client_id, request.client) raise errors.MismatchingRedirectURIError(request=request) for validator in self.custom_validators.post_token: validator(request)
def validate_token_request(self, request): """validate token request by request attributes and jwt claims.""" # pylint: disable-msg=too-many-branches # REQUIRED. Per http://tools.ietf.org/html/rfc7523#section-2.1, value # MUST be set to "urn:ietf:params:oauth:grant-type:jwt-bearer". # Note: support for receiving client authentication jwt requests # following the parameter values specified by rfc7523 section 2.2 is # not yet implemented https://tools.ietf.org/html/rfc7523#section-2.2 if request.grant_type != 'urn:ietf:params:oauth:grant-type:jwt-bearer': raise errors.UnsupportedGrantTypeError(request=request) for validator in self.custom_validators.pre_token: validator(request) if getattr(request, 'assertion', None) is None: raise errors.InvalidRequestError( description='Missing jwt assertion parameter.', request=request) elif len(request.assertion.split(',')) > 1: raise errors.InvalidRequestError( description='Assertion MUST NOT contain more than one JWT', request=request) # Jwt MUST contain exp claim per # https://tools.ietf.org/html/rfc7523#section-3. Signature verification # is postponed for handling in request_validator after retrieval of # public key matching correct client. options = {'verify_signature': False, 'require_exp': True} # The JWT MUST contain an "aud" (audience) claim containing a # value that identifies the authorization server as an intended # audience. audience = self.request_validator.get_audience(request) try: payload = jwt.decode(request.assertion, '', audience=audience, options=options, algorithms=['RS256']) except jwt.ExpiredSignatureError: raise errors.InvalidGrantError( description='JWT request contains an expired signature', request=request) except jwt.ImmatureSignatureError: raise errors.InvalidGrantError( description='JWT is not yet valid (nbf)', request=request) except jwt.InvalidAudienceError: raise errors.InvalidGrantError( description='JWT request contains invalid audience claim', request=request) except jwt.MissingRequiredClaimError: raise errors.InvalidGrantError( description='JWT is missing a required claim', request=request) except jwt.DecodeError: raise errors.InvalidGrantError( description='One of more errors occurred during JWT decode', request=request) # The JWT MUST contain an "iss" (issuer) claim that contains a # unique identifier for the entity that issued the JWT. In the # absence of an application profile specifying otherwise, # compliant applications MUST compare issuer values using the # Simple String Comparison method defined in Section 6.2.1 of RFC # 3986 [RFC3986] https://tools.ietf.org/html/rfc7523#section-3 if not self.request_validator.validate_issuer(request, payload): msg = 'Missing or invalid (iss) claim' log_msg = INVALID_CLAIM.format(msg=msg, payload=payload) log.debug(log_msg) raise errors.InvalidGrantError(description=msg, request=request) # If client_id is not supplied as a param or claim, the issuer must be # client_id. client_id is validated, rather than authenticated, since # authentication is completed by validating token signature. # request.client is set in validate_client_id. if not request.client_id: request.client_id = payload.get('client_id', payload.get('iss')) if not self.request_validator.validate_client_id( request.client_id, request): log.debug('Client authentication failed, %s.', request) raise errors.InvalidClientError(request=request) # A validate_signature method that provides functionality for # retrieving the public key appropriate to the issuer or client_id, # and completes the decoding of the jwt using said key, # MUST be added to the request_validator class. if not self.request_validator.validate_signature( request, request.client, request.assertion): msg = 'Missing or invalid token signature' log_msg = INVALID_TOKEN.format(msg=msg, payload=payload) log.debug(log_msg) raise errors.InvalidGrantError(description=msg, request=request) # Ensure client is authorized use of this grant type. self.validate_grant_type(request) # The request_validator class must include a validate_subject method # that ensures the user ('sub') exists and is active. if not self.request_validator.validate_subject(request, request.client, payload): msg = 'Missing or invalid (sub) claim' log_msg = INVALID_CLAIM.format(msg=msg, payload=payload) log.debug(log_msg) raise errors.InvalidGrantError(description=msg, request=request) # Since a jwt flow acts much like a refresh token flow, giving the # client access to the user's resources without the user present, # server's MAY wish to validate an existing authorization exists # containing explicit offline_access via scope or implied via # refresh token. A validate_offline_access method MUST be added to your # request_validator class; it is recommended that # request.refresh_tokens be set to avoid another query to validate # other scopes against previously authorized scopes. log.debug('Validating offline access for client %s.', request.client) if not self.request_validator.validate_offline_access( request, request.user, request.client): msg = 'Client not authorized for offline_access grants' log_msg = INVALID_CLAIM.format(msg=msg, payload=payload) log.debug(log_msg) raise errors.InvalidGrantError(description=msg, request=request) # A validate_refresh_scopes method must be implemented in the # request_validator class, to either verify that all requested scopes # are within previously authorized scopes, or allow all scopes that # are available to the client. requested_scope = request.scope or payload.get('scope') if not self.request_validator.validate_refresh_scopes( request, getattr(request, 'refresh_tokens', None), requested_scope): log.debug('Client %s lacks requested scopes, %s.', request.client_id, request.scopes) raise errors.InvalidScopeError(request=request) # A jwt MAY contain additional claims. A validate_additional_claims # method should be implemented in the request_validator class to # validate all other jwt claims. if not self.request_validator.validate_additional_claims( request, payload): msg = ("One or more additional claims failed validation. See " "provider for jwt claim requirements") raise errors.InvalidGrantError(description=msg, request=request) for validator in self.custom_validators.post_token: validator(request)