def move_file(path): """ Move the file by path to the file provided by the URL parameter 'to=<path/to/destination>' .. :quickref: 04 Files; Move **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { “name”: “<path/to/destination>” } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found .. sourcecode:: http HTTP/1.1 455 Already Exists """ if not path: return InvalidParameterException( msg='Resource path is mandatory, but its missing.' ).get_error_response() dst_path, _ = rqargs.get_str('to') if not dst_path: return InvalidParameterException( msg='The path MUST be provided.').get_error_response() if path == dst_path: return InvalidParameterException(msg=f'The source file {path} can be moved to a target file with same name') \ .get_error_response() return ipfs_files.move_file(path, dst_path)
def backup_client_auth(self, host_url, challenge_response, backup_service_instance_did): """ for vault /backup & /restore :return backup access token """ body = self.http.post(host_url + URL_DID_BACKUP_AUTH, None, {"challenge_response": challenge_response}) if 'token' not in body or not body["token"]: raise InvalidParameterException( msg='backup_auth: failed to backup auth to backup node.') jws = lib.DefaultJWSParser_Parse(body["token"].encode()) if not jws: raise InvalidParameterException( msg= f'backup_auth: failed to parse token with error {self.get_error_message()}.' ) audience = ffi.string(lib.JWT_GetAudience(jws)).decode() if audience != self.get_did_string(): lib.JWT_Destroy(jws) raise InvalidParameterException( msg=f'backup_auth: failed to get the audience of the challenge.' ) issuer = ffi.string(lib.JWT_GetIssuer(jws)).decode() lib.JWT_Destroy(jws) if issuer != backup_service_instance_did: raise InvalidParameterException( msg=f'backup_auth: failed to get the issuer of the challenge.') return body["token"]
def copy_file(self, src_path, dst_path): if not src_path or not dst_path: raise InvalidParameterException() elif src_path == dst_path: raise InvalidParameterException( msg= 'The source file and the destination file can not be the same.' ) return self._move_file(src_path, dst_path, True)
def delete_document(collection_name): """ Delete the documents by collection name. .. :quickref: 03 Database; Delete the documents **URL Parameters**: .. sourcecode:: http deleteone=<true|false> # Whether delete only one matched document. Default is false. **Request**: .. code-block:: json { "filter": { "author": "john doe1", } } **Response OK**: .. sourcecode:: http HTTP/1.1 204 No Content **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ is_delete_one, msg = rqargs.get_bool('deleteone') if msg: return InvalidParameterException(msg=msg).get_error_response() col_filter, msg = params.get_dict('filter') if msg: return InvalidParameterException(msg=msg).get_error_response() return database.delete_document(collection_name, col_filter, is_delete_one)
def backup_client_sign_in(self, host_url, credential, subject): """ for vault /backup & /restore :return challenge_response, backup_service_instance_did """ vc = lib.Credential_FromJson(credential.encode(), ffi.NULL) if not vc: raise InvalidParameterException( msg='backup_sign_in: invalid credential.') doc_str = ffi.string( lib.DIDDocument_ToJson(lib.DIDStore_LoadDID(self.store, self.did), True)).decode() doc = json.loads(doc_str) body = self.http.post(host_url + URL_DID_SIGN_IN, None, {"id": doc}) if 'challenge' not in body or not body["challenge"]: raise InvalidParameterException( msg='backup_sign_in: failed to sign in to backup node.') jws = lib.DefaultJWSParser_Parse(body["challenge"].encode()) if not jws: raise InvalidParameterException( msg= f'backup_sign_in: failed to parse challenge with error {self.get_error_message()}.' ) aud = ffi.string(lib.JWT_GetAudience(jws)).decode() if aud != self.get_did_string(): lib.JWT_Destroy(jws) raise InvalidParameterException( msg= f'backup_sign_in: failed to get the audience of the challenge.' ) nonce = ffi.string(lib.JWT_GetClaim(jws, "nonce".encode())).decode() if nonce is None: lib.JWT_Destroy(jws) raise InvalidParameterException( msg=f'backup_sign_in: failed to get the nonce of the challenge.' ) issuer = ffi.string(lib.JWT_GetIssuer(jws)).decode() lib.JWT_Destroy(jws) if issuer is None: raise InvalidParameterException( msg= f'backup_sign_in: failed to get the issuer of the challenge.') vp_json = self.create_presentation(vc, nonce, issuer) if vp_json is None: raise InvalidParameterException( msg=f'backup_sign_in: failed to create presentation.') challenge_response = self.create_vp_token( vp_json, subject, issuer, hive_setting.AUTH_CHALLENGE_EXPIRED) if challenge_response is None: raise InvalidParameterException( msg=f'backup_sign_in: failed to create the challenge response.' ) return challenge_response, issuer
def backup_get_file_hash(self, file_name): if not file_name: raise InvalidParameterException() user_did, _, _ = self._check_auth_backup() return fm.get_hashes_by_file( (get_vault_backup_path(user_did) / file_name).resolve())
def backup_get_file(self, file_name): if not file_name: raise InvalidParameterException() user_did, _, _ = self._check_auth_backup() return self.http_server.create_range_request( (get_vault_backup_path(user_did) / file_name).resolve())
def upload_file(self, path): if not path: raise InvalidParameterException() user_did, app_did = check_auth_and_vault(VAULT_ACCESS_WR) self.upload_file_by_did(user_did, app_did, path) return {'name': path}
def _check_place_order_params(self, json_body): if not json_body: raise InvalidParameterException(msg='Request body should not empty.') validate_exists(json_body, '', ('subscription', 'pricing_name')) subscription, pricing_name = json_body.get('subscription', None), json_body.get('pricing_name', None) if subscription not in ('vault', 'backup'): raise InvalidParameterException(msg=f'Invalid subscription: {subscription}.') plan = self._get_vault_subscription().get_price_plan(subscription, pricing_name) if not plan: raise InvalidParameterException(msg=f'Invalid pricing_name: {pricing_name}.') if plan['amount'] <= 0: raise InvalidParameterException(msg=f'Invalid pricing_name which is free.') return subscription, plan
def _check_param_order_id(self, user_did, order_id, is_pay_order=False): if not order_id: raise InvalidParameterException(msg='Order id MUST be provided.') col_filter = {'_id': ObjectId(order_id), USR_DID: user_did} if is_pay_order: col_filter[COL_ORDERS_STATUS] = COL_ORDERS_STATUS_NORMAL order = cli.find_one_origin(DID_INFO_DB_NAME, COL_ORDERS, col_filter, throw_exception=False) if not order: raise InvalidParameterException(msg='Order id is invalid because of not finding the order.') if is_pay_order: receipt = cli.find_one_origin(DID_INFO_DB_NAME, COL_RECEIPTS, {COL_RECEIPTS_ORDER_ID: order_id}, throw_exception=False) if receipt: raise InvalidParameterException(msg='Order id is invalid because of existing the relating receipt.') return order
def _check_pay_order_params(self, user_did, order_id, json_body): order = self._check_param_order_id(user_did, order_id, is_pay_order=True) if not json_body: raise InvalidParameterException(msg='Request body should not empty.') validate_exists(json_body, '', ['transaction_id', ]) transaction_id = json_body.get('transaction_id', None) paid_did = self._check_transaction_id(user_did, order, transaction_id) return order, transaction_id, paid_did
def get_backup_credential_info(self, credential): """ for vault /backup """ from src.utils_v1.auth import get_credential_info credential_info, err = get_credential_info(credential, ["targetHost", "targetDID"]) if credential_info is None: raise InvalidParameterException( msg=f'Failed to get credential info: {err}') return credential_info
def ipfs_backup_state(self, to, vault_size): if to != STATE_RUNNING and to != STATE_FINISH: raise InvalidParameterException(f'Invalid parameter to = {to}') user_did, _, backup = self._check_auth_backup() if backup[VAULT_BACKUP_SERVICE_MAX_STORAGE] < vault_size: raise InsufficientStorageException( 'No more space for the backup process.') self.ipfs_update_state_really(user_did, to, vault_size)
def backup_upload_file(self, file_name): if not file_name: raise InvalidParameterException() is_ipfs = file_name.endswith(BACKUP_FILE_SUFFIX) user_did, _, backup = self._check_auth_backup(is_check_size=is_ipfs) dst_file = (get_vault_backup_path(user_did) / file_name).resolve() fm.write_file_by_request_stream(dst_file) if is_ipfs: self.ipfs_increase_used_size(backup, dst_file.stat().st_size)
def get_hash(self, path): if not path: raise InvalidParameterException() user_did, app_did = check_auth_and_vault(VAULT_ACCESS_WR) data, err = query_hash(user_did, app_did, path) if err: raise FileNotFoundException( f'Failed getting file hash code: {str(err)}') return {'name': path, 'algorithm': 'SHA256', 'hash': data['SHA256']}
def backup_get_file_delta(self, file_name): if not file_name: raise InvalidParameterException(msg='The file name must provide.') user_did, _, _ = self._check_auth_backup() data = request.get_data() hashes = fm.get_hashes_by_lines( list() if not data else data.split(b'\n')) return fm.get_rsync_data( (get_vault_backup_path(user_did) / file_name).resolve(), hashes)
def get_properties(self, path): if not path: raise InvalidParameterException() user_did, app_did = check_auth_and_vault(VAULT_ACCESS_R) full_path, stat = self.get_file_stat_by_did(user_did, app_did, path) return { 'name': path, 'is_file': full_path.is_file(), 'size': stat.st_size, 'created': cli.timestamp_to_epoch(int(stat.st_ctime)), 'updated': cli.timestamp_to_epoch(int(stat.st_mtime)), }
def backup_patch_file(self, file_name): if not file_name: raise InvalidParameterException() user_did, _, _ = self._check_auth_backup() temp_file = gene_temp_file_name() fm.write_file_by_request_stream(temp_file) pickle_data = fm.read_rsync_data_from_file(temp_file) temp_file.unlink() fm.apply_rsync_data( (get_vault_backup_path(user_did) / file_name).resolve(), pickle_data)
def delete_file(self, path): if not path: raise InvalidParameterException() user_did, app_did = check_auth_and_vault(VAULT_ACCESS_WR) full_path = self._get_file_full_path(user_did, app_did, path) if full_path.exists(): if full_path.is_dir(): dir_size = get_dir_size(full_path.as_posix(), 0.0) shutil.rmtree(full_path) update_used_storage_for_files_data(user_did, -dir_size) else: file_size = os.path.getsize(full_path.as_posix()) full_path.unlink() update_used_storage_for_files_data(user_did, -file_size)
def get_orders(self, subscription, order_id): _, _ = check_auth() if subscription not in ('vault', 'backup'): raise InvalidParameterException(msg=f'Invalid subscription: {subscription}.') col_filter = {} if subscription: col_filter[COL_ORDERS_SUBSCRIPTION] = subscription if order_id: col_filter[COL_RECEIPTS_ORDER_ID] = order_id orders = cli.find_many_origin(DID_INFO_DB_NAME, COL_ORDERS, col_filter, throw_exception=False) if not orders: raise OrderNotFoundException(msg='Can not get the matched orders.') return {'orders': list(map(lambda o: {'order_id': str(o['_id']), COL_ORDERS_SUBSCRIPTION: o[COL_ORDERS_SUBSCRIPTION], COL_ORDERS_PRICING_NAME: o[COL_ORDERS_PRICING_NAME], COL_ORDERS_ELA_AMOUNT: o[COL_ORDERS_ELA_AMOUNT], COL_ORDERS_ELA_ADDRESS: o[COL_ORDERS_ELA_ADDRESS], COL_ORDERS_PROOF: o[COL_ORDERS_PROOF], COL_ORDERS_STATUS: o[COL_ORDERS_STATUS], CREATE_TIME: int(o[CREATE_TIME])}, orders))}
def delete_file(path): """ Delete the file by path. .. :quickref: 04 Files; Delete **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 204 No Content **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden """ if not path: return InvalidParameterException( msg='Resource path is mandatory, but its missing.' ).get_error_response() return ipfs_files.delete_file(path)
def did_sign_in(): """ Sign in with the application DID and get the challenge string. .. :quickref: 01 Authentication; Sign in with app DID **Request**: .. code-block:: json { "id": "<the user’s did_document>", } **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { “challenge”: “<the authentication challenge encoded in JWT>” } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request """ doc, msg = params.get_dict('id') if msg or not doc: return InvalidParameterException().get_error_response() return auth.sign_in(doc)
def did_auth(): """ Auth to get the access token for the user DID and the application DID. .. :quickref: 01 Authentication; Get the access token. **Request**: .. code-block:: json { "challenge_response": "<the response for the authentication challenge encoded in JWT>", } **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { “token”: “<the access token encoded in JWT>” } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request """ challenge_response, msg = params.get_str('challenge_response') if msg: return InvalidParameterException(msg=msg).get_error_response() return auth.auth(challenge_response)
def _check_transaction_id_local(self, transaction_id): receipt = cli.find_one_origin(DID_INFO_DB_NAME, COL_RECEIPTS, {COL_RECEIPTS_TRANSACTION_ID: transaction_id}, throw_exception=False) if receipt: raise InvalidParameterException(msg=f'Transaction id {transaction_id} has already been used.')
def query_document(): """ Query the documents with more options .. :quickref: 03 Database; Query the documents **Request**: .. code-block:: json { "collection": "works", "filter": { "author": "john doe1_1", }, "options": { "skip": 0, "limit": 3, "projection": { "_id": false }, "sort": [('_id', -1)], # pymongo.DESCENDING "allow_partial_results": false, "return_key": false, "show_record_id": false, "batch_size": 0 } } **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { "items": [{ "author": "john doe1_1", "title": "Eve for Dummies1_1", "created": { "$date": 1630022400000 }, "modified": { "$date": 1598803861786 } }, { "author": "john doe1_2", "title": "Eve for Dummies1_2", "created": { "$date": 1630022400000 }, "modified": { "$date": 1598803861786 } }] } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ json_body, msg = params.get_root() if msg: return InvalidParameterException(msg=msg).get_error_response() if 'collection' not in json_body or not json_body['collection']: return InvalidParameterException( msg='No collection name in the request body.').get_error_response( ) if 'filter' not in json_body or type(json_body.get('filter')) is not dict: return InvalidParameterException( msg='Invalid parameter filter.').get_error_response() return database.query_document(json_body['collection'], json_body)
def find_document(collection_name): """ Find the documents by collection name. The parameters are URL ones. .. :quickref: 03 Database; Find the documents **URL Parameters**: .. sourcecode:: http filter (json str) : the filter doc need to be encoded by url skip (int): limit (int): **Request**: .. code-block:: json None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { "items": [{ "author": "john doe1_1", "title": "Eve for Dummies1_1", "created": { "$date": 1630022400000 }, "modified": { "$date": 1598803861786 } }] } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ col_filter, msg = rqargs.get_dict('filter') if msg: return InvalidParameterException(msg=msg).get_error_response() skip, limit = rqargs.get_int('skip')[0], rqargs.get_int('limit')[0] if skip < 0 or limit < 0: return InvalidParameterException( msg='Invalid parameter skip or limit.').get_error_response() return database.find_document(collection_name, col_filter, skip, limit)
def writing_operation(path): """ Copy or upload file by path. Copy the file by the path if the URL parameter is 'dest=<path/to/destination>'. .. :quickref: 04 Files; Copy/upload **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { “name”: “<path/to/destination>” } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found .. sourcecode:: http HTTP/1.1 455 Already Exists Upload the content of the file by path if no URL parameter. **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { “name”: “<path/to/res>” } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ if not path: return InvalidParameterException( msg='Resource path is mandatory, but its missing.' ).get_error_response() dst_path, _ = rqargs.get_str('dest') if not dst_path: return ipfs_files.upload_file(path) if path == dst_path: return InvalidParameterException(msg=f'The source file {path} can be copied to a target file with same name')\ .get_error_response() return ipfs_files.copy_file(path, dst_path)
def update_document(collection_name): """ Update the documents by collection name. .. :quickref: 03 Database; Update the documents **URL Parameters**: .. sourcecode:: http updateone=<true|false> # Whether update only one matched document. Default is false. **Request**: .. code-block:: json { "filter": { "author": "john doe1", }, "update": {"$set": { "author": "john doe1_1", "title": "Eve for Dummies1_1" }}, "options": { "upsert": true, "bypass_document_validation": false } } **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { "acknowledged": true, "matched_count": 10, "modified_count": 10, "upserted_id": null } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ is_update_one, msg = rqargs.get_bool('updateone') if msg: return InvalidParameterException(msg=msg).get_error_response() json_body, msg = params.get_root() if msg or not json_body: return InvalidParameterException( msg=f'Invalid request body.').get_error_response() if 'filter' in json_body and type(json_body.get('filter')) is not dict: return InvalidParameterException( msg='Invalid parameter filter.').get_error_response() if 'update' not in json_body or type(json_body.get('update')) is not dict: return InvalidParameterException( msg='Invalid parameter update.').get_error_response() return database.update_document(collection_name, json_body, is_update_one)
def insert_or_count_document(collection_name): """ Insert or count the documents. Insert the documents if no URL parameters. .. :quickref: 03 Database; Insert&count the documents **Request**: .. code-block:: json { "document": [ { "author": "john doe1", "title": "Eve for Dummies1" }, { "author": "john doe2", "title": "Eve for Dummies2" } ], "options": { "bypass_document_validation":false, "ordered":true } } **Response OK**: .. sourcecode:: http HTTP/1.1 201 Created .. code-block:: json { "acknowledged": true, "inserted_ids": [ "5f4658d122c95b17e72f2d4a", "5f4658d122c95b17e72f2d4b" ] } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found Count the documents if the URL parameter is **op = count** **Request**: .. code-block:: json { "filter": { "author": "john doe1_1", }, "options": { "skip": 0, "limit": 10, "maxTimeMS": 1000000000 } } **Response OK**: .. sourcecode:: http HTTP/1.1 204 No Content .. code-block:: json { "count": 10 } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ op, _ = rqargs.get_str('op') json_body, msg = params.get_root() if msg or not json_body: return InvalidParameterException( msg=f'Invalid request body.').get_error_response() if op == 'count': if 'filter' not in json_body or type( json_body.get('filter')) is not dict: return InvalidParameterException().get_error_response() return database.count_document(collection_name, json_body) if 'document' not in json_body or type(json_body.get('document')) != list: return InvalidParameterException().get_error_response() return database.insert_document(collection_name, json_body)
def reading_operation(path): """ Download/get the properties of/get the hash of the file, list the files of the folder. Download the content of the file by path if no URL parameter. .. :quickref: 04 Files; Download/properties/hash/list **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json <The bytes of the content of the file.> **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found List the files of the directory by the path if the URL parameter is 'comp=children'. **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { “value”: [{ “name”: “<path/to/res>” “is_file”: false, “size”: <Integer> }, { “name”: “<path/to/dir>” “is_file”: true }] } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found Get the properties of the file by the path if the URL parameter is 'comp=metadata'. **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { “name”: <path/to/res>, “is_file”: <true: file, false: folder>, “size”: <size>, “created”: <created timestamp> “updated”: <updated timestamp> } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found Get the hash of the file by the path if the URL parameter is 'comp=hash'. **Request**: .. sourcecode:: http None **Response OK**: .. sourcecode:: http HTTP/1.1 200 OK .. code-block:: json { "name": <the path of the file> “algorithm”: <“algorithm name: currently support SHA256”> "hash": <SHA-256 computation value of the file content> } **Response Error**: .. sourcecode:: http HTTP/1.1 400 Bad Request .. sourcecode:: http HTTP/1.1 401 Unauthorized .. sourcecode:: http HTTP/1.1 403 Forbidden .. sourcecode:: http HTTP/1.1 404 Not Found """ component, _ = rqargs.get_str('comp') if not path and component != 'children': return InvalidParameterException( msg='Resource path is mandatory, but its missing.' ).get_error_response() if not component: return ipfs_files.download_file(path) elif component == 'children': return ipfs_files.list_folder(path) elif component == 'metadata': return ipfs_files.get_properties(path) elif component == 'hash': return ipfs_files.get_hash(path) else: return BadRequestException( msg=f'Unsupported parameter "comp" value {component}' ).get_error_response()