Exemplo n.º 1
0
    def auth_complete(self, *args, **kwargs):
        """Return user, might be logged in"""
        # Multiple unauthorized tokens are supported (see #521)
        self.process_error(self.data)
        name = self.name + 'unauthorized_token_name'
        token = None
        unauthed_tokens = self.strategy.session_get(name, [])
        if not unauthed_tokens:
            raise AuthTokenError(self, 'Missing unauthorized token')
        token_param_name = self.OAUTH_TOKEN_PARAMETER_NAME
        data_token = self.data.get(token_param_name, 'no-token')
        for unauthed_token in unauthed_tokens:
            orig_unauthed_token = unauthed_token
            if not isinstance(unauthed_token, dict):
                unauthed_token = parse_qs(unauthed_token)
            if unauthed_token.get(token_param_name) == data_token:
                self.strategy.session_set(
                    name,
                    list(set(unauthed_tokens) - set([orig_unauthed_token])))
                token = unauthed_token
                break
        else:
            raise AuthTokenError(self, 'Incorrect tokens')

        try:
            access_token = self.access_token(token)
        except HTTPError as err:
            if err.response.status_code == 400:
                raise AuthCanceled(self)
            else:
                raise
        return self.do_auth(access_token, *args, **kwargs)
Exemplo n.º 2
0
    def validate_and_return_id_token(self, id_token):
        """
        Validates the id_token according to the steps at
        http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation.
        """
        client_id, _client_secret = self.get_key_and_secret()
        decryption_key = self.setting('ID_TOKEN_DECRYPTION_KEY')
        try:
            # Decode the JWT and raise an error if the secret is invalid or
            # the response has expired.
            id_token = jwt_decode(id_token,
                                  decryption_key,
                                  audience=client_id,
                                  issuer=self.ID_TOKEN_ISSUER,
                                  algorithms=['HS256'])
        except InvalidTokenError as err:
            raise AuthTokenError(self, err)

        # Verify the token was issued in the last 10 minutes
        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        if id_token['iat'] < (utc_timestamp - 600):
            raise AuthTokenError(self, 'Incorrect id_token: iat')

        # Validate the nonce to ensure the request was not modified
        nonce = id_token.get('nonce')
        if not nonce:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')

        nonce_obj = self.get_nonce(nonce)
        if nonce_obj:
            self.remove_nonce(nonce_obj.id)
        else:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')
        return id_token
Exemplo n.º 3
0
 def oauth_auth(self,
                token=None,
                oauth_verifier=None,
                signature_type=SIGNATURE_TYPE_AUTH_HEADER):
     key, secret = self.get_key_and_secret()
     oauth_verifier = oauth_verifier or self.data.get('oauth_verifier')
     if token:
         resource_owner_key = token.get('oauth_token')
         resource_owner_secret = token.get('oauth_token_secret')
         if not resource_owner_key:
             raise AuthTokenError(self, 'Missing oauth_token')
         if not resource_owner_secret:
             raise AuthTokenError(self, 'Missing oauth_token_secret')
     else:
         resource_owner_key = None
         resource_owner_secret = None
     # decoding='utf-8' produces errors with python-requests on Python3
     # since the final URL will be of type bytes
     decoding = None if six.PY3 else 'utf-8'
     state = self.get_or_create_state()
     return OAuth1(key,
                   secret,
                   resource_owner_key=resource_owner_key,
                   resource_owner_secret=resource_owner_secret,
                   callback_uri=self.get_redirect_uri(state),
                   verifier=oauth_verifier,
                   signature_type=signature_type,
                   decoding=decoding)
Exemplo n.º 4
0
    def _validate_id_token_times(self, id_token):
        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        # Verify the token has no expired yet, with x minutes leeway.
        if utc_timestamp > id_token['exp'] + self.CLOCK_LEEWAY:
            raise AuthTokenError(self, 'Signature has expired')

        if ('nbf' in id_token
                and utc_timestamp < id_token['nbf'] - self.CLOCK_LEEWAY):
            raise AuthTokenError(self, 'Incorrect id_token: nbf')

        # Verify the token was issued in the last x minutes.
        if abs(utc_timestamp - id_token['iat']) > self.CLOCK_LEEWAY:
            raise AuthTokenError(self, 'Incorrect id_token: iat')
