"""Subaccount related handlers.""" import json from ddtrace import tracer from flask import Response, g, request from owsrequest import error_response, flask_request from owsrequest.constants import headers as header_constants from owsresponse import response from owsresponse.adaptors.flask import flaskify from account.api import app, authorization_backend from account.constants import error, header from account.logic import identity, sme, subaccount from account.logic.resource_getters.account import AccountDistributorByIdResourceGetter from account.logic.resource_getters.subaccount import SubaccountByUuidResourceGetter from account.utils import handler_util, pagination from account.utils.api_utils import jwt_check, validate_request_data from account.validation import validation from account.validation.schemas.create_subaccount import CreateSubaccountSchema @app.route('/subaccounts', methods=['GET']) def get_subaccounts(): """Get list of subaccounts. Gets list of subaccounts for vendor_id in Grass header. This endpoint can also accept query string. '/subaccounts?status=deactivated' status=deactivated - It will fetch only disabled subaccounts Default, it fetches only active subaccounts. Returns: Flask.response: contains list of dicts of subaccount information. When a vendor id has no subaccounts, this vendor is considered to not be a D3 and should have no concept of subaccounts. ows-account returns a 404 response to obfuscate the subaccounts concept. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) grass_account_type, grass_account_id = flask_request.get_grass_headers(request) page = pagination.get_pagination(request) validation = flask_request.verify_grass_access(request, required=True, vendor=grass_account_id) if not validation: return flaskify(validation) status = request.args.get('status') data = subaccount.get_subaccounts( grass_account_id, status, page_offset=page.offset, page_limit=page.limit ) return flaskify(data) @app.route('//subaccounts', methods=['GET']) def get_vendor_subaccounts(vendor_id): """Get list of subaccounts. Gets list of subaccounts given . Args: vendor_id (int): unique identifier for vendor. Returns: Flask.response: contains list of dicts of subaccount information. When a vendor id has no subaccounts, this vendor is considered to not be a D3 and should have no concept of subaccounts. ows-account returns a 404 response to obfuscate the subaccounts concept. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) page = pagination.get_pagination(request) validation = flask_request.verify_grass_access(request, required=False, vendor=vendor_id) if not validation: return flaskify(validation) data = subaccount.get_subaccounts(vendor_id, page_offset=page.offset, page_limit=page.limit) return flaskify(data) @app.route('/subaccount/', methods=['GET']) @tracer.wrap() def get_subaccount(subaccount_id): """Get a subaccount. Gets a subaccount, given . Args: subaccount_id (int): unique identifier for subaccount. Returns: Flask.response: contains dict of subaccount information. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) with tracer.trace('get_grass_headers_from_request'): grass_account_type, grass_account_id = flask_request.get_grass_headers(request) with tracer.trace('verify_grass_access_from_request'): validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id, subaccount=subaccount_id ) if not validation: return flaskify(response=validation) # Validate this Vendor has permission to access this subaccount if grass_account_type == header.GRASS_ACCOUNT_TYPE_VENDOR: with tracer.trace('validate_vendor_has_permission'): data = subaccount.is_subaccount_for_vendor( subaccount_id, grass_account_id, return_result=True ) if not data: return flaskify(data) else: with tracer.trace('get_subaccount_with_subaccount_id'): data = subaccount.get_subaccount(subaccount_id) return flaskify(data) @app.route('/subaccount', methods=['HEAD']) def is_subaccount_type(): """Get if user in Grass headers is a subaccount. Returns: Flask.response """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) grass_account_type, grass_account_id = flask_request.get_grass_headers(request) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return flaskify(response.Response(error.SUCCESS_CODE)) return flaskify( response.create_error_response( error.ERROR_CODE_NOT_TYPE, error.ERROR_MESSAGE_NOT_SUBACCOUNT ) ) @app.route('//subaccount/', methods=['HEAD']) def is_subaccount_for_vendor(vendor_id, subaccount_id): """Validate subaccount belongs to vendor. Given vendor with , validates subaccount with is owned by vendor. Args: vendor_id (int): unique identifier for a vendor. subaccount_id (int): unique identifier for a subaccount. Returns: Flask.response. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) validation = flask_request.verify_grass_access(request, required=False, vendor=vendor_id) if not validation: return flaskify(validation) data = subaccount.is_subaccount_for_vendor(subaccount_id, vendor_id) return flaskify(data) @app.route('/sony/subaccount/', methods=['HEAD']) def is_subaccount_sme(subaccount_id): """Validate subaccount is Sony distributed. Given subaccount with , returns status code 200 if subaccount is Sony distributed, otherwise returns 404. Args: subaccount_id (int): unique identifier for a vendor. Returns: Flask.response. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) grass_account_type, grass_account_id = flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, subaccount=subaccount_id, vendor=grass_account_id ) if not validation: return flaskify(validation) # If Vendor is passed from GRASS, validate that this has permission # to access this subaccount if grass_account_type == header.GRASS_ACCOUNT_TYPE_VENDOR: data = subaccount.is_subaccount_for_vendor(subaccount_id, grass_account_id) if not data: return flaskify(data) data = sme.is_sme_subaccount(subaccount_id) return flaskify(data) @app.route('/subaccount', methods=['POST']) @validate_request_data(CreateSubaccountSchema()) def create_subaccount(deserialize_schema): """Create a new subaccount for given vendor_id. Params: vendor_id (int): Required for subaccount creation. subaccount_name (str): Required for creates. Returns: Flask.response. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) if not g.request_context.context_type == header_constants.CONTEXT_TYPE_PROFILE: return flaskify(error_response.create_error_forbidden()) return flaskify(subaccount.create_subaccount(deserialize_schema)) @app.route('/v2/subaccounts', methods=['POST']) @validate_request_data(CreateSubaccountSchema()) def v2_create_subaccount(deserialize_schema): """Create a new subaccount for given vendor_id. Use PP authorization backend to verify that the vendor is a distributor. Params: vendor_id (int): Required for subaccount creation. subaccount_name (str): Required for creates. Returns: Flask.response. """ vendor_id = deserialize_schema['vendor_id'] if not authorization_backend.is_authorized( action='create', resource_type='subaccount', resource_id=0, resource_getter=AccountDistributorByIdResourceGetter(vendor_id), ): return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, ) ) return flaskify(subaccount.create_subaccount(deserialize_schema)) @tracer.wrap() @app.route('/v2/subaccounts/', methods=['DELETE']) @jwt_check def v2_delete_subaccount(subaccount_uuid: str): """Soft-delete a subaccount by setting its date_deleted field.""" auth = authorization_backend.is_authorized( action='delete', resource_id=subaccount_uuid, resource_type='subaccount', resource_getter=SubaccountByUuidResourceGetter(subaccount_uuid), ) if not auth: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403, ) ) oa_user_id = identity.get_oa_user_id(g.request_context.jwt_identity_id) if not oa_user_id: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403, ) ) try: result = subaccount.delete_subaccount(subaccount_uuid) except Exception: return flaskify(response.create_fatal_response('Unable to delete subaccount')) return flaskify( response.Response(status=200, message={'subaccount_uuid': result['subaccount_uuid']}) ) @app.route('/subaccount//status', methods=['PUT']) def update_subaccount_status(subaccount_id): """Update subaccount status for given subaccout_id. Arge: subaccount_id (int): subaccount id Returns: Flask.response: Returns updated status of subaccount. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) is_active = request.get_json().get('active', None) if is_active is None or not isinstance(is_active, bool): return flaskify( response.create_error_response( code=error.ERROR_CODE_INVALID_INPUT, message=error.ERROR_MESSAGE_INVALID_SUBACCOUNT_STATUS.format(subaccount_id), status=400, ) ) headers_response = flask_request.verify_grass_headers(request) if not headers_response: return flaskify(headers_response) ownership_response = flask_request.verify_grass_ownership( request, handler_util.check_subaccount_ownership, subaccount_id=subaccount_id ) if not ownership_response: return flaskify(ownership_response) subaccount_status = subaccount.update_subaccount_status(subaccount_id, is_active) return flaskify(subaccount_status) @app.route('/subaccount//document', methods=['GET']) @validation.reject_grass_headers def get_subaccount_document(subaccount_id): """Get single subaccount document for cloudsearch corpus. Should have no grass headers since this is intended for internal use Args: subaccount_id (int): subaccount id Returns: Response: Flask response """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) with_tenant_uuids = request.args.get('with_tenant_uuids', False, type=json.loads) return flaskify(subaccount.get_subaccount_document(subaccount_id, with_tenant_uuids)) @app.route('/subaccounts/names/dataloader', methods=['POST']) def get_subaccounts_names() -> Response: """Handler to get subaccount names in bulk Expects JSON payload with a list of vendor uuids. Example payload: ['uuid1', 'uuid2'] Returns: Flask.response. """ subaccount_uuids = request.get_json() if not isinstance(subaccount_uuids, list) or not all( isinstance(item, str) for item in subaccount_uuids ): return flaskify( response.create_error_response( error.ERROR_CODE_INVALID_INPUT, error.ERROR_MESSAGE_INVALID_REQUEST ) ) try: return flaskify(response.Response(subaccount.get_subaccount_names(subaccount_uuids))) except Exception as e: return flaskify(response.create_fatal_response(e.args))