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)
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
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)
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')
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')
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
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
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')
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
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')
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')
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
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)
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
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}
class AuthTokenErrorTest(BaseExceptionTestCase): exception = AuthTokenError('foobar', 'Incorrect tokens') expected_message = 'Token error: Incorrect tokens'