import logging from .base import BaseGrant, TokenEndpointMixin from ..errors import UnauthorizedClientError log = logging.getLogger(__name__) class ClientCredentialsGrant(BaseGrant, TokenEndpointMixin): """The client can request an access token using only its client credentials (or other supported means of authentication) when the client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server. The client credentials grant type MUST only be used by confidential clients:: +---------+ +---------------+ | | | | | |>--(A)- Client Authentication --->| Authorization | | Client | | Server | | |<--(B)---- Access Token ---------<| | | | | | +---------+ +---------------+ https://tools.ietf.org/html/rfc6749#section-4.4 """ GRANT_TYPE = 'client_credentials' def validate_token_request(self): """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 "client_credentials". scope OPTIONAL. The scope of the access request as described by Section 3.3. The client MUST authenticate with the authorization server as described in Section 3.2.1. For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only): .. code-block:: http POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=client_credentials The authorization server MUST authenticate the client. """ # ignore validate for grant_type, since it is validated by # check_token_endpoint client = self.authenticate_token_endpoint_client() log.debug('Validate token request of %r', client) if not client.check_grant_type(self.GRANT_TYPE): raise UnauthorizedClientError() self.request.client = client self.validate_requested_scope() def create_token_response(self): """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. An example successful response: .. code-block:: http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "example_parameter":"example_value" } :returns: (status_code, body, headers) """ client = self.request.client token = self.generate_token( client, self.GRANT_TYPE, scope=client.get_allowed_scope(self.request.scope), include_refresh_token=False, ) log.debug('Issue token %r to %r', token, client) self.save_token(token) self.execute_hook('process_token', self, token=token) return 200, token, self.TOKEN_RESPONSE_HEADER