diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 5bbbb55e..149d741d 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -628,6 +628,7 @@ def changedetection_app(config=None, datastore_o=None): urls = request.values.get('urls').split("\n") for url in urls: url = url.strip() + # Flask wtform validators wont work with basic auth, use validators package if len(url) and validators.url(url): new_uuid = datastore.add_watch(url=url.strip(), tag="") # Straight into the queue. diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 5c193cdb..9af85b31 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -176,7 +176,24 @@ class ValidateTokensList(object): if not p.strip('{}') in notification.valid_tokens: message = field.gettext('Token \'%s\' is not a valid token.') raise ValidationError(message % (p)) + +class validateURL(object): + + """ + Flask wtform validators wont work with basic auth + """ + + def __init__(self, message=None): + self.message = message + def __call__(self, form, field): + import validators + try: + validators.url(field.data.strip()) + except validators.ValidationFailure: + message = field.gettext('\'%s\' is not a valid URL.' % (field.data.strip())) + raise ValidationError(message) + class ValidateListRegex(object): """ Validates that anything that looks like a regex passes as a regex @@ -244,7 +261,7 @@ class ValidateCSSJSONXPATHInput(object): class quickWatchForm(Form): # https://wtforms.readthedocs.io/en/2.3.x/fields/#module-wtforms.fields.html5 # `require_tld` = False is needed even for the test harness "http://localhost:5005.." to run - url = html5.URLField('URL', [validators.URL(require_tld=False)]) + url = html5.URLField('URL', validators=[validateURL()]) tag = StringField('Group tag', [validators.Optional(), validators.Length(max=35)]) class commonSettingsForm(Form): @@ -259,7 +276,7 @@ class commonSettingsForm(Form): class watchForm(commonSettingsForm): - url = html5.URLField('URL', [validators.URL(require_tld=False)]) + url = html5.URLField('URL', validators=[validateURL()]) tag = StringField('Group tag', [validators.Optional(), validators.Length(max=35)]) minutes_between_check = html5.IntegerField('Maximum time in minutes until recheck', diff --git a/changedetectionio/tests/test_auth.py b/changedetectionio/tests/test_auth.py new file mode 100644 index 00000000..45c8c909 --- /dev/null +++ b/changedetectionio/tests/test_auth.py @@ -0,0 +1,39 @@ +#!/usr/bin/python3 + +import time +from flask import url_for +from . util import live_server_setup + +def test_basic_auth(client, live_server): + + live_server_setup(live_server) + # Give the endpoint time to spin up + time.sleep(1) + + # Add our URL to the import page + test_url = url_for('test_basicauth_method', _external=True).replace("//","//myuser:mypass@") + + res = client.post( + url_for("import_page"), + data={"urls": test_url}, + follow_redirects=True + ) + assert b"1 Imported" in res.data + + # Check form validation + res = client.post( + url_for("edit_page", uuid="first"), + data={"css_filter": "", "url": test_url, "tag": "", "headers": "", 'fetch_backend': "html_requests"}, + follow_redirects=True + ) + assert b"Updated watch." in res.data + + # Trigger a check + client.get(url_for("api_watch_checknow"), follow_redirects=True) + time.sleep(1) + res = client.get( + url_for("preview_page", uuid="first"), + follow_redirects=True + ) + + assert b'myuser mypass basic' in res.data \ No newline at end of file diff --git a/changedetectionio/tests/util.py b/changedetectionio/tests/util.py index 54532680..86b78767 100644 --- a/changedetectionio/tests/util.py +++ b/changedetectionio/tests/util.py @@ -103,4 +103,14 @@ def live_server_setup(live_server): print("\n>> Test notification endpoint was hit.\n") return "Text was set" + + # Just return the verb in the request + @live_server.app.route('/test-basicauth', methods=['GET']) + def test_basicauth_method(): + + from flask import request + auth = request.authorization + ret = " ".join([auth.username, auth.password, auth.type]) + return ret + live_server.start()