"""Route handlers for Vendor Core endpoints.""" from account import config from account.api import app, authorization_backend from account.constants import error, header from account.constants.connectors import NEO4J_DATABASE_NAME from account.constants.constants import ( FEATURE_USE_NEO4J_MANAGED_TX, WARNING_MESSAGE_VENDOR_ACCESS_CHECK, ) from account.logic import ( subaccount, vendor, ) from account.logic.resource_getters.account import AccountByIdResourceGetter from account.logic.resource_getters.company_brand import CompanyBrandByNameResourceGetter from account.utils.api_utils import validate_request_data from account.validation.schemas import create_vendor from account.validation.schemas.update_vendor import UpdateVendorSchema from connector_neo4j import Neo4jSession from ddtrace import tracer from flask import 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 pythonfeatures import pythonfeatures from pythonfeatures.constants import split as split_constants @app.route('/distributor', methods=['HEAD']) def is_distributor_type(): """Validate Grass user is a distributor. Gets if vendor in Grass headers is a distributor. 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=True, vendor=grass_account_id, subaccount=grass_account_id ) if not validation: return flaskify(response=validation) if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return flaskify( response.create_error_response( error.ERROR_CODE_NOT_FOUND, error.ERROR_MESSAGE_NO_MATCH, status=204 ) ) data = vendor.is_distributor(grass_account_id) return flaskify(data) @app.route('//distributor', methods=['HEAD']) def is_vendor_a_distributor(vendor_id): """Validate vendor is a distributor. Given vendor with , gets if vendor is a distributor. Args: vendor_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 ) ) validation = flask_request.verify_grass_access(request, required=False, vendor=vendor_id) if not validation: return flaskify(response=validation) data = vendor.is_distributor(vendor_id) return flaskify(data) @app.route('/vendor/', methods=['HEAD']) def is_valid_vendor(vendor_id): """Validate if vendor_id is a valid vendor. Args: vendor_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 ) ) return flaskify(vendor.is_vendor(vendor_id)) @app.route('/vendor/', methods=['GET']) @Neo4jSession(use_v2=True, database=NEO4J_DATABASE_NAME) def get_vendor(vendor_id): """Return basic information about vendor. Args: vendor_id (int): unique identifier for a vendor. Returns: Flask.response. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) pdp_check = False if not access_rule_decision: pdp_check = authorization_backend.is_authorized( action='view', resource_id=vendor_id, resource_type='account', resource_getter=AccountByIdResourceGetter(vendor_id), ) if not pdp_check: return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403 ) ) use_managed_tx = ( pythonfeatures.get_single_feature(FEATURE_USE_NEO4J_MANAGED_TX, g.request_context).message == split_constants.FEATURE_ENABLED ) if not pdp_check: profile_vendor_access_call = ( vendor.profile_has_access_tx if use_managed_tx else vendor.profile_has_access ) grass_account_type, grass_id = flask_request.get_grass_headers(request) validation_response = response.Response() if grass_account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: if not subaccount.is_subaccount_for_vendor(grass_id, vendor_id): validation_response = response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message=error.ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) else: validation_response = flask_request.verify_grass_access( request, required=False, vendor=vendor_id ) if not validation_response: return flaskify(validation_response) request_profile_id = g.request_context.profile_id request_profile_type = g.request_context.profile_type if config.ENVIRONMENT == config.QA_ENVIRONMENT and ( request_profile_id is None and request_profile_type is None ): pass elif not profile_vendor_access_call(request_profile_id, request_profile_type, vendor_id): g.log.warning( WARNING_MESSAGE_VENDOR_ACCESS_CHECK.format( request_profile_id, request_profile_type, vendor_id ) ) if use_managed_tx: return flaskify(vendor.get_vendor_label_info(vendor_id, neo_tx=True)) return flaskify(vendor.get_vendor_label_info(vendor_id)) @app.route('/vendor', methods=['PATCH']) @validate_request_data(create_vendor.CreateVendorSchema()) def create_update_vendor(): """Create a new vendor or update an existing vendor. Params: vendor_id (int): Required for updates. Unique identifier for a vendor. vendor_name (str): Required for creates. email (str): Required. email + vendor_name = unique identifier for new vendor create. owner (str | None): Optional. owner name. Default is orchard. label_identifier (str): Optional. label identifier. status (str | None): Optional. label status. Default is signed. company_brand (str | None): Optional. The Company's brand this vendor belongs to. service_tier_uuid (str): Required. uuid of the service tier the vendor will be in. migrated_to_abacus (bool | None): Optional. country (str | None): Optional. Country affiliated with brand, iso alpha 3 format. assigned_to (int | None): Optional. orchardadmin_user id the vendor is assigned to. assigned_reviewer (int | None): Optional. Foreign key to orchadmin_users table. source (str): Required. The source that is creating the 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 ) ) if not g.request_context.context_type == header_constants.CONTEXT_TYPE_PROFILE: return flaskify(error_response.create_error_forbidden()) @Neo4jSession(transaction=True, use_v2=True, database=NEO4J_DATABASE_NAME) def call_logic(): data = request.get_json() return flaskify(vendor.create_or_update_vendor(data)) return call_logic() @tracer.wrap() @app.route('/v2/vendors', methods=['POST']) @Neo4jSession(transaction=True, use_v2=True, database=NEO4J_DATABASE_NAME) @validate_request_data(create_vendor.V2CreateVendorSchema()) def create_vendor(): """Create a vendor with minimal attributes. Properties: -name (str):Full name of the Vendor -owner (str): Owner of this Vendor (what business unit it maps back to) -company_brand (str): Company's brand name. eg: awal, columbia, theorchard, hrs -service_tier_uuid (str): Unique id for service tier -payment_currency (str): 3 char currency code. Not validated! Returns: Flask.response """ request_data = request.json company_brand = request_data['company_brand'] if not authorization_backend.is_authorized( action='create', resource_id=0, resource_type='account', resource_getter=CompanyBrandByNameResourceGetter(company_brand), ): return flaskify( response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Forbidden', status=403 ) ) result_response = vendor.create_vendor(request_data) return flaskify(result_response) @app.route('/v2/vendor/', methods=['PATCH']) @validate_request_data(UpdateVendorSchema()) def update_vendor(vendor_id): """Update partial vendor information. Args: -vendor_id (int): unique identifier for a vendor. Properties: -name (str): full name of the Vendor -company_brand (str): Company's brand name. eg: awal, columbia, theorchard, hrs -company_brand_uuid (str): unique id for company brand. -service_tier_uuid (str): unique id for service tier. -label_identifier (enum): 'Frontline','Client Services','Catalog','D3','Film','TV','Test'. . . . for full list of supported updatable properties see update_vendor validation schema. Returns: Flask.response: field(s) changed and update timestamp """ 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()) @Neo4jSession(transaction=True, use_v2=True, database=NEO4J_DATABASE_NAME) def call_logic(): data = request.get_json() user_id = request.headers.get(header_constants.ORCHARD_USER_ID) if user_id: if user_id.startswith('oa:'): data['last_modified_by'] = user_id.lstrip('oa:') else: return flaskify( response.Response( status=401, message=f'Unauthorized user with user_id = {user_id}' ) ) return flaskify(vendor.update_vendor(vendor_id, data)) return call_logic() @app.route('/vendor//distributor', methods=['PATCH']) def set_vendor_distributor(vendor_id): """Set is_distributor field from N to Y. Args: vendor_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 ) ) return flaskify(vendor.update_is_distributor_in_vendor(vendor_id))