Beispiel #1
0
    def test_json_omitempty(self):
        protected_jobj = self.protected.to_partial_json(flat=True)
        unprotected_jobj = self.unprotected.to_partial_json(flat=True)

        self.assertTrue('protected' not in unprotected_jobj)
        self.assertTrue('header' not in protected_jobj)

        unprotected_jobj['header'] = unprotected_jobj['header'].to_json()

        from josepy.jws import JWS
        self.assertEqual(JWS.from_json(protected_jobj), self.protected)
        self.assertEqual(JWS.from_json(unprotected_jobj), self.unprotected)
Beispiel #2
0
    def setUp(self):
        self.privkey = KEY
        self.pubkey = self.privkey.public_key()

        from josepy.jws import JWS
        self.unprotected = JWS.sign(
            payload=b'foo', key=self.privkey, alg=jwa.RS256)
        self.protected = JWS.sign(
            payload=b'foo', key=self.privkey, alg=jwa.RS256,
            protect=frozenset(['jwk', 'alg']))
        self.mixed = JWS.sign(
            payload=b'foo', key=self.privkey, alg=jwa.RS256,
            protect=frozenset(['alg']))
Beispiel #3
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the jwks endpoint of the identity
        provider."""
        key = self.cache.get(self.OIDC_OP_JWKS_ENDPOINT)
        if key:
            return key

        response_jwks = requests.get(self.OIDC_OP_JWKS_ENDPOINT,
                                     verify=import_from_settings(
                                         'OIDC_VERIFY_SSL', True))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if (jwk['alg'] == smart_text(header.alg)
                    and jwk['kid'] == smart_text(header.kid)):
                key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')

        self.cache.set(self.OIDC_OP_JWKS_ENDPOINT, key, 3600)
        return key
Beispiel #4
0
 def get_userinfo(self, access_token, id_token, payload):
     """Retrive userinfo, parse JWT data, merge the two dictionaries"""
     userdata = super().get_userinfo(access_token, id_token, payload)
     jws = JWS.from_compact(force_bytes(id_token))
     payload = json.loads(jws.payload.decode("utf-8"))
     payload.update(userdata)
     return payload
Beispiel #5
0
Datei: drf.py Projekt: krtb/glam
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        jwks = get_jwks()

        # Compute the current header from the given token to find a match
        try:
            jws = JWS.from_compact(token)
        except DeserializationError as e:
            raise exceptions.AuthenticationFailed(e)

        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks["keys"]:
            if jwk["kid"] != smart_text(header.kid):
                continue
            if "alg" in jwk and jwk["alg"] != smart_text(header.alg):
                raise exceptions.AuthenticationFailed(
                    "alg values do not match.")
            key = jwk
        if key is None:
            raise exceptions.AuthenticationFailed(
                "Could not find a valid JWKS.")
        return key
Beispiel #6
0
    def _verify_jws(self, payload, key):
        """Verify the given JWS payload with the given key and return the payload"""
        jws = JWS.from_compact(payload)

        try:
            alg = jws.signature.combined.alg.name
        except KeyError:
            msg = 'No alg value found in header'
            raise SuspiciousOperation(msg)

        if alg != self.OIDC_RP_SIGN_ALGO:
            msg = "The provider algorithm {!r} does not match the client's " \
                  "OIDC_RP_SIGN_ALGO.".format(alg)
            raise SuspiciousOperation(msg)

        if isinstance(key, six.string_types):
            # Use smart_bytes here since the key string comes from settings.
            jwk = JWK.load(smart_bytes(key))
        else:
            # The key is a json returned from the IDP JWKS endpoint.
            jwk = JWK.from_json(key)

        if not jws.verify(jwk):
            msg = 'JWS token verification failed.'
            raise SuspiciousOperation(msg)

        return jws.payload
Beispiel #7
0
    def verify_token(self, token, **kwargs):
        """Validate the token signature."""

        token = force_bytes(token)
        jws = JWS.from_compact(token)
        header = json.loads(jws.signature.protected)

        try:
            header.get("alg")
        except KeyError:
            msg = "No alg value found in header"
            raise SuspiciousOperation(msg)

        jwk_json = self.retrieve_matching_jwk(header)
        jwk = JWK.from_json(jwk_json)

        if not jws.verify(jwk):
            msg = "JWS token verification failed."
            raise SuspiciousOperation(msg)

        # The 'token' will always be a byte string since it's
        # the result of base64.urlsafe_b64decode().
        # The payload is always the result of base64.urlsafe_b64decode().
        # In Python 3 and 2, that's always a byte string.
        # In Python3.6, the json.loads() function can accept a byte string
        # as it will automagically decode it to a unicode string before
        # deserializing https://bugs.python.org/issue17909
        return json.loads(jws.payload.decode("utf-8"))
Beispiel #8
0
    def _verify_jws(self, payload, key):
        """Verify the given JWS payload with the given key and return the payload"""
        jws = JWS.from_compact(payload)

        try:
            alg = jws.signature.combined.alg.name
        except KeyError:
            msg = 'No alg value found in header'
            raise SuspiciousOperation(msg)

        if alg != self.OIDC_RP_SIGN_ALGO:
            msg = "The provider algorithm {!r} does not match the client's " \
                  "OIDC_RP_SIGN_ALGO.".format(alg)
            raise SuspiciousOperation(msg)

        if isinstance(key, six.string_types):
            # Use smart_bytes here since the key string comes from settings.
            jwk = JWK.load(smart_bytes(key))
        else:
            # The key is a json returned from the IDP JWKS endpoint.
            jwk = JWK.from_json(key)

        if not jws.verify(jwk):
            msg = 'JWS token verification failed.'
            raise SuspiciousOperation(msg)

        return jws.payload
Beispiel #9
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        response_jwks = requests.get(
            self.OIDC_OP_JWKS_ENDPOINT,
            verify=self.get_settings('OIDC_VERIFY_SSL', True),
            timeout=self.get_settings('OIDC_TIMEOUT', None),
            proxies=self.get_settings('OIDC_PROXY', None))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if jwk['kid'] != smart_text(header.kid):
                continue
            if 'alg' in jwk and jwk['alg'] != smart_text(header.alg):
                raise SuspiciousOperation('alg values do not match.')
            key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
    def get_userinfo(self, access_token, id_token, verified_id):
        """
        Extract user details from JSON web tokens
        These map to fields on the user field.
        """
        id_info = json.loads(
            JWS.from_compact(id_token).payload.decode("utf-8"))
        access_info = json.loads(
            JWS.from_compact(access_token).payload.decode("utf-8"))

        info = {}

        for oidc_attr, user_attr in settings.OIDC_ACCESS_ATTRIBUTE_MAP.items():
            info[user_attr] = access_info[oidc_attr]

        for oidc_attr, user_attr in settings.OIDC_ID_ATTRIBUTE_MAP.items():
            info[user_attr] = id_info[oidc_attr]

        return info
Beispiel #11
0
    def test_json_not_flat(self):
        jobj_to = {
            'signatures': (self.mixed.signature,),
            'payload': json_util.encode_b64jose(b'foo'),
        }
        jobj_from = jobj_to.copy()
        jobj_from['signatures'] = [jobj_to['signatures'][0].to_json()]

        self.assertEqual(self.mixed.to_partial_json(flat=False), jobj_to)
        from josepy.jws import JWS
        self.assertEqual(self.mixed, JWS.from_json(jobj_from))
Beispiel #12
0
    def test_compact_lost_unprotected(self):
        compact = self.mixed.to_compact()
        self.assertEqual(
            b'eyJhbGciOiAiUlMyNTYifQ.Zm9v.OHdxFVj73l5LpxbFp1AmYX4yJM0Pyb'
            b'_893n1zQjpim_eLS5J1F61lkvrCrCDErTEJnBGOGesJ72M7b6Ve1cAJA',
            compact)

        from josepy.jws import JWS
        mixed = JWS.from_compact(compact)

        self.assertNotEqual(self.mixed, mixed)
        self.assertEqual({'alg'}, set(mixed.signature.combined.not_omitted()))
Beispiel #13
0
    def get_userinfo(self, access_token, id_token, verified_id):
        """Extract user details from JSON web tokens.

        It returns a dict of user details that will be applied directly to the
        user model.
        """
        # JWS.from_compact expects bytes.
        id_token = id_token.encode("utf-8")
        access_token = access_token.encode("utf-8")

        id_info = json.loads(JWS.from_compact(id_token).payload.decode("utf-8"))
        access_info = json.loads(JWS.from_compact(access_token).payload.decode("utf-8"))

        info = {}

        for oidc_attr, user_attr in settings.OIDC_ACCESS_ATTRIBUTE_MAP.items():
            info[user_attr] = access_info.get(oidc_attr)

        for oidc_attr, user_attr in settings.OIDC_ID_ATTRIBUTE_MAP.items():
            info[user_attr] = id_info.get(oidc_attr)

        return info
Beispiel #14
0
    def test_compact_lost_unprotected(self):
        compact = self.mixed.to_compact()
        self.assertEqual(
            b'eyJhbGciOiAiUlMyNTYifQ.Zm9v.OHdxFVj73l5LpxbFp1AmYX4yJM0Pyb'
            b'_893n1zQjpim_eLS5J1F61lkvrCrCDErTEJnBGOGesJ72M7b6Ve1cAJA',
            compact)

        from josepy.jws import JWS
        mixed = JWS.from_compact(compact)

        self.assertNotEqual(self.mixed, mixed)
        self.assertEqual(
            set(['alg']), set(mixed.signature.combined.not_omitted()))
Beispiel #15
0
    def test_json_flat(self):
        jobj_to = {
            'signature': json_util.encode_b64jose(
                self.mixed.signature.signature),
            'payload': json_util.encode_b64jose(b'foo'),
            'header': self.mixed.signature.header,
            'protected': json_util.encode_b64jose(
                self.mixed.signature.protected.encode('utf-8')),
        }
        jobj_from = jobj_to.copy()
        jobj_from['header'] = jobj_from['header'].to_json()

        self.assertEqual(self.mixed.to_partial_json(flat=True), jobj_to)
        from josepy.jws import JWS
        self.assertEqual(self.mixed, JWS.from_json(jobj_from))
Beispiel #16
0
    async def jwt_verify_and_decode(
        self,
        id_token: str,
        jwks_endpoint: str,
        verify: bool = True,
        audience: str = None,
    ) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method also verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token

        Returns:
          Decoded dictionary that represents the k/v's included with the JWT
        """
        if verify is False:
            unverified_token = jwt.decode(id_token,
                                          options={"verify_signature": False})
            self.log.debug(
                f"JWK verification is off, returning token {unverified_token}")
            return unverified_token

        jws = JWS.from_compact(id_token)
        self.log.debug(f"Retrieving matching jws {jws}")
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug(f"Header from decoded jwt {header}")

        key_from_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify, header.kid)
        self.log.debug(
            f"Returning decoded jwt with token {id_token} key {key_from_jwks} and verify {verify}"
        )

        return jwt.decode(
            id_token,
            key=key_from_jwks,
            audience=audience,
            options={"verify_signature": False},
        )
