diff --git a/changedetectionio/auth_decorator.py b/changedetectionio/auth_decorator.py index 3358e76a..3f495762 100644 --- a/changedetectionio/auth_decorator.py +++ b/changedetectionio/auth_decorator.py @@ -20,10 +20,7 @@ def login_optionally_required(func): has_password_enabled = datastore.data['settings']['application'].get('password') or os.getenv("SALTED_PASS", False) # Permitted - if request.endpoint and 'static_content' in request.endpoint and request.view_args and request.view_args.get('group') == 'styles': - return func(*args, **kwargs) - # Permitted - elif request.endpoint and 'diff_history_page' in request.endpoint and datastore.data['settings']['application'].get('shared_diff_access'): + if request.endpoint and 'diff_history_page' in request.endpoint and datastore.data['settings']['application'].get('shared_diff_access'): return func(*args, **kwargs) elif request.method in flask_login.config.EXEMPT_METHODS: return func(*args, **kwargs) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 78025595..638e9670 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -233,7 +233,8 @@ def changedetection_app(config=None, datastore_o=None): if has_password_enabled and not flask_login.current_user.is_authenticated: # Permitted - if request.endpoint and request.endpoint == 'static_content' and request.view_args and request.view_args.get('group') in ['styles', 'js', 'images', 'favicons']: + if request.endpoint and request.endpoint == 'static_content' and request.view_args: + # Handled by static_content handler return None # Permitted elif request.endpoint and 'login' in request.endpoint: @@ -351,11 +352,15 @@ def changedetection_app(config=None, datastore_o=None): @app.route("/static//", methods=['GET']) def static_content(group, filename): from flask import make_response + import re + group = re.sub(r'[^\w.-]+', '', group.lower()) + filename = re.sub(r'[^\w.-]+', '', filename.lower()) if group == 'screenshot': # Could be sensitive, follow password requirements if datastore.data['settings']['application']['password'] and not flask_login.current_user.is_authenticated: - abort(403) + if not datastore.data['settings']['application'].get('shared_diff_access'): + abort(403) screenshot_filename = "last-screenshot.png" if not request.args.get('error_screenshot') else "last-error-screenshot.png" @@ -404,7 +409,7 @@ def changedetection_app(config=None, datastore_o=None): # These files should be in our subdirectory try: - return send_from_directory("static/{}".format(group), path=filename) + return send_from_directory(f"static/{group}", path=filename) except FileNotFoundError: abort(404) diff --git a/changedetectionio/tests/test_access_control.py b/changedetectionio/tests/test_access_control.py index 65823e3f..b35de268 100644 --- a/changedetectionio/tests/test_access_control.py +++ b/changedetectionio/tests/test_access_control.py @@ -60,6 +60,11 @@ def test_check_access_control(app, client, live_server): res = c.get(url_for('static_content', group='styles', filename='404-testetest.css')) assert res.status_code == 404 + # Access to screenshots should be limited by 'shared_diff_access' + path = url_for('static_content', group='screenshot', filename='random-uuid-that-will-404.png', _external=True) + res = c.get(path) + assert res.status_code == 404 + # Check wrong password does not let us in res = c.post( url_for("login"), @@ -163,7 +168,7 @@ def test_check_access_control(app, client, live_server): url_for("settings.settings_page"), data={"application-password": "foobar", # Should be disabled -# "application-shared_diff_access": "True", + "application-shared_diff_access": "", "requests-time_between_check-minutes": 180, 'application-fetch_backend': "html_requests"}, follow_redirects=True @@ -176,6 +181,10 @@ def test_check_access_control(app, client, live_server): # Should be logged out assert b"Login" in res.data + # Access to screenshots should be limited by 'shared_diff_access' + res = c.get(url_for('static_content', group='screenshot', filename='random-uuid-that-will-403.png')) + assert res.status_code == 403 + # The diff page should return something valid when logged out res = c.get(url_for("ui.ui_views.diff_history_page", uuid="first")) assert b'Random content' not in res.data diff --git a/changedetectionio/tests/test_conditions.py b/changedetectionio/tests/test_conditions.py index b14a98d9..b0e55631 100644 --- a/changedetectionio/tests/test_conditions.py +++ b/changedetectionio/tests/test_conditions.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import json -import urllib +import time from flask import url_for from .util import live_server_setup, wait_for_all_checks @@ -113,6 +113,7 @@ def test_conditions_with_text_and_number(client, live_server): client.get(url_for("ui.form_watch_checknow"), follow_redirects=True) wait_for_all_checks(client) + time.sleep(2) # 75 is > 20 and < 100 and contains "5" res = client.get(url_for("watchlist.index")) assert b'unviewed' in res.data