def put(self, api_key_id_or_key, api_key_api): api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) LOG.debug( 'PUT /apikeys/ lookup with api_key_id_or_key=%s found object: %s', api_key_id_or_key, api_key_db) old_api_key_db = api_key_db api_key_db = ApiKeyAPI.to_model(api_key_api) # Passing in key_hash as MASKED_ATTRIBUTE_VALUE is expected since we do not # leak it out therefore it is expected we get the same value back. Interpret # this special code and empty value as no-change if api_key_db.key_hash == MASKED_ATTRIBUTE_VALUE or not api_key_db.key_hash: api_key_db.key_hash = old_api_key_db.key_hash # Rather than silently ignore any update to key_hash it is better to explicitly # disallow and notify user. if old_api_key_db.key_hash != api_key_db.key_hash: raise ValueError('Update of key_hash is not allowed.') api_key_db.id = old_api_key_db.id api_key_db = ApiKey.add_or_update(api_key_db) extra = { 'old_api_key_db': old_api_key_db, 'new_api_key_db': api_key_db } LOG.audit('API Key updated. ApiKey.id=%s.' % (api_key_db.id), extra=extra) api_key_api = ApiKeyAPI.from_model(api_key_db) return api_key_api
def put(self, api_key_id_or_key, api_key_api): api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) LOG.debug('PUT /apikeys/ lookup with api_key_id_or_key=%s found object: %s', api_key_id_or_key, api_key_db) old_api_key_db = api_key_db api_key_db = ApiKeyAPI.to_model(api_key_api) # Passing in key_hash as MASKED_ATTRIBUTE_VALUE is expected since we do not # leak it out therefore it is expected we get the same value back. Interpret # this special code and empty value as no-change if api_key_db.key_hash == MASKED_ATTRIBUTE_VALUE or not api_key_db.key_hash: api_key_db.key_hash = old_api_key_db.key_hash # Rather than silently ignore any update to key_hash it is better to explicitly # disallow and notify user. if old_api_key_db.key_hash != api_key_db.key_hash: raise ValueError('Update of key_hash is not allowed.') api_key_db.id = old_api_key_db.id api_key_db = ApiKey.add_or_update(api_key_db) extra = {'old_api_key_db': old_api_key_db, 'new_api_key_db': api_key_db} LOG.audit('API Key updated. ApiKey.id=%s.' % (api_key_db.id), extra=extra) api_key_api = ApiKeyAPI.from_model(api_key_db) return api_key_api
def delete(self, api_key_id_or_key): """ Delete the key value pair. Handles requests: DELETE /apikeys/1 """ api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) LOG.debug('DELETE /apikeys/ lookup with api_key_id_or_key=%s found object: %s', api_key_id_or_key, api_key_db) ApiKey.delete(api_key_db) extra = {'api_key_db': api_key_db} LOG.audit('ApiKey deleted. ApiKey.id=%s' % (api_key_db.id), extra=extra)
def post(self, api_key_api): """ Create a new entry. """ api_key_db = None api_key = None try: if not getattr(api_key_api, 'user', None): api_key_api.user = self._get_user() # If key_hash is provided use that and do not create a new key. The assumption # is user already has the original api-key if not getattr(api_key_api, 'key_hash', None): api_key, api_key_hash = auth_util.generate_api_key_and_hash() # store key_hash in DB api_key_api.key_hash = api_key_hash api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api)) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for api_key data=%s.', api_key_api) abort(http_client.BAD_REQUEST, str(e)) extra = {'api_key_db': api_key_db} LOG.audit('ApiKey created. ApiKey.id=%s' % (api_key_db.id), extra=extra) api_key_create_response_api = ApiKeyCreateResponseAPI.from_model(api_key_db) # Return real api_key back to user. A one-way hash of the api_key is stored in the DB # only the real value only returned at create time. Also, no masking of key here since # the user needs to see this value atleast once. api_key_create_response_api.key = api_key return api_key_create_response_api
def validate_api_key(api_key_in_headers, api_key_query_params): """ Validate the provided API key. :param api_key_in_headers: API key provided via headers. :type api_key_in_headers: ``str`` :param api_key_query_params: API key provided via query params. :type api_key_query_params: ``str`` :return: TokenDB object on success. :rtype: :class:`.ApiKeyDB` """ if not api_key_in_headers and not api_key_query_params: LOG.audit('API key is not found in header or query parameters.') raise exceptions.ApiKeyNotProvidedError('API key is not provided.') if api_key_in_headers: LOG.audit('API key provided in headers') if api_key_query_params: LOG.audit('API key provided in query parameters') api_key = api_key_in_headers or api_key_query_params api_key_db = ApiKey.get(api_key) if not api_key_db.enabled: raise exceptions.ApiKeyDisabledError('API key is disabled.') LOG.audit('API key with id "%s" is validated.' % (api_key_db.id)) return api_key_db
def get_all(self, requester_user, show_secrets=None, limit=None, offset=0): """ List all keys. Handles requests: GET /apikeys/ """ mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) if limit and int(limit) > self.max_limit: msg = 'Limit "%s" specified, maximum value is "%s"' % (limit, self.max_limit) raise ValueError(msg) api_key_dbs = ApiKey.get_all(limit=limit, offset=offset) try: api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) for api_key_db in api_key_dbs] except OverflowError: msg = 'Offset "%s" specified is more than 32 bit int' % (offset) raise ValueError(msg) resp = Response(json=api_keys) resp.headers['X-Total-Count'] = str(api_key_dbs.count()) if limit: resp.headers['X-Limit'] = str(limit) return resp
def get_one(self, api_key_id_or_key, requester_user, show_secrets=None): """ List api keys. Handle: GET /apikeys/1 """ api_key_db = None try: api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) except ApiKeyNotFoundError: msg = ('ApiKey matching %s for reference and id not found.' % (api_key_id_or_key)) LOG.exception(msg) abort(http_client.NOT_FOUND, msg) permission_type = PermissionType.API_KEY_VIEW rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=api_key_db, permission_type=permission_type) try: mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) return ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) except (ValidationError, ValueError) as e: LOG.exception('Failed to serialize API key.') abort(http_client.INTERNAL_SERVER_ERROR, str(e))
def get_one(self, api_key_id_or_key, requester_user, show_secrets=None): """ List api keys. Handle: GET /apikeys/1 """ api_key_db = None try: api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) except ApiKeyNotFoundError: msg = ('ApiKey matching %s for reference and id not found.' % (api_key_id_or_key)) LOG.exception(msg) abort(http_client.NOT_FOUND, msg) permission_type = PermissionType.API_KEY_VIEW rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=api_key_db, permission_type=permission_type) try: mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) return ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) except (ValidationError, ValueError) as e: LOG.exception('Failed to serialize API key.') abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e))
def post(self, api_key_api): """ Create a new entry or update an existing one. """ api_key_db = None try: api_key_api.user = self._get_user() api_key, api_key_hash = auth_util.generate_api_key_and_hash() # store key_hash in DB api_key_api.key_hash = api_key_hash api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api)) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for api_key data=%s.', api_key_api) abort(http_client.BAD_REQUEST, str(e)) extra = {'api_key_db': api_key_db} LOG.audit('ApiKey created. ApiKey.id=%s' % (api_key_db.id), extra=extra) api_key_create_response_api = ApiKeyCreateResponseAPI.from_model( api_key_db) # Return real api_key back to user. A one-way hash of the api_key is stored in the DB # only the real value only returned at create time. Also, no masking of key here since # the user needs to see this value atleast once. api_key_create_response_api.key = api_key return api_key_create_response_api
def get_all(self, requester_user, show_secrets=None, limit=None, offset=0): """ List all keys. Handles requests: GET /apikeys/ """ mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) limit = resource.validate_limit_query_param(limit, requester_user=requester_user) try: api_key_dbs = ApiKey.get_all(limit=limit, offset=offset) api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) for api_key_db in api_key_dbs] except OverflowError: msg = 'Offset "%s" specified is more than 32 bit int' % (offset) raise ValueError(msg) resp = Response(json=api_keys) resp.headers['X-Total-Count'] = str(api_key_dbs.count()) if limit: resp.headers['X-Limit'] = str(limit) return resp
def put(self, api_key_api, api_key_id_or_key, requester_user): api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) permission_type = PermissionType.API_KEY_MODIFY rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=api_key_db, permission_type=permission_type, ) old_api_key_db = api_key_db api_key_db = ApiKeyAPI.to_model(api_key_api) try: User.get_by_name(api_key_api.user) except StackStormDBObjectNotFoundError: user_db = UserDB(name=api_key_api.user) User.add_or_update(user_db) extra = {"username": api_key_api.user, "user": user_db} LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra) # Passing in key_hash as MASKED_ATTRIBUTE_VALUE is expected since we do not # leak it out therefore it is expected we get the same value back. Interpret # this special code and empty value as no-change if api_key_db.key_hash == MASKED_ATTRIBUTE_VALUE or not api_key_db.key_hash: api_key_db.key_hash = old_api_key_db.key_hash # Rather than silently ignore any update to key_hash it is better to explicitly # disallow and notify user. if old_api_key_db.key_hash != api_key_db.key_hash: raise ValueError("Update of key_hash is not allowed.") api_key_db.id = old_api_key_db.id api_key_db = ApiKey.add_or_update(api_key_db) extra = { "old_api_key_db": old_api_key_db, "new_api_key_db": api_key_db } LOG.audit("API Key updated. ApiKey.id=%s." % (api_key_db.id), extra=extra) api_key_api = ApiKeyAPI.from_model(api_key_db) return api_key_api
def post(self, api_key_api, requester_user): """ Create a new entry. """ permission_type = PermissionType.API_KEY_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=api_key_api, permission_type=permission_type, ) api_key_db = None api_key = None try: if not getattr(api_key_api, "user", None): if requester_user: api_key_api.user = requester_user.name else: api_key_api.user = cfg.CONF.system_user.user try: User.get_by_name(api_key_api.user) except StackStormDBObjectNotFoundError: user_db = UserDB(name=api_key_api.user) User.add_or_update(user_db) extra = {"username": api_key_api.user, "user": user_db} LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra) # If key_hash is provided use that and do not create a new key. The assumption # is user already has the original api-key if not getattr(api_key_api, "key_hash", None): api_key, api_key_hash = auth_util.generate_api_key_and_hash() # store key_hash in DB api_key_api.key_hash = api_key_hash api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api)) except (ValidationError, ValueError) as e: LOG.exception("Validation failed for api_key data=%s.", api_key_api) abort(http_client.BAD_REQUEST, six.text_type(e)) extra = {"api_key_db": api_key_db} LOG.audit("ApiKey created. ApiKey.id=%s" % (api_key_db.id), extra=extra) api_key_create_response_api = ApiKeyCreateResponseAPI.from_model( api_key_db) # Return real api_key back to user. A one-way hash of the api_key is stored in the DB # only the real value only returned at create time. Also, no masking of key here since # the user needs to see this value atleast once. api_key_create_response_api.key = api_key return Response(json=api_key_create_response_api, status=http_client.CREATED)
def delete(self, api_key_id_or_key, requester_user): """ Delete the key value pair. Handles requests: DELETE /apikeys/1 """ api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) permission_type = PermissionType.API_KEY_DELETE rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=api_key_db, permission_type=permission_type) ApiKey.delete(api_key_db) extra = {'api_key_db': api_key_db} LOG.audit('ApiKey deleted. ApiKey.id=%s' % (api_key_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def put(self, api_key_api, api_key_id_or_key, requester_user): api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) permission_type = PermissionType.API_KEY_MODIFY rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=api_key_db, permission_type=permission_type) old_api_key_db = api_key_db api_key_db = ApiKeyAPI.to_model(api_key_api) try: User.get_by_name(api_key_api.user) except StackStormDBObjectNotFoundError: user_db = UserDB(name=api_key_api.user) User.add_or_update(user_db) extra = {'username': api_key_api.user, 'user': user_db} LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra) # Passing in key_hash as MASKED_ATTRIBUTE_VALUE is expected since we do not # leak it out therefore it is expected we get the same value back. Interpret # this special code and empty value as no-change if api_key_db.key_hash == MASKED_ATTRIBUTE_VALUE or not api_key_db.key_hash: api_key_db.key_hash = old_api_key_db.key_hash # Rather than silently ignore any update to key_hash it is better to explicitly # disallow and notify user. if old_api_key_db.key_hash != api_key_db.key_hash: raise ValueError('Update of key_hash is not allowed.') api_key_db.id = old_api_key_db.id api_key_db = ApiKey.add_or_update(api_key_db) extra = {'old_api_key_db': old_api_key_db, 'new_api_key_db': api_key_db} LOG.audit('API Key updated. ApiKey.id=%s.' % (api_key_db.id), extra=extra) api_key_api = ApiKeyAPI.from_model(api_key_db) return api_key_api
def get_all(self, **kw): """ List all keys. Handles requests: GET /keys/ """ api_key_dbs = ApiKey.get_all(**kw) api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=True) for api_key_db in api_key_dbs] return api_keys
def get_all(self, requester_user, show_secrets=None): """ List all keys. Handles requests: GET /apikeys/ """ mask_secrets = self._get_mask_secrets(show_secrets=show_secrets, requester_user=requester_user) api_key_dbs = ApiKey.get_all() api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets) for api_key_db in api_key_dbs] return api_keys
def post(self, api_key_api, requester_user): """ Create a new entry. """ permission_type = PermissionType.API_KEY_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission(user_db=requester_user, resource_api=api_key_api, permission_type=permission_type) api_key_db = None api_key = None try: if not getattr(api_key_api, 'user', None): if requester_user: api_key_api.user = requester_user.name else: api_key_api.user = cfg.CONF.system_user.user try: User.get_by_name(api_key_api.user) except StackStormDBObjectNotFoundError: user_db = UserDB(name=api_key_api.user) User.add_or_update(user_db) extra = {'username': api_key_api.user, 'user': user_db} LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra) # If key_hash is provided use that and do not create a new key. The assumption # is user already has the original api-key if not getattr(api_key_api, 'key_hash', None): api_key, api_key_hash = auth_util.generate_api_key_and_hash() # store key_hash in DB api_key_api.key_hash = api_key_hash api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api)) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for api_key data=%s.', api_key_api) abort(http_client.BAD_REQUEST, six.text_type(e)) extra = {'api_key_db': api_key_db} LOG.audit('ApiKey created. ApiKey.id=%s' % (api_key_db.id), extra=extra) api_key_create_response_api = ApiKeyCreateResponseAPI.from_model(api_key_db) # Return real api_key back to user. A one-way hash of the api_key is stored in the DB # only the real value only returned at create time. Also, no masking of key here since # the user needs to see this value atleast once. api_key_create_response_api.key = api_key return Response(json=api_key_create_response_api, status=http_client.CREATED)
def test_post_delete_same_key_hash(self): api_key = {'user': '******', 'key_hash': 'ABCDE'} resp1 = self.app.post_json('/v1/apikeys', api_key) self.assertEqual(resp1.status_int, 201) self.assertEqual(resp1.json['key'], None, 'Key should be None.') # drop into the DB since API will be masking this value. api_key_db = ApiKey.get_by_id(resp1.json['id']) self.assertEqual(api_key_db.key_hash, api_key['key_hash'], 'Key_hash should match.') self.assertEqual(api_key_db.user, api_key['user'], 'Key_hash should match.') resp = self.app.delete('/v1/apikeys/%s' % resp1.json['id']) self.assertEqual(resp.status_int, 204)
def test_post_delete_same_key_hash(self): api_key = { 'user': '******', 'key_hash': 'ABCDE' } resp1 = self.app.post_json('/v1/apikeys', api_key) self.assertEqual(resp1.status_int, 201) self.assertEqual(resp1.json['key'], None, 'Key should be None.') # drop into the DB since API will be masking this value. api_key_db = ApiKey.get_by_id(resp1.json['id']) self.assertEqual(api_key_db.key_hash, api_key['key_hash'], 'Key_hash should match.') self.assertEqual(api_key_db.user, api_key['user'], 'Key_hash should match.') resp = self.app.delete('/v1/apikeys/%s' % resp1.json['id']) self.assertEqual(resp.status_int, 204)
def validate_api_key(api_key): """ Validate the provided API key. :param api_key: API key provided. :type api_key: ``str`` :return: TokenDB object on success. :rtype: :class:`.ApiKeyDB` """ api_key_db = ApiKey.get(api_key) if not api_key_db.enabled: raise exceptions.ApiKeyDisabledError('API key is disabled.') LOG.audit('API key with id "%s" is validated.' % (api_key_db.id)) return api_key_db
def get_one(self, api_key_id_or_key): """ List api keys. Handle: GET /apikeys/1 """ api_key_db = None try: api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key) except ApiKeyNotFoundError: msg = 'ApiKey matching %s for reference and id not found.', api_key_id_or_key LOG.exception(msg) abort(http_client.NOT_FOUND, msg) try: return ApiKeyAPI.from_model(api_key_db, mask_secrets=True) except (ValidationError, ValueError) as e: LOG.exception('Failed to serialize API key.') abort(http_client.INTERNAL_SERVER_ERROR, str(e))
def test_post_delete_same_key_hash(self): api_key = { "id": "5c5dbb576cb8de06a2d79a4d", "user": "******", "key_hash": "ABCDE", } resp1 = self.app.post_json("/v1/apikeys", api_key) self.assertEqual(resp1.status_int, 201) self.assertEqual(resp1.json["key"], None, "Key should be None.") # drop into the DB since API will be masking this value. api_key_db = ApiKey.get_by_id(resp1.json["id"]) self.assertEqual(resp1.json["id"], api_key["id"], "PK ID of created API should match.") self.assertEqual(api_key_db.key_hash, api_key["key_hash"], "Key_hash should match.") self.assertEqual(api_key_db.user, api_key["user"], "User should match.") resp = self.app.delete("/v1/apikeys/%s" % resp1.json["id"]) self.assertEqual(resp.status_int, 204)