Exemplo n.º 5
0
    def _validate_id_token_audience(self, id_token):
        client_id, _client_secret = self.get_key_and_secret()
        if isinstance(id_token['aud'], six.string_types):
            id_token['aud'] = [id_token['aud']]
        if client_id not in id_token['aud']:
            raise AuthTokenError(self, 'Invalid audience')

        # Pretty sure this is wrong. Text of spec says "This Claim is only
        # needed when the ID Token has a single audience value and that
        # audience is different than the authorized party."
        if len(id_token['aud']) > 1 and 'azp' not in id_token:
            raise AuthTokenError(self, 'Incorrect id_token: azp')

        if 'azp' in id_token and id_token['azp'] != client_id:
            raise AuthTokenError(self, 'Incorrect id_token: azp')
Exemplo n.º 6
0
    def validate_and_return_id_token(self, id_token):
        """
        Validates the id_token according to the steps at
        http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation.
        """
        client_id, _client_secret = self.get_key_and_secret()

        decode_kwargs = {
            'algorithms': getattr(self, "JWT_ALGO", None) or ('HS256', ),
            'audience': client_id,
            'issuer': self.ID_TOKEN_ISSUER,
            'key': self.setting('ID_TOKEN_DECRYPTION_KEY'),
            'options': {
                'verify_signature': True,
                'verify_exp': True,
                'verify_iat': True,
                'verify_aud': True,
                'verify_iss': True,
                'require_exp': True,
                'require_iat': True,
            },
        }
        decode_kwargs.update(self.setting('ID_TOKEN_JWT_DECODE_KWARGS', {}))

        try:
            # Decode the JWT and raise an error if the secret is invalid or
            # the response has expired.
            id_token = jwt_decode(id_token, **decode_kwargs)
        except InvalidTokenError as err:
            raise AuthTokenError(self, err)

        # Verify the token was issued within a specified amount of time
        iat_leeway = self.setting('ID_TOKEN_MAX_AGE', self.ID_TOKEN_MAX_AGE)
        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        if id_token['iat'] < (utc_timestamp - iat_leeway):
            raise AuthTokenError(self, 'Incorrect id_token: iat')

        # Validate the nonce to ensure the request was not modified
        nonce = id_token.get('nonce')
        if not nonce:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')

        nonce_obj = self.get_nonce(nonce)
        if nonce_obj:
            self.remove_nonce(nonce_obj.id)
        else:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')
        return id_token
Exemplo n.º 7
0
    def user_data(self, access_token, *args, **kwargs):
        """
        Returns a user's details from the userinfo endpoint.

        Called by PSA when building info about a user.
        """
        # TODO: Extend https://github.com/omab/python-social-auth/pull/747
        # to include a better version of this -- one which treats the response
        # as json if the Content-Type is application/json and treats it as a
        # JWS needing to be verified as below if it's application/jwt.
        #
        # To support all servers, that should also support JWEs.
        #
        # When PSA is eventually improves its OIDC support, this file can be
        # simplified.
        response = self.request(
            self.USERINFO_URL,
            headers={'Authorization': 'Bearer {0}'.format(access_token)})
        jws = response.content
        try:
            # Decode the JWT and raise an error if the sig is invalid.
            # Make sure to specify the expected algorithm to prevent attacks
            # that use an RSA public key as an HMAC secret key.
            user_info = JWS().verify_compact(
                jws.encode('utf-8'),
                keys=self.get_jwks_keys(),
                sigalg=self.setting('USERINFO_SIGNING_ALGORITHM'))
        except JWKESTException as e:
            raise AuthTokenError(
                self, ('User info error: Signature verification failed. '
                       'Reason: {0}'.format(e)))
        print(user_info)
        self.validate_userinfo_claims(user_info)
        return user_info