Beispiel #17
0
 def _verified(self):
     try:
         jwk = JWK.load(self.public_key)
         self.jws_obj = JWS.from_compact(self.jws)
         if self._signed(jwk) is False:
             logger.warning(
                 'The public key signature was not valid for jws {jws}'.
                 format(jws=self.jws))
             self.jws_data = json.loads(self.jws.payload)
             self.jws_data['code'] = 'invalid'
             return False
         else:
             self.jws_data = json.loads(self.jws_obj.payload.decode())
             logger.info('Loaded JWS data.')
             self.jws_data['connection_name'] = self._get_connection_name(
                 self.jws_data['connection'])
             return True
     except UnicodeDecodeError:
         return False
Beispiel #18
0
    def _verify_jws(self, payload, key):
        """Verify the given JWS payload with the given key and return the payload"""

        jws = JWS.from_compact(payload)
        jwk = JWK.load(key)
        if not jws.verify(jwk):
            msg = 'JWS token verification failed.'
            raise SuspiciousOperation(msg)

        try:
            alg = jws.signature.combined.alg.name
            if alg != self.OIDC_RP_SIGN_ALGO:
                msg = 'The specified alg value is not allowed'
                raise SuspiciousOperation(msg)
        except KeyError:
            msg = 'No alg value found in header'
            raise SuspiciousOperation(msg)

        return jws.payload
