1"""
2oauthlib.oauth2.rfc6749.errors
3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5Error used both by OAuth 2 clients and providers to represent the spec
6defined error responses for all four core grant types.
7"""
8import json
9
10from oauthlib.common import add_params_to_uri, urlencode
11
12
13class OAuth2Error(Exception):
14    error = None
15    status_code = 400
16    description = ''
17
18    def __init__(self, description=None, uri=None, state=None,
19                 status_code=None, request=None):
20        """
21        :param description: A human-readable ASCII [USASCII] text providing
22                            additional information, used to assist the client
23                            developer in understanding the error that occurred.
24                            Values for the "error_description" parameter
25                            MUST NOT include characters outside the set
26                            x20-21 / x23-5B / x5D-7E.
27
28        :param uri: A URI identifying a human-readable web page with information
29                    about the error, used to provide the client developer with
30                    additional information about the error.  Values for the
31                    "error_uri" parameter MUST conform to the URI- Reference
32                    syntax, and thus MUST NOT include characters outside the set
33                    x21 / x23-5B / x5D-7E.
34
35        :param state: A CSRF protection value received from the client.
36
37        :param status_code:
38
39        :param request: OAuthlib request.
40        :type request: oauthlib.common.Request
41        """
42        if description is not None:
43            self.description = description
44
45        message = '({}) {}'.format(self.error, self.description)
46        if request:
47            message += ' ' + repr(request)
48        super().__init__(message)
49
50        self.uri = uri
51        self.state = state
52
53        if status_code:
54            self.status_code = status_code
55
56        if request:
57            self.redirect_uri = request.redirect_uri
58            self.client_id = request.client_id
59            self.scopes = request.scopes
60            self.response_type = request.response_type
61            self.response_mode = request.response_mode
62            self.grant_type = request.grant_type
63            if not state:
64                self.state = request.state
65        else:
66            self.redirect_uri = None
67            self.client_id = None
68            self.scopes = None
69            self.response_type = None
70            self.response_mode = None
71            self.grant_type = None
72
73    def in_uri(self, uri):
74        fragment = self.response_mode == "fragment"
75        return add_params_to_uri(uri, self.twotuples, fragment)
76
77    @property
78    def twotuples(self):
79        error = [('error', self.error)]
80        if self.description:
81            error.append(('error_description', self.description))
82        if self.uri:
83            error.append(('error_uri', self.uri))
84        if self.state:
85            error.append(('state', self.state))
86        return error
87
88    @property
89    def urlencoded(self):
90        return urlencode(self.twotuples)
91
92    @property
93    def json(self):
94        return json.dumps(dict(self.twotuples))
95
96    @property
97    def headers(self):
98        if self.status_code == 401:
99            """
100            https://tools.ietf.org/html/rfc6750#section-3
101
102            All challenges defined by this specification MUST use the auth-scheme
103            value "Bearer".  This scheme MUST be followed by one or more
104            auth-param values.
105            """
106            authvalues = [
107                "Bearer",
108                'error="{}"'.format(self.error)
109            ]
110            if self.description:
111                authvalues.append('error_description="{}"'.format(self.description))
112            if self.uri:
113                authvalues.append('error_uri="{}"'.format(self.uri))
114            return {"WWW-Authenticate": ", ".join(authvalues)}
115        return {}
116
117
118class TokenExpiredError(OAuth2Error):
119    error = 'token_expired'
120
121
122class InsecureTransportError(OAuth2Error):
123    error = 'insecure_transport'
124    description = 'OAuth 2 MUST utilize https.'
125
126
127class MismatchingStateError(OAuth2Error):
128    error = 'mismatching_state'
129    description = 'CSRF Warning! State not equal in request and response.'
130
131
132class MissingCodeError(OAuth2Error):
133    error = 'missing_code'
134
135
136class MissingTokenError(OAuth2Error):
137    error = 'missing_token'
138
139
140class MissingTokenTypeError(OAuth2Error):
141    error = 'missing_token_type'
142
143
144class FatalClientError(OAuth2Error):
145    """
146    Errors during authorization where user should not be redirected back.
147
148    If the request fails due to a missing, invalid, or mismatching
149    redirection URI, or if the client identifier is missing or invalid,
150    the authorization server SHOULD inform the resource owner of the
151    error and MUST NOT automatically redirect the user-agent to the
152    invalid redirection URI.
153
154    Instead the user should be informed of the error by the provider itself.
155    """
156    pass
157
158
159class InvalidRequestFatalError(FatalClientError):
160    """
161    For fatal errors, the request is missing a required parameter, includes
162    an invalid parameter value, includes a parameter more than once, or is
163    otherwise malformed.
164    """
165    error = 'invalid_request'
166
167
168class InvalidRedirectURIError(InvalidRequestFatalError):
169    description = 'Invalid redirect URI.'
170
171
172class MissingRedirectURIError(InvalidRequestFatalError):
173    description = 'Missing redirect URI.'
174
175
176class MismatchingRedirectURIError(InvalidRequestFatalError):
177    description = 'Mismatching redirect URI.'
178
179
180class InvalidClientIdError(InvalidRequestFatalError):
181    description = 'Invalid client_id parameter value.'
182
183
184class MissingClientIdError(InvalidRequestFatalError):
185    description = 'Missing client_id parameter.'
186
187
188class InvalidRequestError(OAuth2Error):
189    """
190    The request is missing a required parameter, includes an invalid
191    parameter value, includes a parameter more than once, or is
192    otherwise malformed.
193    """
194    error = 'invalid_request'
195
196
197class MissingResponseTypeError(InvalidRequestError):
198    description = 'Missing response_type parameter.'
199
200
201class MissingCodeChallengeError(InvalidRequestError):
202    """
203    If the server requires Proof Key for Code Exchange (PKCE) by OAuth
204    public clients and the client does not send the "code_challenge" in
205    the request, the authorization endpoint MUST return the authorization
206    error response with the "error" value set to "invalid_request".  The
207    "error_description" or the response of "error_uri" SHOULD explain the
208    nature of error, e.g., code challenge required.
209    """
210    description = 'Code challenge required.'
211
212
213class MissingCodeVerifierError(InvalidRequestError):
214    """
215    The request to the token endpoint, when PKCE is enabled, has
216    the parameter `code_verifier` REQUIRED.
217    """
218    description = 'Code verifier required.'
219
220
221class AccessDeniedError(OAuth2Error):
222    """
223    The resource owner or authorization server denied the request.
224    """
225    error = 'access_denied'
226
227
228class UnsupportedResponseTypeError(OAuth2Error):
229    """
230    The authorization server does not support obtaining an authorization
231    code using this method.
232    """
233    error = 'unsupported_response_type'
234
235
236class UnsupportedCodeChallengeMethodError(InvalidRequestError):
237    """
238    If the server supporting PKCE does not support the requested
239    transformation, the authorization endpoint MUST return the
240    authorization error response with "error" value set to
241    "invalid_request".  The "error_description" or the response of
242    "error_uri" SHOULD explain the nature of error, e.g., transform
243    algorithm not supported.
244    """
245    description = 'Transform algorithm not supported.'
246
247
248class InvalidScopeError(OAuth2Error):
249    """
250    The requested scope is invalid, unknown, or malformed, or
251    exceeds the scope granted by the resource owner.
252
253    https://tools.ietf.org/html/rfc6749#section-5.2
254    """
255    error = 'invalid_scope'
256
257
258class ServerError(OAuth2Error):
259    """
260    The authorization server encountered an unexpected condition that
261    prevented it from fulfilling the request.  (This error code is needed
262    because a 500 Internal Server Error HTTP status code cannot be returned
263    to the client via a HTTP redirect.)
264    """
265    error = 'server_error'
266
267
268class TemporarilyUnavailableError(OAuth2Error):
269    """
270    The authorization server is currently unable to handle the request
271    due to a temporary overloading or maintenance of the server.
272    (This error code is needed because a 503 Service Unavailable HTTP
273    status code cannot be returned to the client via a HTTP redirect.)
274    """
275    error = 'temporarily_unavailable'
276
277
278class InvalidClientError(FatalClientError):
279    """
280    Client authentication failed (e.g. unknown client, no client
281    authentication included, or unsupported authentication method).
282    The authorization server MAY return an HTTP 401 (Unauthorized) status
283    code to indicate which HTTP authentication schemes are supported.
284    If the client attempted to authenticate via the "Authorization" request
285    header field, the authorization server MUST respond with an
286    HTTP 401 (Unauthorized) status code, and include the "WWW-Authenticate"
287    response header field matching the authentication scheme used by the
288    client.
289    """
290    error = 'invalid_client'
291    status_code = 401
292
293
294class InvalidGrantError(OAuth2Error):
295    """
296    The provided authorization grant (e.g. authorization code, resource
297    owner credentials) or refresh token is invalid, expired, revoked, does
298    not match the redirection URI used in the authorization request, or was
299    issued to another client.
300
301    https://tools.ietf.org/html/rfc6749#section-5.2
302    """
303    error = 'invalid_grant'
304    status_code = 400
305
306
307class UnauthorizedClientError(OAuth2Error):
308    """
309    The authenticated client is not authorized to use this authorization
310    grant type.
311    """
312    error = 'unauthorized_client'
313
314
315class UnsupportedGrantTypeError(OAuth2Error):
316    """
317    The authorization grant type is not supported by the authorization
318    server.
319    """
320    error = 'unsupported_grant_type'
321
322
323class UnsupportedTokenTypeError(OAuth2Error):
324    """
325    The authorization server does not support the hint of the
326    presented token type.  I.e. the client tried to revoke an access token
327    on a server not supporting this feature.
328    """
329    error = 'unsupported_token_type'
330
331
332class InvalidTokenError(OAuth2Error):
333    """
334    The access token provided is expired, revoked, malformed, or
335    invalid for other reasons.  The resource SHOULD respond with
336    the HTTP 401 (Unauthorized) status code.  The client MAY
337    request a new access token and retry the protected resource
338    request.
339    """
340    error = 'invalid_token'
341    status_code = 401
342    description = ("The access token provided is expired, revoked, malformed, "
343                   "or invalid for other reasons.")
344
345
346class InsufficientScopeError(OAuth2Error):
347    """
348    The request requires higher privileges than provided by the
349    access token.  The resource server SHOULD respond with the HTTP
350    403 (Forbidden) status code and MAY include the "scope"
351    attribute with the scope necessary to access the protected
352    resource.
353    """
354    error = 'insufficient_scope'
355    status_code = 403
356    description = ("The request requires higher privileges than provided by "
357                   "the access token.")
358
359
360class ConsentRequired(OAuth2Error):
361    """
362    The Authorization Server requires End-User consent.
363
364    This error MAY be returned when the prompt parameter value in the
365    Authentication Request is none, but the Authentication Request cannot be
366    completed without displaying a user interface for End-User consent.
367    """
368    error = 'consent_required'
369
370
371class LoginRequired(OAuth2Error):
372    """
373    The Authorization Server requires End-User authentication.
374
375    This error MAY be returned when the prompt parameter value in the
376    Authentication Request is none, but the Authentication Request cannot be
377    completed without displaying a user interface for End-User authentication.
378    """
379    error = 'login_required'
380
381
382class CustomOAuth2Error(OAuth2Error):
383    """
384    This error is a placeholder for all custom errors not described by the RFC.
385    Some of the popular OAuth2 providers are using custom errors.
386    """
387    def __init__(self, error, *args, **kwargs):
388        self.error = error
389        super().__init__(*args, **kwargs)
390
391
392def raise_from_error(error, params=None):
393    import inspect
394    import sys
395    kwargs = {
396        'description': params.get('error_description'),
397        'uri': params.get('error_uri'),
398        'state': params.get('state')
399    }
400    for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
401        if cls.error == error:
402            raise cls(**kwargs)
403    raise CustomOAuth2Error(error=error, **kwargs)
404