def test_disconnect_when_more_than_one_associated_token_for_provider(self): self.trans.user = User(email=self.test_email, username=self.test_username) custos_authnz_token1 = CustosAuthnzToken( user=self.trans.user, external_user_id=self.test_user_id + "1", provider=self.custos_authnz.config['provider'], access_token=self.test_access_token, id_token=self.test_id_token, refresh_token=self.test_refresh_token, expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), ) custos_authnz_token2 = CustosAuthnzToken( user=self.trans.user, external_user_id=self.test_user_id + "2", provider=self.custos_authnz.config['provider'], access_token=self.test_access_token, id_token=self.test_id_token, refresh_token=self.test_refresh_token, expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), ) self.trans.user.custos_auth = [custos_authnz_token1, custos_authnz_token2] success, message, redirect_uri = self.custos_authnz.disconnect("Custos", self.trans, "/") self.assertEqual(0, len(self.trans.sa_session.deleted)) self.assertFalse(self.trans.sa_session.flush_called) self.assertFalse(success) self.assertNotEqual("", message) self.assertIsNone(redirect_uri)
def test_disconnect(self): custos_authnz_token = CustosAuthnzToken( user=User(email=self.test_email, username=self.test_username), external_user_id=self.test_user_id, provider=self.custos_authnz.config['provider'], access_token=self.test_access_token, id_token=self.test_id_token, refresh_token=self.test_refresh_token, expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), ) self.trans.user = custos_authnz_token.user self.trans.user.custos_auth = [custos_authnz_token] success, message, redirect_uri = self.custos_authnz.disconnect( "Custos", self.trans, "/") self.assertEqual(1, len(self.trans.sa_session.deleted)) deleted_token = self.trans.sa_session.deleted[0] self.assertIs(custos_authnz_token, deleted_token) self.assertTrue(self.trans.sa_session.flush_called) self.assertTrue(success) self.assertEqual("", message) self.assertEqual("/", redirect_uri)
def test_callback_galaxy_user_not_created_when_custos_authnz_token_exists(self): self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) old_access_token = "old-access-token" old_id_token = "old-id-token" old_refresh_token = "old-refresh-token" old_expiration_time = datetime.now() - timedelta(days=1) old_refresh_expiration_time = datetime.now() - timedelta(hours=3) existing_custos_authnz_token = CustosAuthnzToken( user=User(email=self.test_email, username=self.test_username), external_user_id=self.test_user_id, provider=self.custos_authnz.config['provider'], access_token=old_access_token, id_token=old_id_token, refresh_token=old_refresh_token, expiration_time=old_expiration_time, refresh_expiration_time=old_refresh_expiration_time, ) self.trans.sa_session._query.custos_authnz_token = existing_custos_authnz_token self.assertIsNotNone( self.trans.sa_session.query(CustosAuthnzToken) .filter_by(external_user_id=self.test_user_id, provider=self.custos_authnz.config['provider']) .one_or_none() ), self.assertEqual(0, len(self.trans.sa_session.items)) login_redirect_url, user = self.custos_authnz.callback( state_token="xxx", authz_code=self.test_code, trans=self.trans, login_redirect_url="http://localhost:8000/") self.assertTrue(self._fetch_token_called) self.assertTrue(self._get_userinfo_called) # Make sure query was called with correct parameters self.assertEqual(self.test_user_id, self.trans.sa_session._query.external_user_id) self.assertEqual(self.custos_authnz.config['provider'], self.trans.sa_session._query.provider) self.assertEqual(1, len(self.trans.sa_session.items), "Session has updated CustosAuthnzToken") session_custos_authnz_token = self.trans.sa_session.items[0] self.assertIsInstance(session_custos_authnz_token, CustosAuthnzToken) self.assertIs(existing_custos_authnz_token, session_custos_authnz_token, "existing CustosAuthnzToken should be updated") # Verify both that existing CustosAuthnzToken has the correct values and different values than before self.assertEqual(self.test_access_token, session_custos_authnz_token.access_token) self.assertNotEqual(old_access_token, session_custos_authnz_token.access_token) self.assertEqual(self.test_id_token, session_custos_authnz_token.id_token) self.assertNotEqual(old_id_token, session_custos_authnz_token.id_token) self.assertEqual(self.test_refresh_token, session_custos_authnz_token.refresh_token) self.assertNotEqual(old_refresh_token, session_custos_authnz_token.refresh_token) expected_expiration_time = datetime.now() + timedelta(seconds=self.test_expires_in) expiration_timedelta = expected_expiration_time - session_custos_authnz_token.expiration_time self.assertTrue(expiration_timedelta.total_seconds() < 1) self.assertNotEqual(old_expiration_time, session_custos_authnz_token.expiration_time) expected_refresh_expiration_time = datetime.now() + timedelta(seconds=self.test_refresh_expires_in) refresh_expiration_timedelta = expected_refresh_expiration_time - session_custos_authnz_token.refresh_expiration_time self.assertTrue(refresh_expiration_timedelta.total_seconds() < 1) self.assertNotEqual(old_refresh_expiration_time, session_custos_authnz_token.refresh_expiration_time) self.assertTrue(self.trans.sa_session.flush_called)
def callback(self, state_token, authz_code, trans, login_redirect_url): # Take state value to validate from token. OAuth2Session.fetch_token # will validate that the state query parameter value on the URL matches # this value. state_cookie = trans.get_cookie(name=STATE_COOKIE_NAME) oauth2_session = self._create_oauth2_session(state=state_cookie) token = self._fetch_token(oauth2_session, trans) log.debug("token={}".format(json.dumps(token, indent=True))) access_token = token['access_token'] id_token = token['id_token'] refresh_token = token[ 'refresh_token'] if 'refresh_token' in token else None expiration_time = datetime.now() + timedelta( seconds=token['expires_in']) refresh_expiration_time = ( datetime.now() + timedelta(seconds=token['refresh_expires_in']) ) if 'refresh_expires_in' in token else None # Get nonce from token['id_token'] and validate. 'nonce' in the # id_token is a hash of the nonce stored in the NONCE_COOKIE_NAME # cookie. id_token_decoded = jwt.decode(id_token, verify=False) nonce_hash = id_token_decoded['nonce'] self._validate_nonce(trans, nonce_hash) # Get userinfo and lookup/create Galaxy user record userinfo = self._get_userinfo(oauth2_session) log.debug("userinfo={}".format(json.dumps(userinfo, indent=True))) username = userinfo['preferred_username'] email = userinfo['email'] user_id = userinfo['sub'] # Create or update custos_authnz_token record custos_authnz_token = self._get_custos_authnz_token( trans.sa_session, user_id, self.config['provider']) if custos_authnz_token is None: user = self._get_current_user(trans) if not user: user = self._create_user(trans.sa_session, username, email) custos_authnz_token = CustosAuthnzToken( user=user, external_user_id=user_id, provider=self.config['provider'], access_token=access_token, id_token=id_token, refresh_token=refresh_token, expiration_time=expiration_time, refresh_expiration_time=refresh_expiration_time) else: custos_authnz_token.access_token = access_token custos_authnz_token.id_token = id_token custos_authnz_token.refresh_token = refresh_token custos_authnz_token.expiration_time = expiration_time custos_authnz_token.refresh_expiration_time = refresh_expiration_time trans.sa_session.add(custos_authnz_token) trans.sa_session.flush() return login_redirect_url, custos_authnz_token.user
def create_user(self, token, trans, login_redirect_url): token_dict = json.loads(token) access_token = token_dict['access_token'] id_token = token_dict['id_token'] refresh_token = token_dict[ 'refresh_token'] if 'refresh_token' in token_dict else None expiration_time = datetime.now() + timedelta(seconds=token_dict.get( 'expires_in', 3600)) # might be a problem cause times no long valid refresh_expiration_time = ( datetime.now() + timedelta(seconds=token_dict['refresh_expires_in']) ) if 'refresh_expires_in' in token_dict else None # Get nonce from token['id_token'] and validate. 'nonce' in the # id_token is a hash of the nonce stored in the NONCE_COOKIE_NAME # cookie. userinfo = self._decode_token_no_signature(id_token) # Get userinfo and create Galaxy user record email = userinfo['email'] # Check if username if already taken username = userinfo.get('preferred_username', self._generate_username(trans, email)) user_id = userinfo['sub'] user = trans.app.user_manager.create(email=email, username=username) if trans.app.config.user_activation_on: trans.app.user_manager.send_activation_email( trans, email, username) custos_authnz_token = CustosAuthnzToken( user=user, external_user_id=user_id, provider=self.config['provider'], access_token=access_token, id_token=id_token, refresh_token=refresh_token, expiration_time=expiration_time, refresh_expiration_time=refresh_expiration_time) trans.sa_session.add(user) trans.sa_session.add(custos_authnz_token) trans.sa_session.flush() return login_redirect_url, user
def test_callback_verify_with_state_cookie(self): """Verify that state from cookie is passed to OAuth2Session constructor.""" self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) old_access_token = "old-access-token" old_id_token = "old-id-token" old_refresh_token = "old-refresh-token" old_expiration_time = datetime.now() - timedelta(days=1) old_refresh_expiration_time = datetime.now() - timedelta(hours=3) existing_custos_authnz_token = CustosAuthnzToken( user=User(email=self.test_email, username=self.test_username), external_user_id=self.test_user_id, provider=self.custos_authnz.config['provider'], access_token=old_access_token, id_token=old_id_token, refresh_token=old_refresh_token, expiration_time=old_expiration_time, refresh_expiration_time=old_refresh_expiration_time, ) self.trans.sa_session._query.custos_authnz_token = existing_custos_authnz_token self.assertIsNotNone( self.trans.sa_session.query(CustosAuthnzToken).filter_by( external_user_id=self.test_user_id, provider=self.custos_authnz.config['provider']).one_or_none()) self.trans.sa_session._query.user = User(email=self.test_email, username=self.test_username) # Mock _create_oauth2_session to make sure it is created with cookie state token self.mock_create_oauth2_session(self.custos_authnz) # Intentionally passing a bad state_token to make sure that code under # test uses the state cookie instead when creating the OAuth2Session login_redirect_url, user = self.custos_authnz.callback( state_token="xxx", authz_code=self.test_code, trans=self.trans, login_redirect_url="http://localhost:8000/") self.assertTrue(self._create_oauth2_session_called) self.assertTrue(self._fetch_token_called) self.assertTrue(self._get_userinfo_called) self.assertEqual(login_redirect_url, "http://localhost:8000/") self.assertIsNotNone(user)
def callback(self, state_token, authz_code, trans, login_redirect_url): # Take state value to validate from token. OAuth2Session.fetch_token # will validate that the state query parameter value on the URL matches # this value. state_cookie = trans.get_cookie(name=STATE_COOKIE_NAME) oauth2_session = self._create_oauth2_session(state=state_cookie) token = self._fetch_token(oauth2_session, trans) log.debug("token={}".format(json.dumps(token, indent=True))) access_token = token['access_token'] id_token = token['id_token'] refresh_token = token[ 'refresh_token'] if 'refresh_token' in token else None expiration_time = datetime.now() + timedelta( seconds=token.get('expires_in', 3600)) refresh_expiration_time = ( datetime.now() + timedelta(seconds=token['refresh_expires_in']) ) if 'refresh_expires_in' in token else None # Get nonce from token['id_token'] and validate. 'nonce' in the # id_token is a hash of the nonce stored in the NONCE_COOKIE_NAME # cookie. id_token_decoded = jwt.decode(id_token, verify=False) nonce_hash = id_token_decoded['nonce'] self._validate_nonce(trans, nonce_hash) # Get userinfo and lookup/create Galaxy user record if id_token_decoded.get('email', None): userinfo = id_token_decoded else: userinfo = self._get_userinfo(oauth2_session) log.debug("userinfo={}".format(json.dumps(userinfo, indent=True))) email = userinfo['email'] # Check if username if already taken username = userinfo.get('preferred_username', self._generate_username(trans, email)) user_id = userinfo['sub'] # Create or update custos_authnz_token record custos_authnz_token = self._get_custos_authnz_token( trans.sa_session, user_id, self.config['provider']) if custos_authnz_token is None: user = trans.user if not user: existing_user = trans.sa_session.query(User).filter_by( email=email).first() if existing_user: # If there is only a single external authentication # provider in use, trust the user provided and # automatically associate. # TODO: Future work will expand on this and provide an # interface for when there are multiple auth providers # allowing explicit authenticated association. if (trans.app.config.enable_oidc and len(trans.app.config.oidc) == 1 and len( trans.app.auth_manager.authenticators) == 0): user = existing_user else: message = 'There already exists a user this email. To associate this external login, you must first be logged in as that existing account.' log.exception(message) raise exceptions.AuthenticationFailed(message) else: user = trans.app.user_manager.create(email=email, username=username) trans.sa_session.add(user) trans.sa_session.flush() custos_authnz_token = CustosAuthnzToken( user=user, external_user_id=user_id, provider=self.config['provider'], access_token=access_token, id_token=id_token, refresh_token=refresh_token, expiration_time=expiration_time, refresh_expiration_time=refresh_expiration_time) else: custos_authnz_token.access_token = access_token custos_authnz_token.id_token = id_token custos_authnz_token.refresh_token = refresh_token custos_authnz_token.expiration_time = expiration_time custos_authnz_token.refresh_expiration_time = refresh_expiration_time trans.sa_session.add(custos_authnz_token) trans.sa_session.flush() return login_redirect_url, custos_authnz_token.user