Beispiel #19
0
    async def jwt_verify_and_decode(self,
                                    id_token: str,
                                    jwks_endpoint: str,
                                    verify: bool = True,
                                    audience: str = None) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method implicitly verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token
        """
        if verify is False:
            self.log.debug('JWK verification is off, returning token %s' %
                           jwt.decode(id_token, verify=False))
            return jwt.decode(id_token, verify=False)
        retrieved_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify)
        jws = JWS.from_compact(bytes(id_token, 'utf-8'))
        self.log.debug('Retrieving matching jws %s' % jws)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug('Header from decoded jwt %s' % header)
        key = None
        for jwk in retrieved_jwks['keys']:
            if jwk['kid'] != header.kid:
                continue
            key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
            self.log.debug('Get keys from jwks dict  %s' % key)
        if key is None:
            self.log.debug('Key is None, returning None')
            return None
        self.log.debug(
            'Returning decoded jwt with token %s key %s and verify %s' %
            (id_token, key, verify))
        return jwt.decode(id_token, key=key, verify=False, audience=audience)
Beispiel #20
0
 def _verified(self):
     try:
         jwk = JWK.load(self.public_key)
         self.jws = JWS.from_compact(self.jws)
         if self._signed(jwk) is False:
             logger.warning(
                 'The public key signature was not valid for jws {jws}'.
                 format(jws=self.jws))
             self.jws_data = json.loads(self.jws.payload)
             self.jws_data['code'] = 'invalid'
             return False
         else:
             self.jws_data = json.loads(self.jws.payload)
             logger.info('Loaded JWS data.')
             self.jws_data['connection_name'] = self._get_connection_name(
                 self.jws_data['connection_name'])
             return True
     except Exception as e:
         logger.warning(
             'JWS could not be decoded due to {error}'.format(error=e))
         return False
Beispiel #21
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        response_jwks = requests.get(
            self.OIDC_OP_JWKS_ENDPOINT,
            verify=import_from_settings('OIDC_VERIFY_SSL', True)
        )
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if jwk['alg'] == smart_text(header.alg) and jwk['kid'] == smart_text(header.kid):
                key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
Beispiel #22
0
    async def jwt_verify_and_decode(
        self,
        id_token: str,
        jwks_endpoint: str,
        verify: bool = True,
        audience: str = None,
    ) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method implicitly verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token
        """
        if verify is False:
            self.log.debug("JWK verification is off, returning token %s" %
                           jwt.decode(id_token, verify=False))
            return jwt.decode(id_token, verify=False)

        jws = JWS.from_compact(id_token)
        self.log.debug("Retrieving matching jws %s" % jws)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug("Header from decoded jwt %s" % header)

        key_from_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify, header.kid)
        self.log.debug(
            "Returning decoded jwt with token %s key %s and verify %s" %
            (id_token, key_from_jwks, verify))

        return jwt.decode(id_token,
                          key=key_from_jwks,
                          verify=False,
                          audience=audience)
