diff --git a/httpobs/website/__init__.py b/httpobs/website/__init__.py index dc2d3f4..5684dd6 100644 --- a/httpobs/website/__init__.py +++ b/httpobs/website/__init__.py @@ -1,3 +1,13 @@ -from httpobs.website.decorators import add_response_headers, sanitized_api_response +from httpobs.website.decorators import ( + add_response_headers, + add_sunset_headers, + check_for_deprecation_override_header, + sanitized_api_response, +) -__all__ = ['add_response_headers', 'sanitized_api_response'] +__all__ = [ + 'add_response_headers', + 'sanitized_api_response', + 'add_sunset_headers', + 'check_for_deprecation_override_header', +] diff --git a/httpobs/website/api.py b/httpobs/website/api.py index be5b65b..aa01ce1 100644 --- a/httpobs/website/api.py +++ b/httpobs/website/api.py @@ -10,7 +10,12 @@ from httpobs.conf import API_ALLOW_VERBOSE_STATS_FROM_PUBLIC, API_COOLDOWN, DEVELOPMENT_MODE from httpobs.scanner import scan from httpobs.scanner.grader import GRADES, get_score_description -from httpobs.website import add_response_headers, sanitized_api_response +from httpobs.website import ( + add_response_headers, + add_sunset_headers, + check_for_deprecation_override_header, + sanitized_api_response, +) from httpobs.website.utils import valid_hostname api = Blueprint('api', __name__) @@ -20,6 +25,8 @@ @api.route('/api/v1/analyze', methods=['GET', 'OPTIONS', 'POST']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) @sanitized_api_response def api_post_scan_hostname(): @@ -119,6 +126,8 @@ def api_post_scan_hostname(): # TODO: Deprecate this and replace with __stats__ once website is updated @api.route('/api/v1/getGradeDistribution', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_grade_totals(): totals = database.select_star_from('grade_distribution') @@ -130,6 +139,8 @@ def api_get_grade_totals(): @api.route('/api/v1/getHostHistory', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_host_history(): # Get the hostname @@ -163,6 +174,8 @@ def api_get_host_history(): @api.route('/api/v1/getRecentScans', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_recent_scans(): try: @@ -184,6 +197,8 @@ def api_get_recent_scans(): # TODO: Deprecate @api.route('/api/v1/getScannerStates', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_scanner_states(): stats = database.select_scan_scanner_statistics(verbose=False) @@ -192,6 +207,8 @@ def api_get_scanner_states(): @api.route('/api/v1/__stats__', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_scanner_stats(): pretty = True if request.args.get('pretty', '').lower() == 'true' else False @@ -259,6 +276,8 @@ def api_get_scanner_stats(): @api.route('/api/v1/getScanResults', methods=['GET', 'OPTIONS']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) @sanitized_api_response def api_get_scan_results(): @@ -288,6 +307,8 @@ def api_get_scan_results(): @api.route('/contribute.json', methods=['GET']) +@add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers() def contribute_json(): __dirname = os.path.abspath(os.path.dirname(__file__)) diff --git a/httpobs/website/decorators.py b/httpobs/website/decorators.py index 53cccaf..cb10996 100644 --- a/httpobs/website/decorators.py +++ b/httpobs/website/decorators.py @@ -3,6 +3,55 @@ from flask import jsonify, make_response, request +def add_sunset_headers(): + """ + Adds a "Sunset" header to the response + """ + + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + resp = make_response(fn(*args, **kwargs)) + resp.headers['Sunset'] = 'Thu, 31 Oct 2024 23:59:59 GMT' + return resp + + return wrapper + + return decorator + + +def check_for_deprecation_override_header(fn): + """ + Checks for the "X-Deprecation-Override" header and sets the response accordingly: + - If the header is set to "yes", it will return the response as normal + - If the header is set to anything else, it will return a 410 Gone response + """ + + @wraps(fn) + def wrapper(*args, **kwargs): + if request.headers.get('X-Deprecation-Override', 'no').lower() == 'yes': + return fn(*args, **kwargs) + else: + return make_response( + """ +This API has been deprecated and is no longer available. +Please use https://observatory-api.mdn.mozilla.net/. +For details about the new endpint, see +https://github.com/mdn/mdn-http-observatory/blob/main/README.md#post-apiv2scan. + +If you really want to continue with this endpoint for now, +please add a header to your request in the form of + +X-Deprecation-Override: yes + +Be aware that this API will go away without further warning on Oct 31, 2024. + """, + 410, + ) + + return wrapper + + def add_response_headers(headers=None, default_headers=None, cors=False): """ Adds a bunch of headers to the Flask responses diff --git a/httpobs/website/main.py b/httpobs/website/main.py index b7e6ad7..2dbf94b 100644 --- a/httpobs/website/main.py +++ b/httpobs/website/main.py @@ -3,7 +3,7 @@ from flask import Flask from httpobs.conf import API_PORT, API_PROPAGATE_EXCEPTIONS, DEVELOPMENT_MODE -from httpobs.website import add_response_headers +from httpobs.website import add_response_headers, add_sunset_headers from httpobs.website.api import api from httpobs.website.monitoring import monitoring_api @@ -21,6 +21,7 @@ def __exit_with(msg: str) -> None: @app.route('/') +@add_sunset_headers() @add_response_headers() def main() -> str: return 'Welcome to the HTTP Observatory!'