From 9815fc25266545ac5fcb64fbd0a94f0082402b9c Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Thu, 16 Dec 2021 00:05:01 +0100 Subject: [PATCH] RSS allow access via token (#310) Allow access via a token * New RSS URL * Redirect the old RSS feed URL * fix tests --- changedetectionio/__init__.py | 104 ++++++++++++------ changedetectionio/store.py | 8 +- .../templates/watch-overview.html | 2 +- changedetectionio/tests/test_backend.py | 2 +- 4 files changed, 78 insertions(+), 38 deletions(-) diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index e2e1f677..5a407e1f 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -224,12 +224,72 @@ def changedetection_app(config=None, datastore_o=None): # Disable password loginif there is not one set app.config['LOGIN_DISABLED'] = datastore.data['settings']['application']['password'] == False + # For the RSS path, allow access via a token + if request.path == '/rss' and request.args.get('token'): + app_rss_token = datastore.data['settings']['application']['rss_access_token'] + rss_url_token = request.args.get('token') + if app_rss_token == rss_url_token: + app.config['LOGIN_DISABLED'] = True + + @app.route("/rss", methods=['GET']) + @login_required + def rss(): + + limit_tag = request.args.get('tag') + + # Sort by last_changed and add the uuid which is usually the key.. + sorted_watches = [] + + # @todo needs a .itemsWithTag() or something + for uuid, watch in datastore.data['watching'].items(): + + if limit_tag != None: + # Support for comma separated list of tags. + for tag_in_watch in watch['tag'].split(','): + tag_in_watch = tag_in_watch.strip() + if tag_in_watch == limit_tag: + watch['uuid'] = uuid + sorted_watches.append(watch) + + else: + watch['uuid'] = uuid + sorted_watches.append(watch) + + sorted_watches.sort(key=lambda x: x['last_changed'], reverse=True) + + fg = FeedGenerator() + fg.title('changedetection.io') + fg.description('Feed description') + fg.link(href='https://changedetection.io') + + for watch in sorted_watches: + if not watch['viewed']: + # Re #239 - GUID needs to be individual for each event + # @todo In the future make this a configurable link back (see work on BASE_URL https://github.com/dgtlmoon/changedetection.io/pull/228) + guid = "{}/{}".format(watch['uuid'], watch['last_changed']) + fe = fg.add_entry() + fe.title(watch['url']) + fe.link(href=watch['url']) + fe.description(watch['url']) + fe.guid(guid, permalink=False) + dt = datetime.datetime.fromtimestamp(int(watch['newest_history_key'])) + dt = dt.replace(tzinfo=pytz.UTC) + fe.pubDate(dt) + + response = make_response(fg.rss_str()) + response.headers.set('Content-Type', 'application/rss+xml') + return response + @app.route("/", methods=['GET']) @login_required def index(): limit_tag = request.args.get('tag') pause_uuid = request.args.get('pause') + # Redirect for the old rss path which used the /?rss=true + if request.args.get('rss'): + return redirect(url_for('rss', tag=limit_tag)) + if pause_uuid: try: datastore.data['watching'][pause_uuid]['paused'] ^= True @@ -239,7 +299,6 @@ def changedetection_app(config=None, datastore_o=None): except KeyError: pass - # Sort by last_changed and add the uuid which is usually the key.. sorted_watches = [] for uuid, watch in datastore.data['watching'].items(): @@ -259,42 +318,17 @@ def changedetection_app(config=None, datastore_o=None): sorted_watches.sort(key=lambda x: x['last_changed'], reverse=True) existing_tags = datastore.get_all_tags() - rss = request.args.get('rss') - - if rss: - fg = FeedGenerator() - fg.title('changedetection.io') - fg.description('Feed description') - fg.link(href='https://changedetection.io') - - for watch in sorted_watches: - if not watch['viewed']: - # Re #239 - GUID needs to be individual for each event - # @todo In the future make this a configurable link back (see work on BASE_URL https://github.com/dgtlmoon/changedetection.io/pull/228) - guid = "{}/{}".format(watch['uuid'], watch['last_changed']) - fe = fg.add_entry() - fe.title(watch['url']) - fe.link(href=watch['url']) - fe.description(watch['url']) - fe.guid(guid, permalink=False) - dt = datetime.datetime.fromtimestamp(int(watch['newest_history_key'])) - dt = dt.replace(tzinfo=pytz.UTC) - fe.pubDate(dt) - - response = make_response(fg.rss_str()) - response.headers.set('Content-Type', 'application/rss+xml') - return response - else: - from changedetectionio import forms - form = forms.quickWatchForm(request.form) + from changedetectionio import forms + form = forms.quickWatchForm(request.form) - output = render_template("watch-overview.html", - form=form, - watches=sorted_watches, - tags=existing_tags, - active_tag=limit_tag, - has_unviewed=datastore.data['has_unviewed']) + output = render_template("watch-overview.html", + form=form, + watches=sorted_watches, + tags=existing_tags, + active_tag=limit_tag, + app_rss_token=datastore.data['settings']['application']['rss_access_token'], + has_unviewed=datastore.data['has_unviewed']) return output diff --git a/changedetectionio/store.py b/changedetectionio/store.py index 7dca4290..5728cbb5 100644 --- a/changedetectionio/store.py +++ b/changedetectionio/store.py @@ -49,7 +49,7 @@ class ChangeDetectionStore: # Custom notification content 'notification_title': None, 'notification_body': None, - 'notification_format': None, + 'notification_format': None } } } @@ -147,6 +147,12 @@ class ChangeDetectionStore: else: self.__data['app_guid'] = str(uuid_builder.uuid4()) + # Generate the URL access token for RSS feeds + if not 'rss_access_token' in self.__data['settings']['application']: + import secrets + secret = secrets.token_hex(16) + self.__data['settings']['application']['rss_access_token'] = secret + self.needs_write = True # Finally start the thread that will manage periodic data saves to JSON diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/templates/watch-overview.html index b7647cf0..ba994b65 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/templates/watch-overview.html @@ -92,7 +92,7 @@ all {% if active_tag%}in "{{active_tag}}"{%endif%}
  • - +
  • diff --git a/changedetectionio/tests/test_backend.py b/changedetectionio/tests/test_backend.py index 3b013f61..610e12b7 100644 --- a/changedetectionio/tests/test_backend.py +++ b/changedetectionio/tests/test_backend.py @@ -59,7 +59,7 @@ def test_check_basic_change_detection_functionality(client, live_server): assert b'unviewed' in res.data # #75, and it should be in the RSS feed - res = client.get(url_for("index", rss="true")) + res = client.get(url_for("rss")) expected_url = url_for('test_endpoint', _external=True) assert b'