Beispiel #23
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP.

        Overriding original method because KeyCloak isn't providing the key id
        in the token.  Since there's only one jwks key, try using that key
        to verify the token's signature.

        Args:
            token

        Returns:
            Public signing key for the KeyCloak realm.

        Raises:
            (SuspiciousOperation)
        """
        response_jwks = requests.get(self.OIDC_OP_JWKS_ENDPOINT,
                                     verify=self.get_settings(
                                         'OIDC_VERIFY_SSL', True))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        num_keys = len(jwks['keys'])
        for jwk in jwks['keys']:
            # If there's only one key, then try it even if the key id doesn't
            # match or is None.
            if jwk['kid'] != smart_text(header.kid) and num_keys > 1:
                continue
            if 'alg' in jwk and jwk['alg'] != smart_text(header.alg):
                raise SuspiciousOperation('alg values do not match.')
            key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
Beispiel #24
0
Datei: drf.py Projekt: krtb/glam
    def _verify_jws(self, payload, key):
        """Verify the given JWS payload with the given key and return the payload"""
        jws = JWS.from_compact(payload)

        try:
            alg = jws.signature.combined.alg.name
        except KeyError:
            raise exceptions.AuthenticationFailed(
                "No alg value found in header")

        if alg != settings.OIDC_RP_SIGN_ALGO:
            raise exceptions.AuthenticationFailed(
                "The provider algorithm {!r} does not match the client's "
                "OIDC_RP_SIGN_ALGO.".format(alg))

        jwk = JWK.from_json(key)

        if not jws.verify(jwk):
            raise exceptions.AuthenticationFailed(
                "JWS token verification failed.")

        return jws.payload
Beispiel #25
0
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET, 'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id':
            self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret':
            self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type':
            'authorization_code',
            'code':
            request.GET['code'],
            'redirect_uri':
            absolutify(self.request,
                       nonprefixed_url('phonebook:verify_identity_callback')),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings(
                                     'OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {'auth0_user_id': user_info['sub'], 'email': email}

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in user_info['sub']:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(
                    pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(
                    next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile,
                                             profile=profile,
                                             primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type)
                    or (current_idp
                        and current_idp.auth0_user_id == idp.auth0_user_id)
                    or (not current_idp and created
                        and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(
                    pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url
                                    or reverse('phonebook:profile_edit'))
Beispiel #26
0
def verify_jws(token, client_id, openid_configuration):
    jws = JWS.from_compact(token.encode("ascii"))
    json_header = jws.signature.protected
    header = Header.json_loads(json_header)

    # alg
    alg = jws.signature.combined.alg.name.upper()
    if "NONE" in alg:
        raise SuspiciousOperation("ID token is not signed")
    if alg not in (alg.upper() for alg in openid_configuration["id_token_signing_alg_values_supported"]):
        raise SuspiciousOperation("Unexpected ID token signature algorithm")

    # retrieve signature key
    # TODO cache
    jwks_response = requests.get(openid_configuration["jwks_uri"], headers={"Accept": "application/json"})
    jwks_response.raise_for_status()
    jwk = None
    for jwk_json in jwks_response.json()["keys"]:
        if jwk_json["kid"] == header.kid:
            jwk = JWK.from_json(jwk_json)
            break
    if not jwk:
        raise SuspiciousOperation("Could not find ID token signing key")

    # verify signature
    if not jws.verify(jwk):
        raise SuspiciousOperation("Invalid ID token signature")

    payload = json.loads(jws.payload.decode('utf-8'))

    # iss
    if payload.get("iss") != openid_configuration["issuer"]:
        raise SuspiciousOperation("Invalid ID token 'iss'")

    # aud
    if payload.get("aud") != client_id:
        raise SuspiciousOperation("Invalid ID token 'aud'")

    timestamp = int(time.time())

    # nbf
    nbf = payload.get("nbf")
    if nbf is not None:
        try:
            nbf = int(nbf)
        except (TypeError, ValueError):
            raise SuspiciousOperation("Invalid ID token 'nbf'")
        if timestamp < nbf - TIMESTAMP_LEEWAY:
            raise SuspiciousOperation("ID token not valid yet")

    # exp
    exp = payload.get("exp")
    if exp is not None:
        try:
            exp = int(exp)
        except (TypeError, ValueError):
            raise SuspiciousOperation("Invalid ID token 'exp'")
        if timestamp > exp + TIMESTAMP_LEEWAY:
            raise SuspiciousOperation("ID token has expired")

    return payload
Beispiel #27
0
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET,
            'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id': self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret': self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type': 'authorization_code',
            'code': request.GET['code'],
            'redirect_uri': absolutify(
                self.request,
                nonprefixed_url('phonebook:verify_identity_callback')
            ),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings('OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {
                'auth0_user_id': user_info['sub'],
                'email': email
            }

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in user_info['sub']:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type) or
                (current_idp and current_idp.auth0_user_id == idp.auth0_user_id) or
                    (not current_idp and created and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))
Beispiel #28
0
 def test_from_json_hashable(self):
     from josepy.jws import JWS
     hash(JWS.from_json(self.mixed.to_json()))
Beispiel #29
0
        '''

        if verify is False:

            token = jwt.decode(id_token, verify=False)

            self.log.debug('JWK verification is off, returning token')

            return token

        if isinstance(id_token, str):

            id_token = bytes(id_token, encoding='utf-8')

        jws = JWS.from_compact(id_token)

        self.log.debug('Retrieving matching jws %s' % jws)

        json_header = jws.signature.protected

        header = Header.json_loads(json_header)

        self.log.debug('Header from decoded jwt %s' % header)

        key_from_jwks = await self._retrieve_matching_jwk(jwks_endpoint,
                                                          header.kid,
                                                          verify=verify)

        self.log.debug(
            'Returning decoded jwt with token %s key %s and verify %s' %