From 5f03a08404cb9bcd0bba1865ecb3feb5d364ae42 Mon Sep 17 00:00:00 2001 From: Andi Pieper Date: Mon, 7 Oct 2024 14:46:39 +0200 Subject: [PATCH 1/4] added sunset header, deprecation header --- httpobs/website/__init__.py | 4 ++-- httpobs/website/api.py | 18 ++++++++++++++- httpobs/website/decorators.py | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/httpobs/website/__init__.py b/httpobs/website/__init__.py index dc2d3f4..dc2ba2f 100644 --- a/httpobs/website/__init__.py +++ b/httpobs/website/__init__.py @@ -1,3 +1,3 @@ -from httpobs.website.decorators import add_response_headers, sanitized_api_response +from httpobs.website.decorators import add_response_headers, sanitized_api_response, add_sunset_headers, check_for_deprecation_override_header -__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..fc5a435 100644 --- a/httpobs/website/api.py +++ b/httpobs/website/api.py @@ -10,7 +10,7 @@ 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, sanitized_api_response, add_sunset_headers, check_for_deprecation_override_header from httpobs.website.utils import valid_hostname api = Blueprint('api', __name__) @@ -20,6 +20,8 @@ @api.route('/api/v1/analyze', methods=['GET', 'OPTIONS', 'POST']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) @sanitized_api_response def api_post_scan_hostname(): @@ -119,6 +121,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']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) def api_get_grade_totals(): totals = database.select_star_from('grade_distribution') @@ -130,6 +134,8 @@ def api_get_grade_totals(): @api.route('/api/v1/getHostHistory', methods=['GET', 'OPTIONS']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) def api_get_host_history(): # Get the hostname @@ -163,6 +169,8 @@ def api_get_host_history(): @api.route('/api/v1/getRecentScans', methods=['GET', 'OPTIONS']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) def api_get_recent_scans(): try: @@ -184,6 +192,8 @@ def api_get_recent_scans(): # TODO: Deprecate @api.route('/api/v1/getScannerStates', methods=['GET', 'OPTIONS']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) def api_get_scanner_states(): stats = database.select_scan_scanner_statistics(verbose=False) @@ -192,6 +202,8 @@ def api_get_scanner_states(): @api.route('/api/v1/__stats__', methods=['GET', 'OPTIONS']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) def api_get_scanner_stats(): pretty = True if request.args.get('pretty', '').lower() == 'true' else False @@ -259,6 +271,8 @@ def api_get_scanner_stats(): @api.route('/api/v1/getScanResults', methods=['GET', 'OPTIONS']) +@check_for_deprecation_override_header +@add_sunset_headers() @add_response_headers(cors=True) @sanitized_api_response def api_get_scan_results(): @@ -288,6 +302,8 @@ def api_get_scan_results(): @api.route('/contribute.json', methods=['GET']) +@check_for_deprecation_override_header +@add_sunset_headers() @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..6478ecf 100644 --- a/httpobs/website/decorators.py +++ b/httpobs/website/decorators.py @@ -2,6 +2,49 @@ 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 "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('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 + +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): """ From 51ac0db5433377d738193d1695e65a3ab06a52b3 Mon Sep 17 00:00:00 2001 From: Andi Pieper Date: Mon, 7 Oct 2024 15:04:46 +0200 Subject: [PATCH 2/4] add sunset header to root response, reorder decorators --- httpobs/website/api.py | 16 ++++++++-------- httpobs/website/main.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/httpobs/website/api.py b/httpobs/website/api.py index fc5a435..0a10585 100644 --- a/httpobs/website/api.py +++ b/httpobs/website/api.py @@ -20,8 +20,8 @@ @api.route('/api/v1/analyze', methods=['GET', 'OPTIONS', 'POST']) -@check_for_deprecation_override_header @add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) @sanitized_api_response def api_post_scan_hostname(): @@ -121,8 +121,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']) -@check_for_deprecation_override_header @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') @@ -134,8 +134,8 @@ def api_get_grade_totals(): @api.route('/api/v1/getHostHistory', methods=['GET', 'OPTIONS']) -@check_for_deprecation_override_header @add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_host_history(): # Get the hostname @@ -169,8 +169,8 @@ def api_get_host_history(): @api.route('/api/v1/getRecentScans', methods=['GET', 'OPTIONS']) -@check_for_deprecation_override_header @add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) def api_get_recent_scans(): try: @@ -192,8 +192,8 @@ def api_get_recent_scans(): # TODO: Deprecate @api.route('/api/v1/getScannerStates', methods=['GET', 'OPTIONS']) -@check_for_deprecation_override_header @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) @@ -202,8 +202,8 @@ def api_get_scanner_states(): @api.route('/api/v1/__stats__', methods=['GET', 'OPTIONS']) -@check_for_deprecation_override_header @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 @@ -271,8 +271,8 @@ def api_get_scanner_stats(): @api.route('/api/v1/getScanResults', methods=['GET', 'OPTIONS']) -@check_for_deprecation_override_header @add_sunset_headers() +@check_for_deprecation_override_header @add_response_headers(cors=True) @sanitized_api_response def api_get_scan_results(): @@ -302,8 +302,8 @@ def api_get_scan_results(): @api.route('/contribute.json', methods=['GET']) -@check_for_deprecation_override_header @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/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!' From d4e6212f154fc67766bcc646e0c695199c3de601 Mon Sep 17 00:00:00 2001 From: Andi Pieper Date: Mon, 7 Oct 2024 15:39:56 +0200 Subject: [PATCH 3/4] make it X-Deprecation-Override --- httpobs/website/decorators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httpobs/website/decorators.py b/httpobs/website/decorators.py index 6478ecf..524dc1a 100644 --- a/httpobs/website/decorators.py +++ b/httpobs/website/decorators.py @@ -20,13 +20,13 @@ def wrapper(*args, **kwargs): def check_for_deprecation_override_header(fn): """ - Checks for the "Deprecation-Override" header and sets the response accordingly: + 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('Deprecation-Override', 'no').lower() == 'yes': + if request.headers.get('X-Deprecation-Override', 'no').lower() == 'yes': return fn(*args, **kwargs) else: return make_response(""" @@ -38,7 +38,7 @@ def wrapper(*args, **kwargs): If you really want to continue with this endpoint for now, please add a header to your request in the form of -Deprecation-Override: yes +X-Deprecation-Override: yes Be aware that this API will go away without further warning on Oct 31, 2024. """, 410) From c541896f8bffb2a23f50b18e3d8b88a298008d5b Mon Sep 17 00:00:00 2001 From: Andi Pieper Date: Mon, 7 Oct 2024 17:49:26 +0200 Subject: [PATCH 4/4] formatting --- httpobs/website/__init__.py | 14 ++++++++++++-- httpobs/website/api.py | 7 ++++++- httpobs/website/decorators.py | 24 +++++++++++++++--------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/httpobs/website/__init__.py b/httpobs/website/__init__.py index dc2ba2f..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, add_sunset_headers, check_for_deprecation_override_header +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', 'add_sunset_headers', 'check_for_deprecation_override_header'] +__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 0a10585..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, add_sunset_headers, check_for_deprecation_override_header +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__) diff --git a/httpobs/website/decorators.py b/httpobs/website/decorators.py index 524dc1a..cb10996 100644 --- a/httpobs/website/decorators.py +++ b/httpobs/website/decorators.py @@ -2,10 +2,12 @@ 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): @@ -24,24 +26,28 @@ def check_for_deprecation_override_header(fn): - 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 + 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 - + +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) + """, + 410, + ) return wrapper