def create_revocation_response(self, uri, http_method='POST', body=None, headers=None): """Revoke supplied access or refresh token. The authorization server responds with HTTP status code 200 if the token has been revoked sucessfully or if the client submitted an invalid token. Note: invalid tokens do not cause an error response since the client cannot handle such an error in a reasonable way. Moreover, the purpose of the revocation request, invalidating the particular token, is already achieved. The content of the response body is ignored by the client as all necessary information is conveyed in the response code. An invalid token type hint value is ignored by the authorization server and does not influence the revocation response. """ request = Request(uri, http_method=http_method, body=body, headers=headers) try: self.validate_revocation_request(request) log.debug('Token revocation valid for %r.', request) except OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) return {}, e.json, e.status_code self.request_validator.revoke_token(request.token, request.token_type_hint, request) response_body = request.callback + '()' if request.callback else None return {}, response_body, 200
def create_token_response(self, request, token_handler): """Validate the authorization code. The client MUST NOT use the authorization code more than once. If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code. The authorization code is bound to the client identifier and redirection URI. """ headers = { 'Content-Type': 'application/json;charset=UTF-8', 'Cache-Control': 'no-store', 'Pragma': 'no-cache', } try: self.validate_token_request(request) log.debug('Token request validation ok for %r.', request) except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) return None, headers, e.json, e.status_code token = token_handler.create_token(request, refresh_token=True) self.request_validator.invalidate_authorization_code( request.client_id, request.code, request) return None, headers, json.dumps(token), 200
def create_token_response(self, request, token_handler): """Create a new access token from a refresh_token. If valid and authorized, the authorization server issues an access token as described in `Section 5.1`_. If the request failed verification or is invalid, the authorization server returns an error response as described in `Section 5.2`_. The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token. The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client. If a new refresh token is issued, the refresh token scope MUST be identical to that of the refresh token included by the client in the request. .. _`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;charset=UTF-8', 'Cache-Control': 'no-store', 'Pragma': 'no-cache', } try: log.debug('Validating refresh token request, %r.', request) self.validate_token_request(request) except errors.OAuth2Error as e: return headers, e.json, e.status_code token = token_handler.create_token(request, refresh_token=self.issue_new_refresh_tokens) log.debug('Issuing new token to client id %r (%r), %r.', request.client_id, request.client, token) return headers, json.dumps(token), 200
def create_token_response(self, request, token_handler): """Create a new access token from a refresh_token. If valid and authorized, the authorization server issues an access token as described in `Section 5.1`_. If the request failed verification or is invalid, the authorization server returns an error response as described in `Section 5.2`_. The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token. The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client. If a new refresh token is issued, the refresh token scope MUST be identical to that of the refresh token included by the client in the request. .. _`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: log.debug('Validating refresh token request, %r.', request) self.validate_token_request(request) except errors.OAuth2Error as e: return headers, e.json, e.status_code token = token_handler.create_token( request, refresh_token=self.issue_new_refresh_tokens) log.debug('Issuing new token to client id %r (%r), %r.', request.client_id, request.client, token) return headers, json.dumps(token), 200
def create_token_response(self, request, token_handler): """Validate the authorization code. The client MUST NOT use the authorization code more than once. If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code. The authorization code is bound to the client identifier and redirection URI. """ headers = { 'Content-Type': 'application/json;charset=UTF-8', 'Cache-Control': 'no-store', 'Pragma': 'no-cache', } try: self.validate_token_request(request) log.debug('Token request validation ok for %r.', request) except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) return headers, e.json, e.status_code token = token_handler.create_token(request, refresh_token=True) self.request_validator.invalidate_authorization_code( request.client_id, request.code, request) return headers, json.dumps(token), 200
def validate_token_request(self, request): # REQUIRED. Value MUST be set to "authorization_code". if request.grant_type != 'authorization_code': 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', 'redirect_uri'): if param in request.duplicate_params: raise errors.InvalidRequestError(state=request.state, 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. # http://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. # http://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.') # 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) for attr in ('user', 'state', 'scopes'): if getattr(request, attr) 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 not self.request_validator.confirm_redirect_uri(request.client_id, request.code, request.redirect_uri, request.client): log.debug('Redirect_uri (%r) invalid for client %r (%r).', request.redirect_uri, request.client_id, request.client) raise errors.AccessDeniedError(request=request)
def create_authorization_code(self, request): """Generates an authorization grant represented as a dictionary.""" grant = {'code': common.generate_token()} if hasattr(request, 'state') and request.state: grant['state'] = request.state log.debug('Created authorization code grant %r for request %r.', grant, request) return grant
def validate_scopes(self, request): if not request.scopes: request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list( self.request_validator.get_default_scopes(request.client_id, request)) log.debug('Validating access to scopes %r for client %r (%r).', request.scopes, request.client_id, request.client) if not self.request_validator.validate_scopes(request.client_id, request.scopes, request.client, request): raise errors.InvalidScopeError(state=request.state, request=request)
def new_state(self): """Generates a state string to be used in authorizations.""" try: self._state = self.state() log.debug('Generated new state %s.', self._state) except TypeError: self._state = self.state log.debug('Re-using previously supplied state %s.', self._state) return self._state
def create_token_response(self, uri, http_method='GET', body=None, headers=None, credentials=None): """Extract grant_type and route to the designated handler.""" request = Request(uri, http_method=http_method, body=body, headers=headers) request.extra_credentials = credentials grant_type_handler = self.grant_types.get(request.grant_type, self.default_grant_type_handler) log.debug('Dispatching grant_type %s request to %r.', request.grant_type, grant_type_handler) return grant_type_handler.create_token_response( request, self.default_token_type)
def verify_request(self, uri, http_method='GET', body=None, headers=None, scopes=None): """Validate client, code etc, return body + headers""" request = Request(uri, http_method, body, headers) request.token_type = self.find_token_type(request) request.scopes = scopes token_type_handler = self.tokens.get(request.token_type, self.default_token_type_handler) log.debug('Dispatching token_type %s request to %r.', request.token_type, token_type_handler) return token_type_handler.validate_request(request), request
def create_token_response(self, uri, http_method='GET', body=None, headers=None, credentials=None): """Extract grant_type and route to the designated handler.""" request = Request(uri, http_method=http_method, body=body, headers=headers) request.scopes = None request.extra_credentials = credentials grant_type_handler = self.grant_types.get(request.grant_type, self.default_grant_type_handler) log.debug('Dispatching grant_type %s request to %r.', request.grant_type, grant_type_handler) return grant_type_handler.create_token_response( request, self.default_token_type)
def validate_token_request(self, request): # REQUIRED. Value MUST be set to "authorization_code". if request.grant_type != 'authorization_code': 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', 'redirect_uri'): if param in request.duplicate_params: raise errors.InvalidRequestError( state=request.state, description='Duplicate %s parameter.' % param, request=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. # http://tools.ietf.org/html/rfc6749#section-3.2.1 if not self.request_validator.authenticate_client(request): # REQUIRED, if the client is not authenticating with the # authorization server as described in Section 3.2.1. # http://tools.ietf.org/html/rfc6749#section-3.2.1 if not self.request_validator.authenticate_client_id( request.client_id, 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.') # 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) for attr in ('user', 'state', 'scopes'): if getattr(request, attr) 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 not self.request_validator.confirm_redirect_uri( request.client_id, request.code, request.redirect_uri, request.client): log.debug('Redirect_uri (%r) invalid for client %r (%r).', request.redirect_uri, request.client_id, request.client) raise errors.AccessDeniedError(request=request)
def create_authorization_response(self, uri, http_method='GET', body=None, headers=None, scopes=None, credentials=None): """Extract response_type and route to the designated handler.""" request = Request(uri, http_method=http_method, body=body, headers=headers) request.scopes = scopes # TODO: decide whether this should be a required argument request.user = None # TODO: explain this in docs for k, v in (credentials or {}).items(): setattr(request, k, v) response_type_handler = self.response_types.get( request.response_type, self.default_response_type_handler) log.debug('Dispatching response_type %s request to %r.', request.response_type, response_type_handler) return response_type_handler.create_authorization_response( request, self.default_token_type)
def validate_token_request(self, request): # REQUIRED. Value MUST be set to "refresh_token". if request.grant_type != 'refresh_token': raise errors.UnsupportedGrantTypeError(request=request) if request.refresh_token is None: raise errors.InvalidRequestError( description='Missing refresh token parameter.', request=request) # Because refresh tokens are typically long-lasting credentials used to # request additional access tokens, the refresh token is bound to the # client to which it was issued. 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. # http://tools.ietf.org/html/rfc6749#section-3.2.1 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, status_code=401) # Ensure client is authorized use of this grant type self.validate_grant_type(request) # REQUIRED. The refresh token issued to the client. log.debug('Validating refresh token %s for client %r.', request.refresh_token, request.client) if not self.request_validator.validate_refresh_token( request.refresh_token, request.client, request): log.debug('Invalid refresh token, %s, for client %r.', request.refresh_token, request.client) raise errors.InvalidGrantError(request=request) original_scopes = utils.scope_to_list( self.request_validator.get_original_scopes(request.refresh_token, request)) if request.scope: request.scopes = utils.scope_to_list(request.scope) if not all((s in original_scopes for s in request.scopes)): log.debug('Refresh token %s lack requested scopes, %r.', request.refresh_token, request.scopes) raise errors.InvalidScopeError(state=request.state, request=request, status_code=401) else: request.scopes = original_scopes
def validate_token_request(self, request): # REQUIRED. Value MUST be set to "refresh_token". if request.grant_type != 'refresh_token': raise errors.UnsupportedGrantTypeError(request=request) if request.refresh_token is None: raise errors.InvalidRequestError( description='Missing refresh token parameter.', request=request) # Because refresh tokens are typically long-lasting credentials used to # request additional access tokens, the refresh token is bound to the # client to which it was issued. 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. # http://tools.ietf.org/html/rfc6749#section-3.2.1 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, status_code=401) # Ensure client is authorized use of this grant type self.validate_grant_type(request) # REQUIRED. The refresh token issued to the client. log.debug('Validating refresh token %s for client %r.', request.refresh_token, request.client) if not self.request_validator.validate_refresh_token( request.refresh_token, request.client, request): log.debug('Invalid refresh token, %s, for client %r.', request.refresh_token, request.client) raise errors.InvalidGrantError(request=request) original_scopes = utils.scope_to_list( self.request_validator.get_original_scopes( request.refresh_token, request)) if request.scope: request.scopes = utils.scope_to_list(request.scope) if not all((s in original_scopes for s in request.scopes)): log.debug('Refresh token %s lack requested scopes, %r.', request.refresh_token, request.scopes) raise errors.InvalidScopeError( state=request.state, request=request, status_code=401) else: request.scopes = original_scopes
def refresh_token(self, token_url, refresh_token=None, body='', auth=None, **kwargs): """Fetch a new access token using a refresh token. :param token_url: The token endpoint, must be HTTPS. :param refresh_token: The refresh_token to use. :param body: Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body. :param auth: An auth tuple or method as accepted by requests. :param kwargs: Extra parameters to include in the token request. :return: A token dict """ if not token_url: raise ValueError('No token endpoint set for auto_refresh.') if not is_secure_transport(token_url): raise InsecureTransportError() # Need to nullify token to prevent it from being added to the request refresh_token = refresh_token or self.token.get('refresh_token') self.token = {} log.debug('Adding auto refresh key word arguments %s.', self.auto_refresh_kwargs) kwargs.update(self.auto_refresh_kwargs) body = self._client.prepare_refresh_body(body=body, refresh_token=refresh_token, scope=self.scope, **kwargs) log.debug('Prepared refresh token request body %s', body) r = self.post(token_url, data=dict(urldecode(body)), auth=auth) log.debug('Request to refresh token completed with status %s.', r.status_code) log.debug('Response headers were %s and content %s.', r.headers, r.text) self.token = self._client.parse_request_body_response(r.text, scope=self.scope) if not 'refresh_token' in self.token: log.debug('No new refresh token given. Re-using old.') self.token['refresh_token'] = refresh_token return self.token
def validate_token_request(self, request): if not getattr(request, 'grant_type'): raise errors.InvalidRequestError('Request is missing grant type.', request=request) if not request.grant_type == 'client_credentials': raise errors.UnsupportedGrantTypeError(request=request) for param in ('grant_type', 'scope'): if param in request.duplicate_params: raise errors.InvalidRequestError(state=request.state, description='Duplicate %s parameter.' % param, request=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.') # Ensure client is authorized use of this grant type self.validate_grant_type(request) log.debug('Authorizing access to user %r.', request.user) request.client_id = request.client_id or request.client.client_id self.validate_scopes(request)
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 as described in `Section 5.1`_. A refresh token SHOULD NOT be included. 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: 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, refresh_token=False) log.debug('Issuing token to client id %r (%r), %r.', request.client_id, request.client, token) return headers, json.dumps(token), 200
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 as described in `Section 5.1`_. A refresh token SHOULD NOT be included. 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: 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 {}, e.json, e.status_code token = token_handler.create_token(request, refresh_token=False) log.debug('Issuing 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): if not getattr(request, "grant_type"): raise errors.InvalidRequestError("Request is missing grant type.", request=request) if not request.grant_type == "client_credentials": raise errors.UnsupportedGrantTypeError(request=request) for param in ("grant_type", "scope"): if param in request.duplicate_params: raise errors.InvalidRequestError( state=request.state, description="Duplicate %s parameter." % param, request=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." ) # Ensure client is authorized use of this grant type self.validate_grant_type(request) log.debug("Authorizing access to user %r.", request.user) request.client_id = request.client_id or request.client.client_id self.validate_scopes(request)
def fetch_token(self, token_url, code=None, authorization_response=None, body='', auth=None, username=None, password=None, **kwargs): """Generic method for fetching an access token from the token endpoint. If you are using the MobileApplicationClient you will want to use token_from_fragment instead of fetch_token. :param token_url: Token endpoint URL, must use HTTPS. :param code: Authorization code (used by WebApplicationClients). :param authorization_response: Authorization response URL, the callback URL of the request back to you. Used by WebApplicationClients instead of code. :param body: Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body. :param auth: An auth tuple or method as accepted by requests. :param username: Username used by LegacyApplicationClients. :param password: Password used by LegacyApplicationClients. :param kwargs: Extra parameters to include in the token request. :return: A token dict """ if 'DEBUG' not in os.environ and not token_url.startswith('https://'): raise InsecureTransportError() if not code and authorization_response: self._client.parse_request_uri_response(authorization_response, state=self._state) code = self._client.code elif not code and isinstance(self._client, WebApplicationClient): code = self._client.code if not code: raise ValueError('Please supply either code or ' 'authorization_code parameters.') body = self._client.prepare_request_body(code=code, body=body, redirect_uri=self.redirect_uri, username=username, password=password, **kwargs) # (ib-lundgren) All known, to me, token requests use POST. r = self.post(token_url, data=dict(urldecode(body)), headers={'Accept': 'application/json'}, auth=auth) log.debug('Prepared fetch token request body %s', body) log.debug('Request to fetch token completed with status %s.', r.status_code) log.debug('Response headers were %s and content %s.', r.headers, r.text) self._client.parse_request_body_response(r.text, scope=self.scope) self.token = self._client.token log.debug('Obtained token %s.', self.token) return self.token
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 as described in `Section 5.1`_. A refresh token SHOULD NOT be included. 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 """ try: 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 None, {}, e.json, e.status_code token = token_handler.create_token(request, refresh_token=False) log.debug("Issuing token to client id %r (%r), %r.", request.client_id, request.client, token) return None, {}, json.dumps(token), 200
def validate_token_request(self, request): """ The client makes a request to the token endpoint by adding the following parameters using the "application/x-www-form-urlencoded" format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body: grant_type REQUIRED. Value MUST be set to "password". username REQUIRED. The resource owner username. password REQUIRED. The resource owner password. scope OPTIONAL. The scope of the access request as described by `Section 3.3`_. 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`_. The authorization server MUST: o require client authentication for confidential clients or for any client that was issued client credentials (or with other authentication requirements), o authenticate the client if client authentication is included, and o validate the resource owner password credentials using its existing password validation algorithm. Since this access token request utilizes the resource owner's password, the authorization server MUST protect the endpoint against brute force attacks (e.g., using rate-limitation or generating alerts). .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 """ for param in ('grant_type', 'username', 'password'): if not getattr(request, param): raise errors.InvalidRequestError( 'Request is missing %s parameter.' % param, request=request) for param in ('grant_type', 'username', 'password', 'scope'): if param in request.duplicate_params: raise errors.InvalidRequestError(state=request.state, description='Duplicate %s parameter.' % param, request=request) # This error should rarely (if ever) occur if requests are routed to # grant type handlers based on the grant_type parameter. if not request.grant_type == 'password': raise errors.UnsupportedGrantTypeError(request=request) log.debug('Validating username %s and password %s.', request.username, request.password) if not self.request_validator.validate_user(request.username, request.password, request.client, request): raise errors.InvalidGrantError('Invalid credentials given.', request=request) else: if not hasattr(request.client, 'client_id'): raise NotImplementedError( 'Validate user must set the ' 'request.client.client_id attribute ' 'in authenticate_client.') log.debug('Authorizing access to user %r.', request.user) # Ensure client is authorized use of this grant type self.validate_grant_type(request) if request.client: request.client_id = request.client_id or request.client.client_id self.validate_scopes(request)
def validate_token_request(self, request): # REQUIRED. Value MUST be set to "refresh_token". if request.grant_type != 'refresh_token': raise errors.UnsupportedGrantTypeError(request=request) if request.refresh_token is None: raise errors.InvalidRequestError( description='Missing refresh token parameter.', request=request) # Because refresh tokens are typically long-lasting credentials used to # request additional access tokens, the refresh token is bound to the # client to which it was issued. 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. # http://tools.ietf.org/html/rfc6749#section-3.2.1 log.debug('Authenticating client, %r.', request) if not self.request_validator.authenticate_client(request): log.debug('Invalid client (%r), denying access.', request) raise errors.AccessDeniedError(request=request) # Ensure client is authorized use of this grant type self.validate_grant_type(request) # OPTIONAL. The scope of the access request as described by # Section 3.3. The requested scope MUST NOT include any scope # not originally granted by the resource owner, and if omitted is # treated as equal to the scope originally granted by the # resource owner. if request.scopes: log.debug('Ensuring refresh token %s has access to scopes %r.', request.refresh_token, request.scopes) else: log.debug('Reusing scopes from previous access token.') if not self.request_validator.confirm_scopes(request.refresh_token, request.scopes, request): log.debug('Refresh token %s lack requested scopes, %r.', request.refresh_token, request.scopes) raise errors.InvalidScopeError(state=request.state, request=request) # REQUIRED. The refresh token issued to the client. log.debug('Validating refresh token %s for client %r.', request.refresh_token, request.client) if not self.request_validator.validate_refresh_token( request.refresh_token, request.client, request): log.debug('Invalid refresh token, %s, for client %r.', request.refresh_token, request.client) raise errors.InvalidRequestError(request=request)
def request(self, method, url, data=None, headers=None, **kwargs): """Intercept all requests and add the OAuth 2 token if present.""" if not is_secure_transport(url): raise InsecureTransportError() if self.token: log.debug('Invoking %d protected resource request hooks.', len(self.compliance_hook['protected_request'])) for hook in self.compliance_hook['protected_request']: log.debug('Invoking hook %s.', hook) url, headers, data = hook(url, headers, data) log.debug('Adding token %s to request.', self.token) try: url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) # Attempt to retrieve and save new access token if expired except TokenExpiredError: if self.auto_refresh_url: log.debug( 'Auto refresh is set, attempting to refresh at %s.', self.auto_refresh_url) token = self.refresh_token(self.auto_refresh_url) if self.token_updater: log.debug('Updating token to %s using %s.', token, self.token_updater) self.token_updater(token) url, headers, data = self._client.add_token( url, http_method=method, body=data, headers=headers) else: raise TokenUpdated(token) else: raise log.debug('Requesting url %s using method %s.', url, method) log.debug('Supplying headers %s and data %s', headers, data) log.debug('Passing through key word arguments %s.', kwargs) return super(OAuth2Session, self).request(method, url, headers=headers, data=data, **kwargs)
def fetch_token(self, token_url, code=None, authorization_response=None, body='', auth=None, username=None, password=None, **kwargs): """Generic method for fetching an access token from the token endpoint. If you are using the MobileApplicationClient you will want to use token_from_fragment instead of fetch_token. :param token_url: Token endpoint URL, must use HTTPS. :param code: Authorization code (used by WebApplicationClients). :param authorization_response: Authorization response URL, the callback URL of the request back to you. Used by WebApplicationClients instead of code. :param body: Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body. :param auth: An auth tuple or method as accepted by requests. :param username: Username used by LegacyApplicationClients. :param password: Password used by LegacyApplicationClients. :param kwargs: Extra parameters to include in the token request. :return: A token dict """ if not is_secure_transport(token_url): raise InsecureTransportError() if not code and authorization_response: self._client.parse_request_uri_response(authorization_response, state=self._state) code = self._client.code elif not code and isinstance(self._client, WebApplicationClient): code = self._client.code if not code: raise ValueError('Please supply either code or ' 'authorization_code parameters.') body = self._client.prepare_request_body( code=code, body=body, redirect_uri=self.redirect_uri, username=username, password=password, **kwargs) # (ib-lundgren) All known, to me, token requests use POST. r = self.post(token_url, data=dict(urldecode(body)), headers={'Accept': 'application/json'}, auth=auth) log.debug('Prepared fetch token request body %s', body) log.debug('Request to fetch token completed with status %s.', r.status_code) log.debug('Response headers were %s and content %s.', r.headers, r.text) log.debug('Invoking %d token response hooks.', len(self.compliance_hook['access_token_response'])) for hook in self.compliance_hook['access_token_response']: log.debug('Invoking hook %s.', hook) r = hook(r) self._client.parse_request_body_response(r.text, scope=self.scope) self.token = self._client.token log.debug('Obtained token %s.', self.token) return self.token
def create_token_response(self, request, token_handler): """Return token or error embedded in the URI fragment. If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection URI using the "application/x-www-form-urlencoded" format, per `Appendix B`_: access_token REQUIRED. The access token issued by the authorization server. token_type REQUIRED. The type of the token issued as described in `Section 7.1`_. Value is case insensitive. expires_in RECOMMENDED. The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. scope OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The scope of the access token as described by `Section 3.3`_. state REQUIRED if the "state" parameter was present in the client authorization request. The exact value received from the client. The authorization server MUST NOT issue a refresh token. .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 """ try: # request.scopes is only mandated in post auth and both pre and # post auth use validate_authorization_request if not request.scopes: raise ValueError('Scopes must be set on post auth.') self.validate_token_request(request) # 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. except errors.FatalClientError as e: log.debug('Fatal client error during validation of %r. %r.', request, e) raise # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the fragment component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B: # http://tools.ietf.org/html/rfc6749#appendix-B except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) return { 'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples, fragment=True) }, None, 302 token = token_handler.create_token(request, refresh_token=False) return { 'Location': common.add_params_to_uri(request.redirect_uri, token.items(), fragment=True) }, None, 302
def validate_token_request(self, request): """Check the token request for normal and fatal errors. This method is very similar to validate_authorization_request in the AuthorizationCodeGrant but differ in a few subtle areas. 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. 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. """ # First check for fatal errors # 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. # REQUIRED. The client identifier as described in Section 2.2. # http://tools.ietf.org/html/rfc6749#section-2.2 if not request.client_id: raise errors.MissingClientIdError(state=request.state, request=request) if not self.request_validator.validate_client_id(request.client_id, request): raise errors.InvalidClientIdError(state=request.state, request=request) # OPTIONAL. As described in Section 3.1.2. # http://tools.ietf.org/html/rfc6749#section-3.1.2 if request.redirect_uri is not None: request.using_default_redirect_uri = False log.debug('Using provided redirect_uri %s', request.redirect_uri) if not is_absolute_uri(request.redirect_uri): raise errors.InvalidRedirectURIError(state=request.state, request=request) # The authorization server MUST verify that the redirection URI # to which it will redirect the access token matches a # redirection URI registered by the client as described in # Section 3.1.2. # http://tools.ietf.org/html/rfc6749#section-3.1.2 if not self.request_validator.validate_redirect_uri( request.client_id, request.redirect_uri, request): raise errors.MismatchingRedirectURIError(state=request.state, request=request) else: request.redirect_uri = self.request_validator.get_default_redirect_uri( request.client_id, request) request.using_default_redirect_uri = True log.debug('Using default redirect_uri %s.', request.redirect_uri) if not request.redirect_uri: raise errors.MissingRedirectURIError(state=request.state, request=request) if not is_absolute_uri(request.redirect_uri): raise errors.InvalidRedirectURIError(state=request.state, request=request) # Then check for normal errors. # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the fragment component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B. # http://tools.ietf.org/html/rfc6749#appendix-B # Note that the correct parameters to be added are automatically # populated through the use of specific exceptions. if request.response_type is None: raise errors.InvalidRequestError(state=request.state, description='Missing response_type parameter.', request=request) for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'): if param in request.duplicate_params: raise errors.InvalidRequestError(state=request.state, description='Duplicate %s parameter.' % param, request=request) # REQUIRED. Value MUST be set to "token". if request.response_type != 'token': raise errors.UnsupportedResponseTypeError(state=request.state, request=request) log.debug('Validating use of response_type token for client %r (%r).', request.client_id, request.client) if not self.request_validator.validate_response_type(request.client_id, request.response_type, request.client, request): log.debug('Client %s is not authorized to use response_type %s.', request.client_id, request.response_type) raise errors.UnauthorizedClientError(request=request) # OPTIONAL. The scope of the access request as described by Section 3.3 # http://tools.ietf.org/html/rfc6749#section-3.3 self.validate_scopes(request) return request.scopes, { 'client_id': request.client_id, 'redirect_uri': request.redirect_uri, 'response_type': request.response_type, 'state': request.state, }
def sign(self, uri, http_method='GET', body=None, headers=None, realm=None): __doc__ = Client.sign.__doc__ # normalize request data request = Request(uri, http_method, body, headers, encoding=self.encoding) # sanity check content_type = request.headers.get('Content-Type', None) multipart = content_type and content_type.startswith('multipart/') should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED has_params = request.decoded_body is not None # 3.4.1.3.1. Parameter Sources # [Parameters are collected from the HTTP request entity-body, but only # if [...]: # * The entity-body is single-part. if multipart and has_params: raise ValueError("Headers indicate a multipart body but body contains parameters.") # * The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by # [W3C.REC-html40-19980424]. elif should_have_params and not has_params: raise ValueError("Headers indicate a formencoded body but body was not decodable.") # * The HTTP request entity-header includes the "Content-Type" # header field set to "application/x-www-form-urlencoded". elif not should_have_params and has_params: raise ValueError("Body contains parameters but Content-Type header was not set.") # 3.5.2. Form-Encoded Body # Protocol parameters can be transmitted in the HTTP request entity- # body, but only if the following REQUIRED conditions are met: # o The entity-body is single-part. # o The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by # [W3C.REC-html40-19980424]. # o The HTTP request entity-header includes the "Content-Type" header # field set to "application/x-www-form-urlencoded". elif self.signature_type == SIGNATURE_TYPE_BODY and not ( should_have_params and has_params and not multipart): raise ValueError('Body signatures may only be used with form-urlencoded content') # We amend http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 # with the clause that parameters from body should only be included # in non GET or HEAD requests. Extracting the request body parameters # and including them in the signature base string would give semantic # meaning to the body, which it should not have according to the # HTTP 1.1 spec. elif http_method.upper() in ('GET', 'HEAD') and has_params: raise ValueError('GET/HEAD requests should not include body.') # generate the basic OAuth parameters request.oauth_params = self.get_oauth_params(request) # generate the signature request.oauth_params.append(('oauth_signature', self.get_oauth_signature(request))) # render the signed request and return it uri, headers, body = self._render(request, formencode=True, realm=(realm or self.realm)) if self.decoding: log.debug('Encoding URI, headers and body to %s.', self.decoding) uri = uri.encode(self.decoding) body = body.encode(self.decoding) if body else body new_headers = {} for k, v in headers.items(): new_headers[k.encode(self.decoding)] = v.encode(self.decoding) headers = new_headers return uri, headers, body
def request(self, method, url, data=None, headers=None, **kwargs): """Intercept all requests and add the OAuth 2 token if present.""" if 'DEBUG' not in os.environ and not url.startswith('https://'): raise InsecureTransportError() if self.token: log.debug('Adding token %s to request.', self.token) try: url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) # Attempt to retrieve and save new access token if expired except TokenExpiredError: if self.auto_refresh_url: log.debug( 'Auto refresh is set, attempting to refresh at %s.', self.auto_refresh_url) token = self.refresh_token(self.auto_refresh_url) if self.token_updater: log.debug('Updating token to %s using %s.', token, self.token_updater) self.token_updater(token) url, headers, data = self._client.add_token( url, http_method=method, body=data, headers=headers) else: raise TokenUpdated(token) else: raise log.debug('Requesting url %s using method %s.', url, method) log.debug('Supplying headers %s and data %s', headers, data) log.debug('Passing through key word arguments %s.', kwargs) return super(OAuth2Session, self).request(method, url, headers=headers, data=data, **kwargs)
def request(self, method, url, data=None, headers=None, **kwargs): """Intercept all requests and add the OAuth 2 token if present.""" if not is_secure_transport(url): raise InsecureTransportError() if self.token: log.debug('Adding token %s to request.', self.token) try: url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) # Attempt to retrieve and save new access token if expired except TokenExpiredError: if self.auto_refresh_url: log.debug('Auto refresh is set, attempting to refresh at %s.', self.auto_refresh_url) token = self.refresh_token(self.auto_refresh_url) if self.token_updater: log.debug('Updating token to %s using %s.', token, self.token_updater) self.token_updater(token) url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) else: raise TokenUpdated(token) else: raise log.debug('Requesting url %s using method %s.', url, method) log.debug('Supplying headers %s and data %s', headers, data) log.debug('Passing through key word arguments %s.', kwargs) return super(OAuth2Session, self).request(method, url, headers=headers, data=data, **kwargs)
def create_authorization_response(self, request, token_handler): """ The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the "application/x-www-form-urlencoded" format, per `Appendix B`_: response_type REQUIRED. Value MUST be set to "code". client_id REQUIRED. The client identifier as described in `Section 2.2`_. redirect_uri OPTIONAL. As described in `Section 3.1.2`_. scope OPTIONAL. The scope of the access request as described by `Section 3.3`_. state RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent. :param request: oauthlib.commong.Request :param token_handler: A token handler instace, for example of type oauthlib.oauth2.BearerToken. :returns: headers, body, status :raises: FatalClientError on invalid redirect URI or client id. ValueError if scopes are not set on the request object. A few examples:: >>> from your_validator import your_validator >>> request = Request('https://example.com/authorize?client_id=valid' ... '&redirect_uri=http%3A%2F%2Fclient.com%2F') >>> from oauthlib.common import Request >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken >>> token = BearerToken(your_validator) >>> grant = AuthorizationCodeGrant(your_validator) >>> grant.create_authorization_response(request, token) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response raise ValueError('Scopes must be set on post auth.') ValueError: Scopes must be set on post auth. >>> request.scopes = ['authorized', 'in', 'some', 'form'] >>> grant.create_authorization_response(request, token) (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400) >>> request = Request('https://example.com/authorize?client_id=valid' ... '&redirect_uri=http%3A%2F%2Fclient.com%2F' ... '&response_type=code') >>> request.scopes = ['authorized', 'in', 'some', 'form'] >>> grant.create_authorization_response(request, token) (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200) >>> # If the client id or redirect uri fails validation >>> grant.create_authorization_response(request, token) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response >>> grant.create_authorization_response(request, token) File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request oauthlib.oauth2.rfc6749.errors.InvalidClientIdError .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 """ try: # request.scopes is only mandated in post auth and both pre and # post auth use validate_authorization_request if not request.scopes: raise ValueError('Scopes must be set on post auth.') self.validate_authorization_request(request) log.debug('Pre resource owner authorization validation ok for %r.', request) # 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. except errors.FatalClientError as e: log.debug('Fatal client error during validation of %r. %r.', request, e) raise # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the query component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B: # http://tools.ietf.org/html/rfc6749#appendix-B except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) request.redirect_uri = request.redirect_uri or self.error_uri return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302 grant = self.create_authorization_code(request) log.debug('Saving grant %r for %r.', grant, request) self.request_validator.save_authorization_code(request.client_id, grant, request) return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
def create_token_response(self, request, token_handler, require_authentication=True): """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;charset=UTF-8', 'Cache-Control': 'no-store', 'Pragma': 'no-cache', } try: if require_authentication: 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.') else: log.debug('Client authentication disabled, %r.', 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, refresh_token=True) 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_grant_type(self, request): if not self.request_validator.validate_grant_type(request.client_id, request.grant_type, request.client, request): log.debug('Unauthorized from %r (%r) access to grant type %s.', request.client_id, request.client, request.grant_type) raise errors.UnauthorizedClientError(request=request)
def create_token_response(self, request, token_handler): """Return token or error embedded in the URI fragment. If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection URI using the "application/x-www-form-urlencoded" format, per `Appendix B`_: access_token REQUIRED. The access token issued by the authorization server. token_type REQUIRED. The type of the token issued as described in `Section 7.1`_. Value is case insensitive. expires_in RECOMMENDED. The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. scope OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The scope of the access token as described by `Section 3.3`_. state REQUIRED if the "state" parameter was present in the client authorization request. The exact value received from the client. The authorization server MUST NOT issue a refresh token. .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 """ try: # request.scopes is only mandated in post auth and both pre and # post auth use validate_authorization_request if not request.scopes: raise ValueError('Scopes must be set on post auth.') self.validate_token_request(request) # 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. except errors.FatalClientError as e: log.debug('Fatal client error during validation of %r. %r.', request, e) raise # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the fragment component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B: # http://tools.ietf.org/html/rfc6749#appendix-B except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples, fragment=True)}, None, 302 token = token_handler.create_token(request, refresh_token=False) return {'Location': common.add_params_to_uri(request.redirect_uri, token.items(), fragment=True)}, None, 302
def validate_authorization_request(self, request): """Check the authorization request for normal and fatal errors. 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. 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. """ # First check for fatal errors # 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. # REQUIRED. The client identifier as described in Section 2.2. # http://tools.ietf.org/html/rfc6749#section-2.2 if not request.client_id: raise errors.MissingClientIdError(state=request.state, request=request) if not self.request_validator.validate_client_id(request.client_id, request): raise errors.InvalidClientIdError(state=request.state, request=request) # OPTIONAL. As described in Section 3.1.2. # http://tools.ietf.org/html/rfc6749#section-3.1.2 log.debug('Validating redirection uri %s for client %s.', request.redirect_uri, request.client_id) if request.redirect_uri is not None: request.using_default_redirect_uri = False log.debug('Using provided redirect_uri %s', request.redirect_uri) if not is_absolute_uri(request.redirect_uri): raise errors.InvalidRedirectURIError(state=request.state, request=request) if not self.request_validator.validate_redirect_uri( request.client_id, request.redirect_uri, request): raise errors.MismatchingRedirectURIError(state=request.state, request=request) else: request.redirect_uri = self.request_validator.get_default_redirect_uri( request.client_id, request) request.using_default_redirect_uri = True log.debug('Using default redirect_uri %s.', request.redirect_uri) if not request.redirect_uri: raise errors.MissingRedirectURIError(state=request.state, request=request) # Then check for normal errors. # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the query component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B. # http://tools.ietf.org/html/rfc6749#appendix-B # Note that the correct parameters to be added are automatically # populated through the use of specific exceptions. if request.response_type is None: raise errors.InvalidRequestError(state=request.state, description='Missing response_type parameter.', request=request) for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'): if param in request.duplicate_params: raise errors.InvalidRequestError(state=request.state, description='Duplicate %s parameter.' % param, request=request) if not self.request_validator.validate_response_type(request.client_id, request.response_type, request.client, request): log.debug('Client %s is not authorized to use response_type %s.', request.client_id, request.response_type) raise errors.UnauthorizedClientError(request=request) # REQUIRED. Value MUST be set to "code". if request.response_type != 'code': raise errors.UnsupportedResponseTypeError(state=request.state, request=request) # OPTIONAL. The scope of the access request as described by Section 3.3 # http://tools.ietf.org/html/rfc6749#section-3.3 self.validate_scopes(request) return request.scopes, { 'client_id': request.client_id, 'redirect_uri': request.redirect_uri, 'response_type': request.response_type, 'state': request.state, }
def fetch_token(self, token_url, code=None, authorization_response=None, body='', auth=None, username=None, password=None, method='POST', verify=True, **kwargs): """Generic method for fetching an access token from the token endpoint. If you are using the MobileApplicationClient you will want to use token_from_fragment instead of fetch_token. :param token_url: Token endpoint URL, must use HTTPS. :param code: Authorization code (used by WebApplicationClients). :param authorization_response: Authorization response URL, the callback URL of the request back to you. Used by WebApplicationClients instead of code. :param body: Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body. :param auth: An auth tuple or method as accepted by requests. :param username: Username used by LegacyApplicationClients. :param password: Password used by LegacyApplicationClients. :param method: The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed. :param verify: Verify SSL certificate. :param kwargs: Extra parameters to include in the token request. :return: A token dict """ if not is_secure_transport(token_url): raise InsecureTransportError() if not code and authorization_response: self._client.parse_request_uri_response(authorization_response, state=self._state) code = self._client.code elif not code and isinstance(self._client, WebApplicationClient): code = self._client.code if not code: raise ValueError('Please supply either code or ' 'authorization_code parameters.') body = self._client.prepare_request_body(code=code, body=body, redirect_uri=self.redirect_uri, username=username, password=password, **kwargs) if method.upper() == 'POST': r = self.post(token_url, data=dict(urldecode(body)), headers={'Accept': 'application/json'}, auth=auth, verify=verify) log.debug('Prepared fetch token request body %s', body) elif method.upper() == 'GET': # if method is not 'POST', switch body to querystring and GET r = self.get(token_url, params=dict(urldecode(body)), headers={'Accept': 'application/json'}, auth=auth, verify=verify) log.debug('Prepared fetch token request querystring %s', body) else: raise ValueError('The method kwarg must be POST or GET.') log.debug('Request to fetch token completed with status %s.', r.status_code) log.debug('Response headers were %s and content %s.', r.headers, r.text) log.debug('Invoking %d token response hooks.', len(self.compliance_hook['access_token_response'])) for hook in self.compliance_hook['access_token_response']: log.debug('Invoking hook %s.', hook) r = hook(r) self._client.parse_request_body_response(r.text, scope=self.scope) self.token = self._client.token log.debug('Obtained token %s.', self.token) return self.token
def request(self, method, url, data=None, headers=None, **kwargs): """Intercept all requests and add the OAuth 2 token if present.""" if not is_secure_transport(url): raise InsecureTransportError() if self.token: log.debug("Invoking %d protected resource request hooks.", len(self.compliance_hook["protected_request"])) for hook in self.compliance_hook["protected_request"]: log.debug("Invoking hook %s.", hook) url, headers, data = hook(url, headers, data) log.debug("Adding token %s to request.", self.token) try: url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) # Attempt to retrieve and save new access token if expired except TokenExpiredError: if self.auto_refresh_url: log.debug("Auto refresh is set, attempting to refresh at %s.", self.auto_refresh_url) token = self.refresh_token(self.auto_refresh_url) if self.token_updater: log.debug("Updating token to %s using %s.", token, self.token_updater) self.token_updater(token) url, headers, data = self._client.add_token(url, http_method=method, body=data, headers=headers) else: raise TokenUpdated(token) else: raise log.debug("Requesting url %s using method %s.", url, method) log.debug("Supplying headers %s and data %s", headers, data) log.debug("Passing through key word arguments %s.", kwargs) return super(OAuth2Session, self).request(method, url, headers=headers, data=data, **kwargs)
def create_authorization_response(self, request, token_handler): """ The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the "application/x-www-form-urlencoded" format, per `Appendix B`_: response_type REQUIRED. Value MUST be set to "code". client_id REQUIRED. The client identifier as described in `Section 2.2`_. redirect_uri OPTIONAL. As described in `Section 3.1.2`_. scope OPTIONAL. The scope of the access request as described by `Section 3.3`_. state RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent. :param request: oauthlib.commong.Request :param token_handler: A token handler instace, for example of type oauthlib.oauth2.BearerToken. :returns: uri, headers, body, status :raises: FatalClientError on invalid redirect URI or client id. ValueError if scopes are not set on the request object. A few examples:: >>> from your_validator import your_validator >>> request = Request('https://example.com/authorize?client_id=valid' ... '&redirect_uri=http%3A%2F%2Fclient.com%2F') >>> from oauthlib.common import Request >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken >>> token = BearerToken(your_validator) >>> grant = AuthorizationCodeGrant(your_validator) >>> grant.create_authorization_response(request, token) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response raise ValueError('Scopes must be set on post auth.') ValueError: Scopes must be set on post auth. >>> request.scopes = ['authorized', 'in', 'some', 'form'] >>> grant.create_authorization_response(request, token) (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400) >>> request = Request('https://example.com/authorize?client_id=valid' ... '&redirect_uri=http%3A%2F%2Fclient.com%2F' ... '&response_type=code') >>> request.scopes = ['authorized', 'in', 'some', 'form'] >>> grant.create_authorization_response(request, token) (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200) >>> # If the client id or redirect uri fails validation >>> grant.create_authorization_response(request, token) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response >>> grant.create_authorization_response(request, token) File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request oauthlib.oauth2.rfc6749.errors.InvalidClientIdError .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 """ try: # request.scopes is only mandated in post auth and both pre and # post auth use validate_authorization_request if not request.scopes: raise ValueError('Scopes must be set on post auth.') self.validate_authorization_request(request) log.debug('Pre resource owner authorization validation ok for %r.', request) # 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. except errors.FatalClientError as e: log.debug('Fatal client error during validation of %r. %r.', request, e) raise # If the resource owner denies the access request or if the request # fails for reasons other than a missing or invalid redirection URI, # the authorization server informs the client by adding the following # parameters to the query component of the redirection URI using the # "application/x-www-form-urlencoded" format, per Appendix B: # http://tools.ietf.org/html/rfc6749#appendix-B except errors.OAuth2Error as e: log.debug('Client error during validation of %r. %r.', request, e) request.redirect_uri = request.redirect_uri or self.error_uri return common.add_params_to_uri(request.redirect_uri, e.twotuples), None, None, e.status_code grant = self.create_authorization_code(request) log.debug('Saving grant %r for %r.', grant, request) self.request_validator.save_authorization_code(request.client_id, grant, request) return common.add_params_to_uri(request.redirect_uri, grant.items()), None, None, 302