"""Application Handlers. Requests are redirected to handlers, which are responsible for getting information from the URL and passing it down to the logic layer. The way each layer talks to each other is through Response objects which defines the type status of the data and the data itself. Please note: the Orchard uses the term handlers over views as convention for clarity See: oto.response for more details. """ from functools import wraps from flask import g from flask import jsonify from flask import request from owsrequest import flask_request from owsresponse import response from owsresponse.adaptors.flask import flaskify from blacklist_manager import config from blacklist_manager.api import app from blacklist_manager.constants import error from blacklist_manager.logic import blacklist_reasons from blacklist_manager.logic import blacklist_words from blacklist_manager.logic import product from blacklist_manager.utils import authorization from blacklist_manager.utils import pagination def require_pdp_auth(*, action, resource_type, resource_id=0): """Decorator to verify PDP authorization for a resource. Args: action (str): action to perform (e.g. 'view', 'edit') resource_type (str): type of resource resource_id (int): optional ID of resource Returns: function: The decorated route handler, or a 403 Flask response if PDP authorization fails. """ def decorator(func): @wraps(func) def decorated_function(*args, **kwargs): authorized = authorization.pdp_authorize_resource_without_attributes( action=action, resource_id=resource_id, resource_type=resource_type, ) if not authorized: return flaskify( response.create_error_response( code=error.ERROR_CODE_FORBIDDEN, message=error.ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) ) return func(*args, **kwargs) return decorated_function return decorator @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.errorhandler(500) def exception_handler(error): """Handle error when uncaught exception is raised. Default exception handler. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') g.log.exception(error) return flaskify(response.create_fatal_response(message)) @app.route('/blacklist-reasons', methods=['GET']) def get_blacklist_reasons(): """Return a list of all blacklist reasons. Returns: flask.Response: JSON object representing blacklist reasons. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) result = blacklist_reasons.get_blacklist_reasons() return flaskify(result) @app.route('/blacklist-words', methods=['GET']) def get_blacklist_words(): """Return a list of all blacklist words. Returns: flask.Response: JSON object representing blacklist words. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) page = pagination.get_pagination(request) term = request.args.get('q', None) result = blacklist_words.get_blacklist_words( page_offset=page.offset, page_limit=page.limit, term=term) return flaskify(result) @app.route('/blacklist-words/export', methods=['GET']) def export_blacklist_words(): """Export all blacklist words to a csv on S3 and return download url. Returns: flask.Response: JSON object representing blacklist words. """ return flaskify(blacklist_words.export_blacklist_words()) @app.route('/blacklist-reason', methods=['POST']) def create_blacklist_reason(): """Create a new blacklist reason. Returns: flask.Response: On successful creation of blacklist reason will return a 200 along with an id of the record else will return 500 on error. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) blacklist_reason_data = request.get_json() result = blacklist_reasons.create_blacklist_reason(blacklist_reason_data) return flaskify(result) @app.route('/blacklist-reason/', methods=['PUT']) def update_blacklist_reason(blacklist_reason_id): """Update an existing blacklist reason. Args: blacklist_reason_id (int): unique identifier of blacklist_reasons. Returns: flask.Response: JSON object representing id of the blacklisted reason. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) blacklist_reason_update_data = request.get_json() result = blacklist_reasons.update_blacklist_reason( blacklist_reason_id, blacklist_reason_update_data) return flaskify(result) @app.route('/blacklist-word/', methods=['DELETE']) def delete_blacklist_word(blacklist_id): """Delete blacklist words. Args: blacklist_id (int): unique identifier of blacklist_word. Returns: flask.Response: on successful, 200 status else 404 status. """ return flaskify(blacklist_words.delete_blacklist_word(blacklist_id)) @app.route('/blacklist-word', methods=['POST']) def create_blacklist_word(): """Save data in blacklist_words table. Returns: response.Response: wrapper containing the created blacklist word id or errors. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) blacklist_word_data = request.get_json() result = blacklist_words.create_blacklist_word(blacklist_word_data) return flaskify(result) @app.route('/blacklist-word/', methods=['PUT']) def update_blacklist_word(blacklist_id): """Update an existing blacklist word. Args: blacklist_id (int): unique identifier of blacklist_word. Returns: flask.Response: JSON object representing id of the blacklisted word. """ grass_account_type, grass_account_id = \ flask_request.get_grass_headers(request) validation = flask_request.verify_grass_access( request, required=False, vendor=grass_account_id) if not validation: return flaskify(validation) blacklist_word_update_data = request.get_json() result = blacklist_words.update_blacklist_word( blacklist_id, blacklist_word_update_data) return flaskify(result) @app.route('/validate/', methods=['GET']) def validate_product_for_blacklisting(product_id): """Validate if release data contains blacklisted word. Args: product_id (int): the product identifier. Returns: flask.Response: boolean response after validation. else will return 500 on error. """ ownership = flask_request.verify_grass_ownership( request, product.check_product_ownership, product_id) if not ownership: return flaskify(ownership) result = blacklist_words.validate_product_for_blacklisting( product_id) return flaskify(result) @app.route('/validate-text', methods=['POST']) def validate_text_for_blacklisting(): """Validate if text contains a blacklisted word. Returns: flask.Response: boolean response after validation. else will return 500 on error. """ data = request.get_json() result = blacklist_words.validate_text_for_blacklisting(data) return flaskify(result) @app.route('/validate-artists', methods=['POST']) def validate_artists_for_blacklisting(): """Validate if artists data contain a blacklisted word. Returns: flask.Response: boolean response after validation. else will return 500 on error. """ data = request.get_json() result = blacklist_words.validate_artists_for_blacklisting(data) return flaskify(result)