1"""
2oauthlib.openid.connect.core.endpoints.userinfo
3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5This module is an implementation of userinfo endpoint.
6"""
7import json
8import logging
9
10from oauthlib.common import Request
11from oauthlib.oauth2.rfc6749 import errors
12from oauthlib.oauth2.rfc6749.endpoints.base import (
13 BaseEndpoint, catch_errors_and_unavailability,
14)
15from oauthlib.oauth2.rfc6749.tokens import BearerToken
16
17log = logging.getLogger(__name__)
18
19
20class UserInfoEndpoint(BaseEndpoint):
21 """Authorizes access to userinfo resource.
22 """
23 def __init__(self, request_validator):
24 self.bearer = BearerToken(request_validator, None, None, None)
25 self.request_validator = request_validator
26 BaseEndpoint.__init__(self)
27
28 @catch_errors_and_unavailability
29 def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
30 """Validate BearerToken and return userinfo from RequestValidator
31
32 The UserInfo Endpoint MUST return a
33 content-type header to indicate which format is being returned. The
34 content-type of the HTTP response MUST be application/json if the
35 response body is a text JSON object; the response body SHOULD be encoded
36 using UTF-8.
37 """
38 request = Request(uri, http_method, body, headers)
39 request.scopes = ["openid"]
40 self.validate_userinfo_request(request)
41
42 claims = self.request_validator.get_userinfo_claims(request)
43 if claims is None:
44 log.error('Userinfo MUST have claims for %r.', request)
45 raise errors.ServerError(status_code=500)
46
47 if isinstance(claims, dict):
48 resp_headers = {
49 'Content-Type': 'application/json'
50 }
51 if "sub" not in claims:
52 log.error('Userinfo MUST have "sub" for %r.', request)
53 raise errors.ServerError(status_code=500)
54 body = json.dumps(claims)
55 elif isinstance(claims, str):
56 resp_headers = {
57 'Content-Type': 'application/jwt'
58 }
59 body = claims
60 else:
61 log.error('Userinfo return unknown response for %r.', request)
62 raise errors.ServerError(status_code=500)
63 log.debug('Userinfo access valid for %r.', request)
64 return resp_headers, body, 200
65
66 def validate_userinfo_request(self, request):
67 """Ensure the request is valid.
68
69 5.3.1. UserInfo Request
70 The Client sends the UserInfo Request using either HTTP GET or HTTP
71 POST. The Access Token obtained from an OpenID Connect Authentication
72 Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
73 Bearer Token Usage [RFC6750].
74
75 It is RECOMMENDED that the request use the HTTP GET method and the
76 Access Token be sent using the Authorization header field.
77
78 The following is a non-normative example of a UserInfo Request:
79
80 .. code-block:: http
81
82 GET /userinfo HTTP/1.1
83 Host: server.example.com
84 Authorization: Bearer SlAV32hkKG
85
86 5.3.3. UserInfo Error Response
87 When an error condition occurs, the UserInfo Endpoint returns an Error
88 Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
89 [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
90 Agent using the appropriate HTTP status code.)
91
92 The following is a non-normative example of a UserInfo Error Response:
93
94 .. code-block:: http
95
96 HTTP/1.1 401 Unauthorized
97 WWW-Authenticate: Bearer error="invalid_token",
98 error_description="The Access Token expired"
99
100 .. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
101 .. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
102 """
103 if not self.bearer.validate_request(request):
104 raise errors.InvalidTokenError()
105 if "openid" not in request.scopes:
106 raise errors.InsufficientScopeError()