class TestTokenValidation(AuthMiddlewareTest): @mock.patch.object( Token, 'get', mock.Mock(return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE))) def test_token_validation_token_in_headers(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=False) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, 'get', mock.Mock(return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE))) def test_token_validation_token_in_query_params(self): response = self.app.get('/v1/actions?x-auth-token=%s' % (TOKEN), expect_errors=False) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, 'get', mock.Mock(return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=PAST))) def test_token_expired(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object( Token, 'get', mock.MagicMock(side_effect=TokenNotFoundError())) def test_token_not_found(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=True) self.assertEqual(response.status_int, 401) def test_token_not_provided(self): response = self.app.get('/v1/actions', expect_errors=True) self.assertEqual(response.status_int, 401)
class TestTokenBasedAuth(FunctionalTest): enable_auth = True @mock.patch.object(Token, 'get', mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE))) def test_token_validation_token_in_headers(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=False) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 200) @mock.patch.object(Token, 'get', mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE))) def test_token_validation_token_in_query_params(self): response = self.app.get('/v1/actions?x-auth-token=%s' % (TOKEN), expect_errors=False) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 200) @mock.patch.object(Token, 'get', mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=PAST))) def test_token_expired(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401) @mock.patch.object(Token, 'get', mock.MagicMock(side_effect=TokenNotFoundError())) def test_token_not_found(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN}, expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401) def test_token_not_provided(self): response = self.app.get('/v1/actions', expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401)
class TestApiKeyBasedAuth(FunctionalTest): enable_auth = True apikey1 = None apikey_disabled = None @classmethod def setUpClass(cls): super(TestApiKeyBasedAuth, cls).setUpClass() models = FixturesLoader().save_fixtures_to_db(fixtures_pack=FIXTURES_PACK, fixtures_dict=TEST_MODELS) cls.apikey1 = models['apikeys']['apikey1.yaml'] cls.apikey_disabled = models['apikeys']['apikey_disabled.yaml'] @mock.patch.object(User, 'get_by_name', mock.Mock(return_value=UserDB(name='bill'))) def test_apikey_validation_apikey_in_headers(self): response = self.app.get('/v1/actions', headers={'St2-Api-key': KEY1_KEY}, expect_errors=False) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 200) @mock.patch.object(User, 'get_by_name', mock.Mock(return_value=UserDB(name='bill'))) def test_apikey_validation_apikey_in_query_params(self): response = self.app.get('/v1/actions?st2-api-key=%s' % (KEY1_KEY), expect_errors=False) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 200) def test_apikey_disabled(self): response = self.app.get('/v1/actions', headers={'St2-Api-key': DISABLED_KEY}, expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401) self.assertEqual(response.json_body['faultstring'], 'Unauthorized - API key is disabled.') def test_apikey_not_found(self): response = self.app.get('/v1/actions', headers={'St2-Api-key': 'UNKNOWN'}, expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401) self.assertRegexpMatches(response.json_body['faultstring'], '^Unauthorized - ApiKey with key_hash=([a-zA-Z0-9]+) not found.$') @mock.patch.object( Token, 'get', mock.Mock(return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE))) @mock.patch.object( ApiKey, 'get', mock.Mock(return_value=ApiKeyDB(user=USER, key_hash=KEY1_KEY, enabled=True))) @mock.patch.object(User, 'get_by_name', mock.Mock(return_value=USER_DB)) def test_multiple_auth_sources(self): response = self.app.get('/v1/actions', headers={'X-Auth-Token': TOKEN, 'St2-Api-key': KEY1_KEY}, expect_errors=True) self.assertTrue('application/json' in response.headers['content-type']) self.assertEqual(response.status_int, 401) self.assertEqual(response.json_body['faultstring'], 'Unauthorized - Only one of Token or API key expected.')
def create_token(username, ttl=None, metadata=None, add_missing_user=True): """ :param username: Username of the user to create the token for. If the account for this user doesn't exist yet it will be created. :type username: ``str`` :param ttl: Token TTL (in seconds). :type ttl: ``int`` :param metadata: Optional metadata to associate with the token. :type metadata: ``dict`` :param add_missing_user: Add the user given by `username` if they don't exist :type add_missing_user: ``bool`` """ if ttl: if ttl > cfg.CONF.auth.token_ttl: msg = 'TTL specified %s is greater than max allowed %s.' % ( ttl, cfg.CONF.auth.token_ttl) raise TTLTooLargeException(msg) else: ttl = cfg.CONF.auth.token_ttl if username: try: User.get_by_name(username) except: if add_missing_user: user_db = UserDB(name=username) User.add_or_update(user_db) extra = {'username': username, 'user': user_db} LOG.audit('Registered new user "%s".' % (username), extra=extra) else: raise UserNotFoundError() token = uuid.uuid4().hex expiry = date_utils.get_datetime_utc_now() + datetime.timedelta( seconds=ttl) token = TokenDB(user=username, token=token, expiry=expiry, metadata=metadata) Token.add_or_update(token) username_string = username if username else 'an anonymous user' token_expire_string = isotime.format(expiry, offset=False) extra = {'username': username, 'token_expiration': token_expire_string} LOG.audit('Access granted to "%s" with the token set to expire at "%s".' % (username_string, token_expire_string), extra=extra) return token
def test_cache_auth_token_invalid_permissions(self): shell = Shell() username = '******' cached_token_path = shell._get_cached_token_path_for_user( username=username) expiry = datetime.datetime.utcnow() + datetime.timedelta(seconds=30) token_db = TokenDB(user=username, token='fyeah', expiry=expiry) cached_token_path = shell._get_cached_token_path_for_user( username=username) data = { 'token': 'yayvalid', 'expire_timestamp': (int(time.time()) + 20) } with open(cached_token_path, 'w') as fp: fp.write(json.dumps(data)) # 1. Current user has no write access to the parent directory os.chmod(self._mock_config_directory_path, 0o000) shell.LOG = mock.Mock() shell._cache_auth_token(token_obj=token_db) self.assertEqual(shell.LOG.warn.call_count, 1) log_message = shell.LOG.warn.call_args[0][0] expected_msg = ( 'Unable to write token to .*? doesn\'t have write access to the parent ' 'directory') self.assertRegexpMatches(log_message, expected_msg) # 2. Current user has no write access to the cached token file os.chmod(self._mock_config_directory_path, 0o777) # nosec os.chmod(cached_token_path, 0o000) shell.LOG = mock.Mock() shell._cache_auth_token(token_obj=token_db) self.assertEqual(shell.LOG.warn.call_count, 1) log_message = shell.LOG.warn.call_args[0][0] expected_msg = ( 'Unable to write token to .*? doesn\'t have write access to this file' ) self.assertRegexpMatches(log_message, expected_msg)
def test_cache_auth_token_success(self): client = Client() shell = Shell() username = '******' password = '******' expiry = datetime.datetime.utcnow() + datetime.timedelta(seconds=30) result = shell._get_cached_auth_token(client=client, username=username, password=password) self.assertEqual(result, None) token_db = TokenDB(user=username, token='fyeah', expiry=expiry) shell._cache_auth_token(token_obj=token_db) result = shell._get_cached_auth_token(client=client, username=username, password=password) self.assertEqual(result, 'fyeah')
} } } LIVE_ACTION_1 = { 'action': 'sixpack.st2.dummy.action1', 'parameters': { 'hosts': 'localhost', 'cmd': 'uname -a', 'd': SUPER_SECRET_PARAMETER } } NOW = date_utils.get_datetime_utc_now() EXPIRY = NOW + datetime.timedelta(seconds=300) SYS_TOKEN = TokenDB(id=bson.ObjectId(), user='******', token=uuid.uuid4().hex, expiry=EXPIRY) USR_TOKEN = TokenDB(id=bson.ObjectId(), user='******', token=uuid.uuid4().hex, expiry=EXPIRY) FIXTURES_PACK = 'generic' FIXTURES = { 'users': ['system_user.yaml', 'token_user.yaml'] } def mock_get_token(*args, **kwargs): if args[0] == SYS_TOKEN.token: return SYS_TOKEN return USR_TOKEN @mock.patch.object(PoolPublisher, 'publish', mock.MagicMock())
"cmd": "uname -a", "d": SUPER_SECRET_PARAMETER, }, } LIVE_ACTION_DEFAULT_ENCRYPT = { "action": "starterpack.st2.dummy.default_encrypted_value", } # NOTE: We use a longer expiry time because this variable is initialized on module import (aka # when nosetests or similar imports this module before running the tests. # Depending on when the import happens and when the tests actually run, token could already expire # by that time and the tests would fail. NOW = date_utils.get_datetime_utc_now() EXPIRY = NOW + datetime.timedelta(seconds=1000) SYS_TOKEN = TokenDB(id=bson.ObjectId(), user="******", token=uuid.uuid4().hex, expiry=EXPIRY) USR_TOKEN = TokenDB(id=bson.ObjectId(), user="******", token=uuid.uuid4().hex, expiry=EXPIRY) FIXTURES_PACK = "generic" FIXTURES = {"users": ["system_user.yaml", "token_user.yaml"]} # These parameters are used for the tests of getting value from datastore and decrypting it at # Jinja expression in a action metadata definition. TEST_USER = UserDB(name="user1") TEST_TOKEN = TokenDB(id=bson.ObjectId(), user=TEST_USER, token=uuid.uuid4().hex,
class TestTokenBasedAuth(FunctionalTest): enable_auth = True @mock.patch.object( Token, "get", mock.Mock( return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE) ), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_headers(self): response = self.app.get( "/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock( return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE) ), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_query_params(self): response = self.app.get( "/v1/actions?x-auth-token=%s" % (TOKEN), expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock( return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE) ), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_cookies(self): response = self.app.get( "/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) with mock.patch.object(self.app.cookiejar, "clear", return_value=None): response = self.app.get("/v1/actions", expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=PAST)), ) def test_token_expired(self): response = self.app.get( "/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=True ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) @mock.patch.object(Token, "get", mock.MagicMock(side_effect=TokenNotFoundError())) def test_token_not_found(self): response = self.app.get( "/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=True ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) def test_token_not_provided(self): response = self.app.get("/v1/actions", expect_errors=True) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401)
class TestApiKeyBasedAuth(FunctionalTest): enable_auth = True apikey1 = None apikey_disabled = None @classmethod def setUpClass(cls): super(TestApiKeyBasedAuth, cls).setUpClass() models = FixturesLoader().save_fixtures_to_db( fixtures_pack=FIXTURES_PACK, fixtures_dict=TEST_MODELS ) cls.apikey1 = models["apikeys"]["apikey1.yaml"] cls.apikey_disabled = models["apikeys"]["apikey_disabled.yaml"] @mock.patch.object(User, "get_by_name", mock.Mock(return_value=UserDB(name="bill"))) def test_apikey_validation_apikey_in_headers(self): response = self.app.get( "/v1/actions", headers={"St2-Api-key": KEY1_KEY}, expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=UserDB(name="bill"))) def test_apikey_validation_apikey_in_query_params(self): response = self.app.get( "/v1/actions?st2-api-key=%s" % (KEY1_KEY), expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=UserDB(name="bill"))) def test_apikey_validation_apikey_in_cookies(self): response = self.app.get( "/v1/actions", headers={"St2-Api-key": KEY1_KEY}, expect_errors=False ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) with mock.patch.object(self.app.cookiejar, "clear", return_value=None): response = self.app.get("/v1/actions", expect_errors=True) self.assertEqual(response.status_int, 401) self.assertEqual( response.json_body["faultstring"], "Unauthorized - One of Token or API key required.", ) def test_apikey_disabled(self): response = self.app.get( "/v1/actions", headers={"St2-Api-key": DISABLED_KEY}, expect_errors=True ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) self.assertEqual( response.json_body["faultstring"], "Unauthorized - API key is disabled." ) def test_apikey_not_found(self): response = self.app.get( "/v1/actions", headers={"St2-Api-key": "UNKNOWN"}, expect_errors=True ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) self.assertRegexpMatches( response.json_body["faultstring"], "^Unauthorized - ApiKey with key_hash=([a-zA-Z0-9]+) not found.$", ) @mock.patch.object( Token, "get", mock.Mock( return_value=TokenDB(id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE) ), ) @mock.patch.object( ApiKey, "get", mock.Mock(return_value=ApiKeyDB(user=USER, key_hash=KEY1_KEY, enabled=True)), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_multiple_auth_sources(self): response = self.app.get( "/v1/actions", headers={"X-Auth-Token": TOKEN, "St2-Api-key": KEY1_KEY}, expect_errors=True, ) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200)
class TestTokenController(FunctionalTest): @classmethod def setUpClass(cls, **kwargs): kwargs['extra_environ'] = { 'REMOTE_USER': USERNAME } super(TestTokenController, cls).setUpClass(**kwargs) def test_token_model(self): dt = date_utils.get_datetime_utc_now() tk1 = TokenAPI(user='******', token=uuid.uuid4().hex, expiry=isotime.format(dt, offset=False)) tkdb1 = TokenAPI.to_model(tk1) self.assertIsNotNone(tkdb1) self.assertIsInstance(tkdb1, TokenDB) self.assertEqual(tkdb1.user, tk1.user) self.assertEqual(tkdb1.token, tk1.token) self.assertEqual(tkdb1.expiry, isotime.parse(tk1.expiry)) tkdb2 = Token.add_or_update(tkdb1) self.assertEqual(tkdb1, tkdb2) self.assertIsNotNone(tkdb2.id) tk2 = TokenAPI.from_model(tkdb2) self.assertEqual(tk2.user, tk1.user) self.assertEqual(tk2.token, tk1.token) self.assertEqual(tk2.expiry, tk1.expiry) def test_token_model_null_token(self): dt = date_utils.get_datetime_utc_now() tk = TokenAPI(user='******', token=None, expiry=isotime.format(dt)) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def test_token_model_null_user(self): dt = date_utils.get_datetime_utc_now() tk = TokenAPI(user=None, token=uuid.uuid4().hex, expiry=isotime.format(dt)) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def test_token_model_null_expiry(self): tk = TokenAPI(user='******', token=uuid.uuid4().hex, expiry=None) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def _test_token_post(self, path=TOKEN_V1_PATH): ttl = cfg.CONF.auth.token_ttl timestamp = date_utils.get_datetime_utc_now() response = self.app.post_json(path, {}, expect_errors=False) expected_expiry = date_utils.get_datetime_utc_now() + datetime.timedelta(seconds=ttl) expected_expiry = date_utils.add_utc_tz(expected_expiry) self.assertEqual(response.status_int, 201) self.assertIsNotNone(response.json['token']) self.assertEqual(response.json['user'], USERNAME) actual_expiry = isotime.parse(response.json['expiry']) self.assertLess(timestamp, actual_expiry) self.assertLess(actual_expiry, expected_expiry) return response def test_token_post_unauthorized(self): response = self.app.post_json(TOKEN_V1_PATH, {}, expect_errors=True, extra_environ={ 'REMOTE_USER': '' }) self.assertEqual(response.status_int, 401) @mock.patch.object( User, 'get_by_name', mock.MagicMock(side_effect=Exception())) @mock.patch.object( User, 'add_or_update', mock.Mock(return_value=UserDB(name=USERNAME))) def test_token_post_new_user(self): self._test_token_post() @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_existing_user(self): self._test_token_post() @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_success_x_api_url_header_value(self): # auth.api_url option is explicitly set cfg.CONF.set_override('api_url', override='https://example.com', group='auth') resp = self._test_token_post() self.assertEqual(resp.headers['X-API-URL'], 'https://example.com') # auth.api_url option is not set, url is inferred from listen host and port cfg.CONF.set_override('api_url', override=None, group='auth') resp = self._test_token_post() self.assertEqual(resp.headers['X-API-URL'], 'http://127.0.0.1:9101') @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_default_url_path(self): self._test_token_post(path=TOKEN_DEFAULT_PATH) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_ttl(self): timestamp = date_utils.add_utc_tz(date_utils.get_datetime_utc_now()) response = self.app.post_json(TOKEN_V1_PATH, {'ttl': 60}, expect_errors=False) expected_expiry = date_utils.get_datetime_utc_now() + datetime.timedelta(seconds=60) self.assertEqual(response.status_int, 201) actual_expiry = isotime.parse(response.json['expiry']) self.assertLess(timestamp, actual_expiry) self.assertLess(actual_expiry, expected_expiry) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_no_data_in_body_text_plain_context_type_used(self): response = self.app.post(TOKEN_V1_PATH, expect_errors=False, content_type='text/plain') self.assertEqual(response.status_int, 201) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_ttl_over_policy(self): ttl = cfg.CONF.auth.token_ttl response = self.app.post_json(TOKEN_V1_PATH, {'ttl': ttl + 60}, expect_errors=True) self.assertEqual(response.status_int, 400) message = 'TTL specified %s is greater than max allowed %s.' % ( ttl + 60, ttl ) self.assertEqual(response.json['faultstring'], message) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_bad_ttl(self): response = self.app.post_json(TOKEN_V1_PATH, {'ttl': -1}, expect_errors=True) self.assertEqual(response.status_int, 400) response = self.app.post_json(TOKEN_V1_PATH, {'ttl': 0}, expect_errors=True) self.assertEqual(response.status_int, 400) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because an API key or token is not provided in header. data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized_bad_api_key(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because the API key is bad. headers = {'St2-Api-Key': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized_bad_token(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because the token is bad. headers = {'X-Auth-Token': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) @mock.patch.object( ApiKey, 'get', mock.MagicMock(return_value=ApiKeyDB(user=USERNAME, key_hash='foobar'))) def test_token_get_auth_with_api_key(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. Use an API key to authenticate with the st2 auth get token endpoint. headers = {'St2-Api-Key': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 200) self.assertTrue(response.json['valid']) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_auth_with_token(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, {}, expect_errors=False) # Verify the token. Use a token to authenticate with the st2 auth get token endpoint. headers = {'X-Auth-Token': str(response.json['token'])} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 200) self.assertTrue(response.json['valid']) @mock.patch.object( User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) @mock.patch.object( ApiKey, 'get', mock.MagicMock(return_value=ApiKeyDB(user=USERNAME, key_hash='foobar'))) @mock.patch.object( Token, 'get', mock.MagicMock( return_value=TokenDB( user=USERNAME, token='12345', expiry=date_utils.get_datetime_utc_now() - datetime.timedelta(minutes=1)))) def test_token_get_unauthorized_bad_ttl(self): # Verify the token. 400 is expected because the token has expired. headers = {'St2-Api-Key': 'foobar'} data = {'token': '12345'} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=False) self.assertEqual(response.status_int, 200) self.assertFalse(response.json['valid'])
class TestTokenController(FunctionalTest): def setUp(self): super(TestTokenController, self).setUp() type(pecan.request).remote_user = mock.PropertyMock( return_value=USERNAME) def test_token_model(self): dt = date_utils.get_datetime_utc_now() tk1 = TokenAPI(user='******', token=uuid.uuid4().hex, expiry=isotime.format(dt, offset=False)) tkdb1 = TokenAPI.to_model(tk1) self.assertIsNotNone(tkdb1) self.assertIsInstance(tkdb1, TokenDB) self.assertEqual(tkdb1.user, tk1.user) self.assertEqual(tkdb1.token, tk1.token) self.assertEqual(tkdb1.expiry, isotime.parse(tk1.expiry)) tkdb2 = Token.add_or_update(tkdb1) self.assertEqual(tkdb1, tkdb2) self.assertIsNotNone(tkdb2.id) tk2 = TokenAPI.from_model(tkdb2) self.assertEqual(tk2.user, tk1.user) self.assertEqual(tk2.token, tk1.token) self.assertEqual(tk2.expiry, tk1.expiry) def test_token_model_null_token(self): dt = date_utils.get_datetime_utc_now() tk = TokenAPI(user='******', token=None, expiry=isotime.format(dt)) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def test_token_model_null_user(self): dt = date_utils.get_datetime_utc_now() tk = TokenAPI(user=None, token=uuid.uuid4().hex, expiry=isotime.format(dt)) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def test_token_model_null_expiry(self): tk = TokenAPI(user='******', token=uuid.uuid4().hex, expiry=None) self.assertRaises(ValueError, Token.add_or_update, TokenAPI.to_model(tk)) def _test_token_post(self, path=TOKEN_V1_PATH): ttl = cfg.CONF.auth.token_ttl timestamp = date_utils.get_datetime_utc_now() response = self.app.post_json(path, {}, expect_errors=False) expected_expiry = date_utils.get_datetime_utc_now( ) + datetime.timedelta(seconds=ttl) expected_expiry = date_utils.add_utc_tz(expected_expiry) self.assertEqual(response.status_int, 201) self.assertIsNotNone(response.json['token']) self.assertEqual(response.json['user'], USERNAME) actual_expiry = isotime.parse(response.json['expiry']) self.assertLess(timestamp, actual_expiry) self.assertLess(actual_expiry, expected_expiry) def test_token_post_unauthorized(self): type(pecan.request).remote_user = None response = self.app.post_json(TOKEN_V1_PATH, {}, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object(User, 'get_by_name', mock.MagicMock(side_effect=Exception())) @mock.patch.object(User, 'add_or_update', mock.Mock(return_value=UserDB(name=USERNAME))) def test_token_post_new_user(self): self._test_token_post() @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_existing_user(self): self._test_token_post() @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_default_url_path(self): self._test_token_post(path=TOKEN_DEFAULT_PATH) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_ttl(self): timestamp = date_utils.add_utc_tz(date_utils.get_datetime_utc_now()) response = self.app.post_json(TOKEN_V1_PATH, {'ttl': 60}, expect_errors=False) expected_expiry = date_utils.get_datetime_utc_now( ) + datetime.timedelta(seconds=60) self.assertEqual(response.status_int, 201) actual_expiry = isotime.parse(response.json['expiry']) self.assertLess(timestamp, actual_expiry) self.assertLess(actual_expiry, expected_expiry) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_ttl_over_policy(self): ttl = cfg.CONF.auth.token_ttl response = self.app.post_json(TOKEN_V1_PATH, {'ttl': ttl + 60}, expect_errors=True) self.assertEqual(response.status_int, 400) message = 'TTL specified %s is greater than max allowed %s.' % ( ttl + 60, ttl) self.assertEqual(response.json['faultstring'], message) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_post_set_bad_ttl(self): response = self.app.post_json(TOKEN_V1_PATH, {'ttl': -1}, expect_errors=True) self.assertEqual(response.status_int, 400) response = self.app.post_json(TOKEN_V1_PATH, {'ttl': 0}, expect_errors=True) self.assertEqual(response.status_int, 400) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because an API key or token is not provided in header. data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized_bad_api_key(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because the API key is bad. headers = {'St2-Api-Key': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_unauthorized_bad_token(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. 401 is expected because the token is bad. headers = {'X-Auth-Token': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 401) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) @mock.patch.object( ApiKey, 'get', mock.MagicMock(return_value=ApiKeyDB(user=USERNAME, key_hash='foobar')) ) def test_token_get_auth_with_api_key(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. Use an API key to authenticate with the st2 auth get token endpoint. headers = {'St2-Api-Key': 'foobar'} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 200) self.assertTrue(response.json['valid']) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) def test_token_get_auth_with_token(self): # Create a new token. response = self.app.post_json(TOKEN_V1_PATH, expect_errors=False) # Verify the token. Use a token to authenticate with the st2 auth get token endpoint. headers = {'X-Auth-Token': str(response.json['token'])} data = {'token': str(response.json['token'])} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=True) self.assertEqual(response.status_int, 200) self.assertTrue(response.json['valid']) @mock.patch.object(User, 'get_by_name', mock.MagicMock(return_value=UserDB(name=USERNAME))) @mock.patch.object( ApiKey, 'get', mock.MagicMock(return_value=ApiKeyDB(user=USERNAME, key_hash='foobar')) ) @mock.patch.object( Token, 'get', mock.MagicMock( return_value=TokenDB(user=USERNAME, token='12345', expiry=date_utils.get_datetime_utc_now() - datetime.timedelta(minutes=1)))) def test_token_get_unauthorized_bad_ttl(self): # Verify the token. 400 is expected because the token has expired. headers = {'St2-Api-Key': 'foobar'} data = {'token': '12345'} response = self.app.post_json(TOKEN_VERIFY_PATH, data, headers=headers, expect_errors=False) self.assertEqual(response.status_int, 200) self.assertFalse(response.json['valid'])
class TestTokenBasedAuth(FunctionalTest): enable_auth = True @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE)), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_headers(self): response = self.app.get("/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE)), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_query_params(self): response = self.app.get("/v1/actions?x-auth-token=%s" % (TOKEN), expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE)), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_query_params_auth_cookie_is_set(self): response = self.app.get("/v1/actions?x-auth-token=%s" % (TOKEN), expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) self.assertTrue("Set-Cookie" in response.headers) self.assertTrue("HttpOnly" in response.headers["Set-Cookie"]) # Also test same cookie values + secure valid_values = ["strict", "lax", "none", "unset"] for value in valid_values: cfg.CONF.set_override(group="api", name="auth_cookie_same_site", override=value) cfg.CONF.set_override(group="api", name="auth_cookie_secure", override=True) response = self.app.get("/v1/actions?x-auth-token=%s" % (TOKEN), expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) self.assertTrue("Set-Cookie" in response.headers) self.assertTrue("HttpOnly" in response.headers["Set-Cookie"]) if value == "unset": self.assertFalse("SameSite" in response.headers["Set-Cookie"]) else: self.assertTrue("SameSite=%s" % (value) in response.headers["Set-Cookie"]) self.assertTrue("secure" in response.headers["Set-Cookie"]) # SameSite=Lax, Secure=False cfg.CONF.set_override(group="api", name="auth_cookie_same_site", override="lax") cfg.CONF.set_override(group="api", name="auth_cookie_secure", override=False) response = self.app.get("/v1/actions?x-auth-token=%s" % (TOKEN), expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) self.assertTrue("Set-Cookie" in response.headers) self.assertTrue("HttpOnly" in response.headers["Set-Cookie"]) self.assertTrue("SameSite=lax" in response.headers["Set-Cookie"]) self.assertTrue("secure" not in response.headers["Set-Cookie"]) @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=FUTURE)), ) @mock.patch.object(User, "get_by_name", mock.Mock(return_value=USER_DB)) def test_token_validation_token_in_cookies(self): response = self.app.get("/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) with mock.patch.object(self.app.cookiejar, "clear", return_value=None): response = self.app.get("/v1/actions", expect_errors=False) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 200) @mock.patch.object( Token, "get", mock.Mock(return_value=TokenDB( id=OBJ_ID, user=USER, token=TOKEN, expiry=PAST)), ) def test_token_expired(self): response = self.app.get("/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=True) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) @mock.patch.object(Token, "get", mock.MagicMock(side_effect=TokenNotFoundError())) def test_token_not_found(self): response = self.app.get("/v1/actions", headers={"X-Auth-Token": TOKEN}, expect_errors=True) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401) def test_token_not_provided(self): response = self.app.get("/v1/actions", expect_errors=True) self.assertIn("application/json", response.headers["content-type"]) self.assertEqual(response.status_int, 401)