"""Application Handlers. The handlers for the feature api has two components: a (which is part of the url) and a context (which provides additional information – for example activity stats.) Both help determine if a user is part (or not) of a specific feature. """ from flask import g from flask import request from owsresponse import response from owsresponse.adaptors.flask import flaskify from owsrequest import error_response from owsrequest.access import verify_grass_access from features import config from features import logic as features_logic from features.constants import context, header from features.server import app @app.route('/features', methods=['GET'], endpoint='get_features') def get_features(): """Get variants for all features. If grass headers exist, then get data in the user context. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of all features. """ query_account_id = request.args.get('account_id') query_account_type = request.args.get('account_type') has_query_params = query_account_id or query_account_type splitio_enabled = request.args.get('splitioEnabled') if g.request_context.context_type == context.ERROR_CONTEXT_TYPE: return flaskify(error_response.create_error_incomplete_profile_headers()) # TODO : Enable logging once we are up with workstation-spa-login # We allow CONTEXT_TYPE_NONE when there are no headers at all # but if some profile headers are # there, then it should have ORCHARD_IDENTITY_ID. # header_identity = request.headers.get(header.ORCHARD_IDENTITY_ID) # if not header_identity and not g.request_context.context_type == header_constants.CONTEXT_TYPE_NONE and config.ENVIRONMENT == config.QA_ENVIRONMENT: # noqa # config.logger.info( # f'Recieved request without ORCHARD_IDENTITY_ID header ' # f'with {header.CORRELATION_ID}:{request.headers.get(header.CORRELATION_ID)} ' # noqa # f'for {header.GRASS_ACCOUNT_ID}:{request.headers.get(header.GRASS_ACCOUNT_ID)} ' # noqa # f'and {header.ORCHARD_USER_ID}:{request.headers.get(header.ORCHARD_USER_ID)} ' # noqa # f'from {header.ORCHARD_REQUESTOR_SERVICE}:{request.headers.get(header.ORCHARD_REQUESTOR_SERVICE)} ' # noqa # f'in {config.ENVIRONMENT} environment') # if profile context, use profile logic if g.request_context.context_type == context.PROFILE_CONTEXT_TYPE: if config.SPLITIO_ENABLED or splitio_enabled == 'true': result = features_logic.get_all_split_features_with_profile_context( # noqa g.request_context.profile_type, g.request_context.profile_id, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_profile_context( g.request_context.profile_type, g.request_context.profile_id, g.request_context.identity_id, ) headers = {'Cache-Control': 'max-age=10'} return flaskify(result, headers) if g.request_context.context_type == context.ACCOUNT_CONTEXT_TYPE: orchard_user_id = g.request_context.orchard_user_id account_type = query_account_type or g.request_context.profile_type account_id = query_account_id or g.request_context.profile_id vendor_id = None subaccount_id = None if account_type == header.VENDOR_GRASS_TYPE: vendor_id = account_id elif account_type == header.SUBACCOUNT_GRASS_TYPE: subaccount_id = account_id # If query parameters and headers were provided, ensure they match. if has_query_params: verification_response = verify_grass_access( g.request_context.profile_type, g.request_context.profile_id, False, vendor=account_id, subaccount=subaccount_id, ) if verification_response.status != 200: return flaskify(verification_response) # Get features without user context. if g.request_context.context_type == context.NONE_CONTEXT_TYPE: if config.SPLITIO_ENABLED == 'true' or splitio_enabled: orchard_user_id = None result = features_logic.get_all_split_features_with_user_context( orchard_user_id, subaccount_id, vendor_id, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_without_user_context( subaccount_id, vendor_id ) else: if config.SPLITIO_ENABLED == 'true' or splitio_enabled: result = features_logic.get_all_split_features_with_user_context( orchard_user_id, subaccount_id, vendor_id, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_user_context( orchard_user_id, subaccount_id, vendor_id ) headers = {'Cache-Control': 'max-age=10'} return flaskify(result, headers) @app.route('/features/vendor//user/', methods=['GET']) def get_features_for_vendor(vendor_id, orchard_user_id): """Get variants for all features in a vendor context. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of all features. """ if ':' not in orchard_user_id: return flaskify(error_response.create_error_user_does_not_exist()) splitio_enabled = request.args.get('splitioEnabled') if config.SPLITIO_ENABLED or splitio_enabled is not None: result = features_logic.get_all_split_features_with_user_context( orchard_user_id, None, vendor_id, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_user_context( orchard_user_id, None, vendor_id ) return flaskify(result) @app.route( '/features/vendor//user_type//user_id/', methods=['GET'], ) def get_features_for_vendor_with_user_type(vendor_id, user_type, user_id): """Get variants for all features in a vendor context. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of all features. """ combined_user_id = ':'.join([user_type, user_id]) splitio_enabled = request.args.get('splitioEnabled') if config.SPLITIO_ENABLED or splitio_enabled is not None: result = features_logic.get_all_split_features_with_user_context( combined_user_id, None, vendor_id, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_user_context( combined_user_id, None, vendor_id ) return flaskify(result) @app.route( '/features/user/', methods=['GET'], endpoint='get_features_for_user', ) def get_features_for_user(orchard_user_id): """Get variants for all features in a user context. If grass headers exist, then validate and confirm that the user is correct. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of all features. """ if g.request_context.context_type == context.ERROR_CONTEXT_TYPE: return flaskify(error_response.create_error_incomplete_account_headers()) header_orchard_user_id = None if g.request_context.context_type == context.ACCOUNT_CONTEXT_TYPE: header_orchard_user_id = g.request_context.orchard_user_id if header_orchard_user_id and (header_orchard_user_id != orchard_user_id): return flaskify(error_response.create_error_user_mismatch()) if ':' not in orchard_user_id: return flaskify(error_response.create_error_user_does_not_exist()) splitio_enabled = request.args.get('splitioEnabled') if config.SPLITIO_ENABLED or splitio_enabled is not None: result = features_logic.get_all_split_features_with_user_context( orchard_user_id, None, None, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_user_context( orchard_user_id, None, None ) return flaskify(result) @app.route('/features/user_type//user_id/', methods=['GET']) def get_features_for_user_with_user_type(user_type, user_id): """Get variants for all features in a user context provided user_type. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of all features. """ combined_user_id = ':'.join([user_type, user_id]) splitio_enabled = request.args.get('splitioEnabled') if config.SPLITIO_ENABLED or splitio_enabled is not None: result = features_logic.get_all_split_features_with_user_context( combined_user_id, None, None, g.request_context.identity_id, g.request_context.identity_uuid, ) else: result = features_logic.get_all_features_with_user_context( combined_user_id, None, None ) return flaskify(result) @app.route('/features/', methods=['GET'], endpoint='get_feature') def get_feature(feature_name): """Get variants for a single feature. If grass headers exist, then get data in Orchard-User-Id context. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of the feature. """ splitio_enabled = request.args.get('splitioEnabled') if g.request_context.context_type == context.ERROR_CONTEXT_TYPE: return flaskify(error_response.create_error_incomplete_account_headers()) orchard_user_id = None if g.request_context.context_type == context.ACCOUNT_CONTEXT_TYPE: orchard_user_id = g.request_context.orchard_user_id if config.SPLITIO_ENABLED or splitio_enabled is not None: # if profile context, use profile logic if g.request_context.context_type == context.PROFILE_CONTEXT_TYPE: result = features_logic.get_single_split_feature_with_profile_context( # noqa feature_name, g.request_context.profile_type, g.request_context.profile_id, g.request_context.identity_id, g.request_context.identity_uuid, ) headers = {'Cache-Control': 'max-age=10'} return flaskify(result, headers) elif g.request_context.context_type == context.ACCOUNT_CONTEXT_TYPE: if g.request_context.profile_type == 'vendor': all_features = features_logic.get_all_split_features_with_user_context( # noqa orchard_user_id, None, g.request_context.profile_id, g.request_context.identity_id, g.request_context.identity_uuid, ) result = response.Response(all_features.message.get(feature_name)) elif g.request_context.profile_type == 'subaccount': all_features = features_logic.get_all_split_features_with_user_context( # noqa orchard_user_id, g.request_context.profile_id, None, g.request_context.identity_id, g.request_context.identity_uuid, ) result = response.Response(all_features.message.get(feature_name)) else: # OA user all_features = features_logic.get_all_split_features_with_user_context( # noqa orchard_user_id, None, None, g.request_context.identity_uuid ) result = response.Response(all_features.message.get(feature_name)) else: # regular grass user. result = features_logic.get_single_split_feature_with_user_context( feature_name, orchard_user_id ) else: # if profile context, use profile logic for legacy feature check. if g.request_context.context_type == context.PROFILE_CONTEXT_TYPE: result = features_logic.get_single_feature_with_profile_context( feature_name, g.request_context.profile_type, g.request_context.profile_id, g.request_context.identity_id, ) headers = {'Cache-Control': 'max-age=10'} return flaskify(result, headers) # regular grass user. result = features_logic.get_single_feature_with_user_context( feature_name, orchard_user_id ) return flaskify(result) @app.route( '/features//user/', methods=['GET'], endpoint='get_feature_for_user', ) def get_feature_for_user(feature_name, orchard_user_id): """Get variants for a single feature in a user context. If grass headers exist, then validate and confirm that the user is correct. Returns: flask.Response: on successful retrieval of feature data, returns a 200 with the JSON representation of the feature. """ if g.request_context.context_type == context.ERROR_CONTEXT_TYPE: return flaskify(error_response.create_error_incomplete_account_headers()) header_orchard_user_id = None if g.request_context.context_type == context.ACCOUNT_CONTEXT_TYPE: header_orchard_user_id = g.request_context.orchard_user_id if header_orchard_user_id and (header_orchard_user_id != orchard_user_id): return flaskify(error_response.create_error_user_mismatch()) result = features_logic.get_single_feature_with_user_context( feature_name, orchard_user_id ) return flaskify(result) @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return flaskify(response.Response({'status': 'ok'}))