"""OAuth errors""" from urllib.parse import quote class OAuth2Error(Exception): """Base class for all OAuth2 Errors""" error: str description: str def create_dict(self): """Return error as dict for JSON Rendering""" return { "error": self.error, "error_description": self.description, } def __repr__(self) -> str: return self.error class RedirectUriError(OAuth2Error): """The request fails due to a missing, invalid, or mismatching redirection URI (redirect_uri).""" error = "Redirect URI Error" description = ( "The request fails due to a missing, invalid, or mismatching" " redirection URI (redirect_uri)." ) class ClientIdError(OAuth2Error): """The client identifier (client_id) is missing or invalid.""" error = "Client ID Error" description = "The client identifier (client_id) is missing or invalid." class UserAuthError(OAuth2Error): """ Specific to the Resource Owner Password Credentials flow when the Resource Owners credentials are not valid. """ error = "access_denied" description = "The resource owner or authorization server denied the request." class TokenIntrospectionError(OAuth2Error): """ Specific to the introspection endpoint. This error will be converted to an "active: false" response, as per the spec. See https://tools.ietf.org/html/rfc7662 """ class AuthorizeError(OAuth2Error): """General Authorization Errors""" _errors = { # OAuth2 errors. # https://tools.ietf.org/html/rfc6749#section-4.1.2.1 "invalid_request": "The request is otherwise malformed", "unauthorized_client": "The client is not authorized to request an " "authorization code using this method", "access_denied": "The resource owner or authorization server denied " "the request", "unsupported_response_type": "The authorization server does not " "support obtaining an authorization code " "using this method", "invalid_scope": "The requested scope is invalid, unknown, or " "malformed", "server_error": "The authorization server encountered an error", "temporarily_unavailable": "The authorization server is currently " "unable to handle the request due to a " "temporary overloading or maintenance of " "the server", # OpenID errors. # http://openid.net/specs/openid-connect-core-1_0.html#AuthError "interaction_required": "The Authorization Server requires End-User " "interaction of some form to proceed", "login_required": "The Authorization Server requires End-User " "authentication", "account_selection_required": "The End-User is required to select a " "session at the Authorization Server", "consent_required": "The Authorization Server requires End-User" "consent", "invalid_request_uri": "The request_uri in the Authorization Request " "returns an error or contains invalid data", "invalid_request_object": "The request parameter contains an invalid " "Request Object", "request_not_supported": "The provider does not support use of the " "request parameter", "request_uri_not_supported": "The provider does not support use of the " "request_uri parameter", "registration_not_supported": "The provider does not support use of " "the registration parameter", } def __init__(self, redirect_uri, error, grant_type): super().__init__() self.error = error self.description = self._errors[error] self.redirect_uri = redirect_uri self.grant_type = grant_type def create_uri(self, redirect_uri: str, state: str) -> str: """Get a redirect URI with the error message""" description = quote(str(self.description)) # See: # http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthError hash_or_question = "#" if self.grant_type == "implicit" else "?" uri = "{0}{1}error={2}&error_description={3}".format( redirect_uri, hash_or_question, self.error, description ) # Add state if present. uri = uri + ("&state={0}".format(state) if state else "") return uri class TokenError(OAuth2Error): """ OAuth2 token endpoint errors. https://tools.ietf.org/html/rfc6749#section-5.2 """ _errors = { "invalid_request": "The request is otherwise malformed", "invalid_client": "Client authentication failed (e.g., unknown client, " "no client authentication included, or unsupported " "authentication method)", "invalid_grant": "The provided authorization grant or refresh token is " "invalid, expired, revoked, does not match the " "redirection URI used in the authorization request, " "or was issued to another client", "unauthorized_client": "The authenticated client is not authorized to " "use this authorization grant type", "unsupported_grant_type": "The authorization grant type is not " "supported by the authorization server", "invalid_scope": "The requested scope is invalid, unknown, malformed, " "or exceeds the scope granted by the resource owner", } def __init__(self, error): super().__init__() self.error = error self.description = self._errors[error] class BearerTokenError(OAuth2Error): """ OAuth2 errors. https://tools.ietf.org/html/rfc6750#section-3.1 """ _errors = { "invalid_request": ("The request is otherwise malformed", 400), "invalid_token": ( "The access token provided is expired, revoked, malformed, " "or invalid for other reasons", 401, ), "insufficient_scope": ( "The request requires higher privileges than provided by " "the access token", 403, ), } def __init__(self, code): super().__init__() self.code = code error_tuple = self._errors.get(code, ("", "")) self.description = error_tuple[0] self.status = error_tuple[1]