Exemplo n.º 8
0
    def validate_userinfo_claims(self, jwt):
        if jwt['iss'] != self.ID_TOKEN_ISSUER:
            raise AuthTokenError(self, 'Invalid issuer')

        if 'sub' not in jwt:
            raise AuthTokenError(self, 'Missing subject')

        client_id, _client_secret = self.get_key_and_secret()
        if isinstance(jwt['aud'], six.string_types):
            jwt['aud'] = [jwt['aud']]
        if client_id not in jwt['aud']:
            raise AuthTokenError(self, 'Invalid audience')

        # Verify this jwt was issued in the last x minutes.
        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        if abs(utc_timestamp - jwt['iat']) > self.CLOCK_LEEWAY:
            raise AuthTokenError(self, 'Incorrect userinfo jwt: iat')
Exemplo n.º 9
0
 def user_data(self, access_token, *args, **kwargs):
     response = kwargs.get('response')
     id_token = response.get('id_token')
     try:
         decoded_id_token = jwt_decode(id_token, verify=False)
     except (DecodeError, ExpiredSignature) as de:
         raise AuthTokenError(self, de)
     return decoded_id_token
Exemplo n.º 10
0
    def validate_id_token_claims(self, id_token):
        if id_token['iss'] != self.ID_TOKEN_ISSUER:
            raise AuthTokenError(self, 'Invalid issuer')

        if 'sub' not in id_token:
            raise AuthTokenError(self, 'Missing subject')

        client_id, _client_secret = self.get_key_and_secret()
        if isinstance(id_token['aud'], six.string_types):
            id_token['aud'] = [id_token['aud']]
        if client_id not in id_token['aud']:
            raise AuthTokenError(self, 'Invalid audience')

        # Pretty sure this is wrong. Text of spec says "This Claim is only
        # needed when the ID Token has a single audience value and that
        # audience is different than the authorized party."
        if len(id_token['aud']) > 1 and 'azp' not in id_token:
            raise AuthTokenError(self, 'Incorrect id_token: azp')

        if 'azp' in id_token and id_token['azp'] != client_id:
            raise AuthTokenError(self, 'Incorrect id_token: azp')

        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        # Verify the token has no expired yet, with x minutes leeway.
        if utc_timestamp > id_token['exp'] + self.CLOCK_LEEWAY:
            raise AuthTokenError(self, 'Signature has expired')

        if ('nbf' in id_token
                and utc_timestamp < id_token['nbf'] - self.CLOCK_LEEWAY):
            raise AuthTokenError(self, 'Incorrect id_token: nbf')

        # Verify the token was issued in the last x minutes.
        if abs(utc_timestamp - id_token['iat']) > self.CLOCK_LEEWAY:
            raise AuthTokenError(self, 'Incorrect id_token: iat')

        # Validate the nonce to ensure the request was not modified
        nonce = id_token.get('nonce')
        if not nonce:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')

        nonce_obj = self.get_nonce(nonce)
        if nonce_obj:
            self.remove_nonce(nonce_obj.id)
        else:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')
Exemplo n.º 11
0
    def validate_id_token_claims(self, id_token):
        if id_token['iss'] != self.ID_TOKEN_ISSUER:
            raise AuthTokenError(self, 'Invalid issuer')

        if 'sub' not in id_token:
            raise AuthTokenError(self, 'Missing subject')

        self._validate_id_token_audience(id_token)

        self._validate_id_token_times(id_token)

        # Validate the nonce to ensure the request was not modified
        nonce = id_token.get('nonce')
        if not nonce:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')

        nonce_obj = self.get_nonce(nonce)
        if nonce_obj:
            self.remove_nonce(nonce_obj.id)
        else:
            raise AuthTokenError(self, 'Incorrect id_token: nonce')
