1import base64 
    2import hashlib 
    3import logging 
    4import time 
    5from json import loads 
    6 
    7from oauthlib.oauth2.rfc6749.errors import ( 
    8    ConsentRequired, InvalidRequestError, LoginRequired, 
    9) 
    10 
    11log = logging.getLogger(__name__) 
    12 
    13 
    14class GrantTypeBase: 
    15 
    16    # Just proxy the majority of method calls through to the 
    17    # proxy_target grant type handler, which will usually be either 
    18    # the standard OAuth2 AuthCode or Implicit grant types. 
    19    def __getattr__(self, attr): 
    20        return getattr(self.proxy_target, attr) 
    21 
    22    def __setattr__(self, attr, value): 
    23        proxied_attrs = {'refresh_token', 'response_types'} 
    24        if attr in proxied_attrs: 
    25            setattr(self.proxy_target, attr, value) 
    26        else: 
    27            super(OpenIDConnectBase, self).__setattr__(attr, value) 
    28 
    29    def validate_authorization_request(self, request): 
    30        """Validates the OpenID Connect authorization request parameters. 
    31 
    32        :returns: (list of scopes, dict of request info) 
    33        """ 
    34        return self.proxy_target.validate_authorization_request(request) 
    35 
    36    def _inflate_claims(self, request): 
    37        # this may be called multiple times in a single request so make sure we only de-serialize the claims once 
    38        if request.claims and not isinstance(request.claims, dict): 
    39            # specific claims are requested during the Authorization Request and may be requested for inclusion 
    40            # in either the id_token or the UserInfo endpoint response 
    41            # see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter 
    42            try: 
    43                request.claims = loads(request.claims) 
    44            except Exception as ex: 
    45                raise InvalidRequestError(description="Malformed claims parameter", 
    46                                          uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") 
    47 
    48    def id_token_hash(self, value, hashfunc=hashlib.sha256): 
    49        """ 
    50        Its value is the base64url encoding of the left-most half of the 
    51        hash of the octets of the ASCII representation of the access_token 
    52        value, where the hash algorithm used is the hash algorithm used in 
    53        the alg Header Parameter of the ID Token's JOSE Header. 
    54 
    55        For instance, if the alg is RS256, hash the access_token value 
    56        with SHA-256, then take the left-most 128 bits and 
    57        base64url-encode them. 
    58        For instance, if the alg is HS512, hash the code value with 
    59        SHA-512, then take the left-most 256 bits and base64url-encode 
    60        them. The c_hash value is a case-sensitive string. 
    61 
    62        Example of hash from OIDC specification (bound to a JWS using RS256): 
    63 
    64        code: 
    65        Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 
    66 
    67        c_hash: 
    68        LDktKdoQak3Pk0cnXxCltA 
    69        """ 
    70        digest = hashfunc(value.encode()).digest() 
    71        left_most = len(digest) // 2 
    72        return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=") 
    73 
    74    def add_id_token(self, token, token_handler, request, nonce=None): 
    75        """ 
    76        Construct an initial version of id_token, and let the 
    77        request_validator sign or encrypt it. 
    78 
    79        The initial version can contain the fields below, accordingly 
    80        to the spec: 
    81        - aud 
    82        - iat 
    83        - nonce 
    84        - at_hash 
    85        - c_hash 
    86        """ 
    87        # Treat it as normal OAuth 2 auth code request if openid is not present 
    88        if not request.scopes or 'openid' not in request.scopes: 
    89            return token 
    90 
    91        # Only add an id token on auth/token step if asked for. 
    92        if request.response_type and 'id_token' not in request.response_type: 
    93            return token 
    94 
    95        # Implementation mint its own id_token without help. 
    96        id_token = self.request_validator.get_id_token(token, token_handler, request) 
    97        if id_token: 
    98            token['id_token'] = id_token 
    99            return token 
    100 
    101        # Fallback for asking some help from oauthlib framework. 
    102        # Start with technicals fields bound to the specification. 
    103        id_token = {} 
    104        id_token['aud'] = request.client_id 
    105        id_token['iat'] = int(time.time()) 
    106 
    107        # nonce is REQUIRED when response_type value is: 
    108        # - id_token token (Implicit) 
    109        # - id_token (Implicit) 
    110        # - code id_token (Hybrid) 
    111        # - code id_token token (Hybrid) 
    112        # 
    113        # nonce is OPTIONAL when response_type value is: 
    114        # - code (Authorization Code) 
    115        # - code token (Hybrid) 
    116        if nonce is not None: 
    117            id_token["nonce"] = nonce 
    118 
    119        # at_hash is REQUIRED when response_type value is: 
    120        # - id_token token (Implicit) 
    121        # - code id_token token (Hybrid) 
    122        # 
    123        # at_hash is OPTIONAL when: 
    124        # - code (Authorization code) 
    125        # - code id_token (Hybrid) 
    126        # - code token (Hybrid) 
    127        # 
    128        # at_hash MAY NOT be used when: 
    129        # - id_token (Implicit) 
    130        if "access_token" in token: 
    131            id_token["at_hash"] = self.id_token_hash(token["access_token"]) 
    132 
    133        # c_hash is REQUIRED when response_type value is: 
    134        # - code id_token (Hybrid) 
    135        # - code id_token token (Hybrid) 
    136        # 
    137        # c_hash is OPTIONAL for others. 
    138        if "code" in token: 
    139            id_token["c_hash"] = self.id_token_hash(token["code"]) 
    140 
    141        # Call request_validator to complete/sign/encrypt id_token 
    142        token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request) 
    143 
    144        return token 
    145 
    146    def openid_authorization_validator(self, request): 
    147        """Perform OpenID Connect specific authorization request validation. 
    148 
    149        nonce 
    150                OPTIONAL. String value used to associate a Client session with 
    151                an ID Token, and to mitigate replay attacks. The value is 
    152                passed through unmodified from the Authentication Request to 
    153                the ID Token. Sufficient entropy MUST be present in the nonce 
    154                values used to prevent attackers from guessing values 
    155 
    156        display 
    157                OPTIONAL. ASCII string value that specifies how the 
    158                Authorization Server displays the authentication and consent 
    159                user interface pages to the End-User. The defined values are: 
    160 
    161                    page - The Authorization Server SHOULD display the 
    162                    authentication and consent UI consistent with a full User 
    163                    Agent page view. If the display parameter is not specified, 
    164                    this is the default display mode. 
    165 
    166                    popup - The Authorization Server SHOULD display the 
    167                    authentication and consent UI consistent with a popup User 
    168                    Agent window. The popup User Agent window should be of an 
    169                    appropriate size for a login-focused dialog and should not 
    170                    obscure the entire window that it is popping up over. 
    171 
    172                    touch - The Authorization Server SHOULD display the 
    173                    authentication and consent UI consistent with a device that 
    174                    leverages a touch interface. 
    175 
    176                    wap - The Authorization Server SHOULD display the 
    177                    authentication and consent UI consistent with a "feature 
    178                    phone" type display. 
    179 
    180                The Authorization Server MAY also attempt to detect the 
    181                capabilities of the User Agent and present an appropriate 
    182                display. 
    183 
    184        prompt 
    185                OPTIONAL. Space delimited, case sensitive list of ASCII string 
    186                values that specifies whether the Authorization Server prompts 
    187                the End-User for reauthentication and consent. The defined 
    188                values are: 
    189 
    190                    none - The Authorization Server MUST NOT display any 
    191                    authentication or consent user interface pages. An error is 
    192                    returned if an End-User is not already authenticated or the 
    193                    Client does not have pre-configured consent for the 
    194                    requested Claims or does not fulfill other conditions for 
    195                    processing the request. The error code will typically be 
    196                    login_required, interaction_required, or another code 
    197                    defined in Section 3.1.2.6. This can be used as a method to 
    198                    check for existing authentication and/or consent. 
    199 
    200                    login - The Authorization Server SHOULD prompt the End-User 
    201                    for reauthentication. If it cannot reauthenticate the 
    202                    End-User, it MUST return an error, typically 
    203                    login_required. 
    204 
    205                    consent - The Authorization Server SHOULD prompt the 
    206                    End-User for consent before returning information to the 
    207                    Client. If it cannot obtain consent, it MUST return an 
    208                    error, typically consent_required. 
    209 
    210                    select_account - The Authorization Server SHOULD prompt the 
    211                    End-User to select a user account. This enables an End-User 
    212                    who has multiple accounts at the Authorization Server to 
    213                    select amongst the multiple accounts that they might have 
    214                    current sessions for. If it cannot obtain an account 
    215                    selection choice made by the End-User, it MUST return an 
    216                    error, typically account_selection_required. 
    217 
    218                The prompt parameter can be used by the Client to make sure 
    219                that the End-User is still present for the current session or 
    220                to bring attention to the request. If this parameter contains 
    221                none with any other value, an error is returned. 
    222 
    223        max_age 
    224                OPTIONAL. Maximum Authentication Age. Specifies the allowable 
    225                elapsed time in seconds since the last time the End-User was 
    226                actively authenticated by the OP. If the elapsed time is 
    227                greater than this value, the OP MUST attempt to actively 
    228                re-authenticate the End-User. (The max_age request parameter 
    229                corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age 
    230                request parameter.) When max_age is used, the ID Token returned 
    231                MUST include an auth_time Claim Value. 
    232 
    233        ui_locales 
    234                OPTIONAL. End-User's preferred languages and scripts for the 
    235                user interface, represented as a space-separated list of BCP47 
    236                [RFC5646] language tag values, ordered by preference. For 
    237                instance, the value "fr-CA fr en" represents a preference for 
    238                French as spoken in Canada, then French (without a region 
    239                designation), followed by English (without a region 
    240                designation). An error SHOULD NOT result if some or all of the 
    241                requested locales are not supported by the OpenID Provider. 
    242 
    243        id_token_hint 
    244                OPTIONAL. ID Token previously issued by the Authorization 
    245                Server being passed as a hint about the End-User's current or 
    246                past authenticated session with the Client. If the End-User 
    247                identified by the ID Token is logged in or is logged in by the 
    248                request, then the Authorization Server returns a positive 
    249                response; otherwise, it SHOULD return an error, such as 
    250                login_required. When possible, an id_token_hint SHOULD be 
    251                present when prompt=none is used and an invalid_request error 
    252                MAY be returned if it is not; however, the server SHOULD 
    253                respond successfully when possible, even if it is not present. 
    254                The Authorization Server need not be listed as an audience of 
    255                the ID Token when it is used as an id_token_hint value. If the 
    256                ID Token received by the RP from the OP is encrypted, to use it 
    257                as an id_token_hint, the Client MUST decrypt the signed ID 
    258                Token contained within the encrypted ID Token. The Client MAY 
    259                re-encrypt the signed ID token to the Authentication Server 
    260                using a key that enables the server to decrypt the ID Token, 
    261                and use the re-encrypted ID token as the id_token_hint value. 
    262 
    263        login_hint 
    264                OPTIONAL. Hint to the Authorization Server about the login 
    265                identifier the End-User might use to log in (if necessary). 
    266                This hint can be used by an RP if it first asks the End-User 
    267                for their e-mail address (or other identifier) and then wants 
    268                to pass that value as a hint to the discovered authorization 
    269                service. It is RECOMMENDED that the hint value match the value 
    270                used for discovery. This value MAY also be a phone number in 
    271                the format specified for the phone_number Claim. The use of 
    272                this parameter is left to the OP's discretion. 
    273 
    274        acr_values 
    275                OPTIONAL. Requested Authentication Context Class Reference 
    276                values. Space-separated string that specifies the acr values 
    277                that the Authorization Server is being requested to use for 
    278                processing this Authentication Request, with the values 
    279                appearing in order of preference. The Authentication Context 
    280                Class satisfied by the authentication performed is returned as 
    281                the acr Claim Value, as specified in Section 2. The acr Claim 
    282                is requested as a Voluntary Claim by this parameter. 
    283        """ 
    284 
    285        # Treat it as normal OAuth 2 auth code request if openid is not present 
    286        if not request.scopes or 'openid' not in request.scopes: 
    287            return {} 
    288 
    289        prompt = request.prompt if request.prompt else [] 
    290        if hasattr(prompt, 'split'): 
    291            prompt = prompt.strip().split() 
    292        prompt = set(prompt) 
    293 
    294        if 'none' in prompt: 
    295 
    296            if len(prompt) > 1: 
    297                msg = "Prompt none is mutually exclusive with other values." 
    298                raise InvalidRequestError(request=request, description=msg) 
    299 
    300            if not self.request_validator.validate_silent_login(request): 
    301                raise LoginRequired(request=request) 
    302 
    303            if not self.request_validator.validate_silent_authorization(request): 
    304                raise ConsentRequired(request=request) 
    305 
    306        self._inflate_claims(request) 
    307 
    308        if not self.request_validator.validate_user_match( 
    309                request.id_token_hint, request.scopes, request.claims, request): 
    310            msg = "Session user does not match client supplied user." 
    311            raise LoginRequired(request=request, description=msg) 
    312 
    313        ui_locales = request.ui_locales if request.ui_locales else [] 
    314        if hasattr(ui_locales, 'split'): 
    315            ui_locales = ui_locales.strip().split() 
    316 
    317        request_info = { 
    318            'display': request.display, 
    319            'nonce': request.nonce, 
    320            'prompt': prompt, 
    321            'ui_locales': ui_locales, 
    322            'id_token_hint': request.id_token_hint, 
    323            'login_hint': request.login_hint, 
    324            'claims': request.claims 
    325        } 
    326 
    327        return request_info 
    328 
    329 
    330OpenIDConnectBase = GrantTypeBase