diff options
author | Ib Lundgren <ib.lundgren@gmail.com> | 2012-12-21 23:54:39 +0100 |
---|---|---|
committer | Ib Lundgren <ib.lundgren@gmail.com> | 2012-12-21 23:54:39 +0100 |
commit | 43124ab671f73e33a6a5f608dcb4afd997619a8f (patch) | |
tree | a8e969e32f0684882b872a863d6aa8ff00b3a38a /oauthlib/oauth2 | |
parent | ae2c5b98106720d4b78057780bdcbb167219a1a7 (diff) | |
download | oauthlib-43124ab671f73e33a6a5f608dcb4afd997619a8f.tar.gz |
Refactoring towards spec compliance, step 1.
Diffstat (limited to 'oauthlib/oauth2')
-rw-r--r-- | oauthlib/oauth2/draft25/errors.py | 18 | ||||
-rw-r--r-- | oauthlib/oauth2/draft25/grant_types.py | 306 |
2 files changed, 251 insertions, 73 deletions
diff --git a/oauthlib/oauth2/draft25/errors.py b/oauthlib/oauth2/draft25/errors.py index 5f06412..57796ed 100644 --- a/oauthlib/oauth2/draft25/errors.py +++ b/oauthlib/oauth2/draft25/errors.py @@ -56,11 +56,23 @@ class FatalClientError(OAuth2Error): pass -class RedirectURIError(FatalClientError): +class InvalidRedirectURIError(FatalClientError): error = 'invalid_redirect_uri' -class ClientIDError(FatalClientError): +class MissingRedirectURIError(FatalClientError): + error = 'missing_redirect_uri' + + +class MismatchingRedirectURIError(FatalClientError): + error = 'mismatching_redirect_uri' + + +class MissingClientIdError(FatalClientError): + error = 'invalid_client_id' + + +class InvalidClientIdError(FatalClientError): error = 'invalid_client_id' @@ -76,7 +88,7 @@ class UnauthorizedClientError(OAuth2Error): """The client is not authorized to request an authorization code using this method. """ -error = 'unauthorized_client' + error = 'unauthorized_client' class AccessDeniedError(OAuth2Error): diff --git a/oauthlib/oauth2/draft25/grant_types.py b/oauthlib/oauth2/draft25/grant_types.py index c6f4470..d92a88c 100644 --- a/oauthlib/oauth2/draft25/grant_types.py +++ b/oauthlib/oauth2/draft25/grant_types.py @@ -11,68 +11,37 @@ from oauthlib.uri_validate import is_absolute_uri class RequestValidator(object): - @property - def response_types(self): - return ('code', 'token') - - def validate_request(self, request, response_types=None): - request.state = getattr(request, 'state', None) - response_types = response_types or self.response_types or [] - - if not request.client_id: - raise errors.InvalidRequestError(state=request.state, - description='Missing client_id parameter.') - - if not request.response_type: - raise errors.InvalidRequestError(state=request.state, - description='Missing response_type parameter.') - - if not self.validate_client(request.client_id): - raise errors.UnauthorizedClientError(state=request.state) - - if not request.response_type in response_types: - raise errors.UnsupportedResponseTypeError(state=request.state) - - self.validate_request_scopes(request) + def validate_client_id(self, client_id, *args, **kwargs): + raise NotImplementedError('Subclasses must implement this method.') - if getattr(request, 'redirect_uri', None): - if not is_absolute_uri(request.redirect_uri): - raise errors.InvalidRequestError(state=request.state, - description='Non absolute redirect URI. See RFC3986') + def validate_client(self, client_id, grant_type, client, *args, **kwargs): + raise NotImplementedError('Subclasses must implement this method.') - if not self.validate_redirect_uri(request.client_id, request.redirect_uri): - raise errors.AccessDeniedError(state=request.state) - else: - request.redirect_uri = self.get_default_redirect_uri(request.client_id) - if not request.redirect_uri: - raise errors.AccessDeniedError(state=request.state) + def validate_code(self, client_id, code, client, *args, **kwargs): + raise NotImplementedError('Subclasses must implement this method.') - return True + def validate_refresh_token(self, refresh_token, client, *args, **kwargs): + raise NotImplementedError('Subclasses must implement this method.') - def validate_request_scopes(self, request): - request.state = getattr(request, 'state', None) - if request.scopes: - if not self.validate_scopes(request.client_id, request.scopes): - raise errors.InvalidScopeError(state=request.state) - else: - request.scopes = self.get_default_scopes(request.client_id) + def authenticate_client(self, request, *args, **kwargs): + raise NotImplementedError('Subclasses must implement this method.') - def validate_client(self, client, *args, **kwargs): + def validate_scopes(self, client_id, scopes, client): raise NotImplementedError('Subclasses must implement this method.') - def validate_scopes(self, client, scopes): + def confirm_scopes(self, refresh_token, scopes): raise NotImplementedError('Subclasses must implement this method.') def validate_user(self, username, password, client=None): raise NotImplementedError('Subclasses must implement this method.') - def validate_redirect_uri(self, client, redirect_uri): + def validate_redirect_uri(self, client_id, redirect_uri): raise NotImplementedError('Subclasses must implement this method.') - def get_default_redirect_uri(self, client): + def confirm_redirect_uri(self, client_id, code, redirect_uri, client): raise NotImplementedError('Subclasses must implement this method.') - def get_default_scopes(self, client): + def get_default_redirect_uri(self, client_id): raise NotImplementedError('Subclasses must implement this method.') @@ -111,7 +80,7 @@ class AuthorizationCodeGrant(GrantTypeBase): def create_authorization_response(self, request, token_handler): try: - self.request_validator.validate_request(request) + self.validate_authorization_request(request) # If the request fails due to a missing, invalid, or mismatching # redirection URI, or if the client identifier is missing or invalid, @@ -151,25 +120,112 @@ class AuthorizationCodeGrant(GrantTypeBase): return None, None, json.dumps(token_handler.create_token(request, refresh_token=True)), 200 + 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) + + if not self.request_validator.validate_client_id(request.client_id): + raise errors.InvalidClientIdError(state=request.state) + + # 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: + if not is_absolute_uri(request.redirect_uri): + raise errors.InvalidRedirectURIError(state=request.state) + + if not self.request_validator.validate_redirect_uri( + request.client_id, request.redirect_uri): + raise errors.MismatchingRedirectURIError(state=request.state) + else: + request.redirect_uri = self.request_validator.get_default_redirect_uri(request.client_id) + if not request.redirect_uri: + raise errors.MissingRedirectURIError(state=request.state) + + # 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.') + + # REQUIRED. Value MUST be set to "code". + if request.response_type != 'code': + raise errors.UnsupportedResponseTypeError(state=request.state) + + # OPTIONAL. The scope of the access request as described by Section 3.3 + # http://tools.ietf.org/html/rfc6749#section-3.3 + if not self.request_validator.validate_scopes(request.client_id, + request.scopes): + raise errors.InvalidScopeError(state=request.state) + def validate_token_request(self, request): - if getattr(request, 'grant_type', '') != 'authorization_code': + # REQUIRED. Value MUST be set to "authorization_code". + if request.grant_type != 'authorization_code': raise errors.UnsupportedGrantTypeError() - if not getattr(request, 'code', None): + if request.code is None: raise errors.InvalidRequestError( description='Missing code parameter.') - # TODO: document diff client & client_id, former is authenticated - # outside spec, i.e. http basic - if (not hasattr(request, 'client') or - not self.request_validator.validate_client(request.client, request.grant_type)): + # 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): + raise errors.AccessDeniedError() + + # 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.validate_client(request.client_id, + request.grant_type, request.client): raise errors.UnauthorizedClientError() - if not self.request_validator.validate_code(request.client, request.code): + # REQUIRED. The authorization code received from the + # authorization server. + if not self.request_validator.validate_code(request.client_id, + request.code, request.client): raise errors.InvalidGrantError() - # TODO: validate scopes + # 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): + raise errors.AccessDeniedError() class ImplicitGrant(GrantTypeBase): @@ -280,7 +336,7 @@ class ImplicitGrant(GrantTypeBase): .. _`Section 7.2`: http://tools.ietf.org/html/rfc6749#section-7.2 """ try: - self.request_validator.validate_request(request) + 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, @@ -305,6 +361,83 @@ class ImplicitGrant(GrantTypeBase): fragment=True), None, None, 200 + 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) + + if not self.request_validator.validate_client_id(request.client_id): + raise errors.InvalidClientIdError(state=request.state) + + # 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: + if not is_absolute_uri(request.redirect_uri): + raise errors.InvalidRedirectURIError(state=request.state) + + # 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): + raise errors.MismatchingRedirectURIError(state=request.state) + else: + request.redirect_uri = self.request_validator.get_default_redirect_uri(request.client_id) + if not request.redirect_uri: + raise errors.MissingRedirectURIError(state=request.state) + + # 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.') + + # REQUIRED. Value MUST be set to "token". + if request.response_type != 'token': + raise errors.UnsupportedResponseTypeError(state=request.state) + + # OPTIONAL. The scope of the access request as described by Section 3.3 + # http://tools.ietf.org/html/rfc6749#section-3.3 + if not self.request_validator.validate_scopes(request): + raise errors.InvalidScopeError(state=request.state) + + class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): """`Resource Owner Password Credentials Grant`_ @@ -476,29 +609,49 @@ class ClientCredentialsGrant(GrantTypeBase): class RefreshTokenGrant(GrantTypeBase): + """`Refresh token grant`_ + + .. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6 + """ @property def scope(self): return ('default',) - def __init__(self, request_validator=None): + def __init__(self, request_validator=None, issue_new_refresh_tokens=True): self.request_validator = request_validator or RequestValidator() def create_token_response(self, request, token_handler): - """ - Validate the refresh token grant and the actual refresh token. + """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. - The client MUST use the refresh token provided on issue of the - access token. + .. _`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: self.validate_token_request(request) except errors.OAuth2Error as e: return None, {}, e.json, 400 - return None, {}, json.dumps(token_handler.create_token(request, refresh_token=True)), 200 + + token = token_handler.create_token(request, + refresh_token=self.issue_new_refresh_tokens) + return None, {}, json.dumps(token), 200 def validate_token_request(self, request): + # REQUIRED. Value MUST be set to "refresh_token". if request.grant_type != 'refresh_token': raise errors.UnsupportedGrantTypeError() @@ -506,13 +659,26 @@ class RefreshTokenGrant(GrantTypeBase): raise errors.InvalidRequestError( description='Missing refresh token parameter.') - # TODO: document diff client & client_id, former is authenticated - # outside spec, i.e. http basic - if not self.request_validator.validate_client( - request.client, request.grant_type): - raise errors.UnauthorizedClientError() - - # validate_refresh_token must be provided by the provided request_validator. + # 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 + if not self.request_validator.authenticate_client(request): + raise errors.AccessDeniedError() + + # 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 not self.request_validator.confirm_scopes(request.refresh_token, + request.scopes): + raise errors.InvalidScopeError(state=request.state) + + # REQUIRED. The refresh token issued to the client. if not self.request_validator.validate_refresh_token( - request.client, request.refresh_token): + request.refresh_token, request.client): raise errors.InvalidRequestError() |