Exemplo n.º 12
0
    def get_unauthorized_token(self):
        name = self.name + self.UNATHORIZED_TOKEN_SUFIX
        unauthed_tokens = self.strategy.session_get(name, [])
        if not unauthed_tokens:
            raise AuthTokenError(self, 'Missing unauthorized token')

        data_token = self.data.get(self.OAUTH_TOKEN_PARAMETER_NAME)

        if data_token is None:
            raise AuthTokenError(self, 'Missing unauthorized token')

        token = None
        for utoken in unauthed_tokens:
            orig_utoken = utoken
            if not isinstance(utoken, dict):
                utoken = parse_qs(utoken)
            if utoken.get(self.OAUTH_TOKEN_PARAMETER_NAME) == data_token:
                self.strategy.session_set(
                    name, list(set(unauthed_tokens) - set([orig_utoken])))
                token = utoken
                break
        else:
            raise AuthTokenError(self, 'Incorrect tokens')
        return token
Exemplo n.º 13
0
    def do_auth(self, access_token, *args, **kwargs):
        """ Exchange bearer token before finishing auth. """
        access_token = json.loads(access_token)

        if not self.validate_bearer_token(access_token):
            raise AuthTokenError(self)

        try:
            access_token = self.access_token(access_token)
        except HTTPError as err:
            if err.response.status_code == 400:
                raise AuthCanceled(self)
            else:
                raise

        return super(LinkedinJSAPIOAuth, self).do_auth(access_token)
Exemplo n.º 14
0
    def validate_and_return_id_token(self, jws):
        """
        Validates the id_token according to the spec.

        http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation.
        """
        try:
            # Decode the JWT and raise an error if the sig is invalid.
            # Make sure to specify the expected algorithm to prevent attacks
            # that used an RSA public key as an HMAC secret key.
            id_token = JWS().verify_compact(
                jws.encode('utf-8'),
                keys=self.get_jwks_keys(),
                sigalg=self.setting('TOKEN_SIGNING_ALGORITHM'))
        except JWKESTException as e:
            raise AuthTokenError(
                self, ('User info error: Signature verification failed. '
                       'Reason: {0}'.format(e)))
        self.validate_id_token_claims(id_token)
        return id_token
Exemplo n.º 15
0
def save_profile(backend,
                 strategy,
                 uid,
                 response={} or object(),
                 details={},
                 *args,
                 **kwargs):
    print('response: ' + str(response))
    print('details: ' + str(details))
    print('args: ' + str(args))
    print('kwargs: ' + str(kwargs))
    user = Account.objects.filter(auth_via=backend.name, social_id=uid).first()
    if user:
        login_user = auth.authenticate(username=details['email'],
                                       password='******')
        social = UserSocialAuth(login_user, uid, backend.name)
        strategy.session_set('email', details['email'])
    else:
        if details['email'] == '':
            raise AuthTokenError('error')

        avatar = '/media/default_avatar.png'

        if response['photo']:
            avatar = response['photo']

        # reg user
        hasher = get_hasher('default')

        if 'bdate' in response and len(response['bdate'].split('.')) != 3:
            date = response['bdate'].split('.')
            date = date[-1] + '.' + date[1] + '.' + date[0]
            try:
                Account.objects.create(password=hasher.encode(
                    '55', hasher.salt()),
                                       email=details['email'],
                                       first_name=details['first_name'],
                                       last_name=details['last_name'],
                                       auth_via=backend.name,
                                       access_token=response['access_token'],
                                       social_id=uid,
                                       avatar=avatar,
                                       sex=response['sex'],
                                       birthday=date)
            except IntegrityError:
                raise AuthMissingParameter
        else:
            try:
                Account.objects.create(password=hasher.encode(
                    '55', hasher.salt()),
                                       email=details['email'],
                                       first_name=details['first_name'],
                                       last_name=details['last_name'],
                                       auth_via=backend.name,
                                       access_token=response['access_token'],
                                       social_id=uid,
                                       avatar=avatar,
                                       sex=response['sex'])
            except IntegrityError:
                raise AuthMissingParameter

        # login user
        login_user = auth.authenticate(username=details['email'],
                                       password='******')
        social = UserSocialAuth(login_user, uid, backend.name)
        strategy.session_set('email', details['email'])

    return {'user': login_user, 'social': social}
Exemplo n.º 16
0
class AuthTokenErrorTest(BaseExceptionTestCase):
    exception = AuthTokenError('foobar', 'Incorrect tokens')
    expected_message = 'Token error: Incorrect tokens'