diff --git a/config.default.yml b/config.default.yml index c2acbff122a557974cd72f898494da147916c67d..b2cd80f6f44d473e832b2723e0d13504e724ed92 100644 --- a/config.default.yml +++ b/config.default.yml @@ -67,3 +67,12 @@ show_github_link: false # Integer: How many items to show per page in the admin interface # Default: 20 admin_links_per_page: 20 + +# List: IP ranges that are allowed to create new links. Everyone is allowed if unset. +# Example: +# allowed_ip_ranges: +# - 127.0.0.1 +# - 130.89.0.0/16 +# - 2001:67c:2564::/48 +# Default: unset +allowed_ip_ranges: diff --git a/liteshort.py b/liteshort.py index ec87a47139b29e34fb5152869c5801a96de66e18..b36a91a77bc1b297d1901758634e810b2ee8d11b 100644 --- a/liteshort.py +++ b/liteshort.py @@ -2,9 +2,10 @@ # This file is part of liteshort by 132ikl # This software is license under the MIT license. It should be included in your copy of this software. # A copy of the MIT license can be obtained at https://mit-license.org/ +import ipaddress from flask import Flask, current_app, flash, g, jsonify, make_response, redirect, render_template, request, \ - send_from_directory, url_for, session + send_from_directory, url_for, session, abort import bcrypt import os import random @@ -157,6 +158,19 @@ def nested_list_to_dict(l): def response(rq, result, error_msg="Error: Unknown error"): + + # Only allow responses to whitelisted IP ranges + whitelist_disabled = 'allowed_ip_ranges' not in app.config or app.config['allowed_ip_ranges'] is None + if not whitelist_disabled: + is_allowed = False + for range in app.config['allowed_ip_ranges']: + network = ipaddress.ip_network(range) + remote = ipaddress.ip_address(request.remote_addr) + if remote in network: + is_allowed = True + if not is_allowed: + abort(403) + if rq.form.get('api') and not rq.form.get('format') == 'json': return "Format type HTML (default) not support for API" # Future-proof for non-json return types if rq.form.get('format') == 'json': @@ -234,6 +248,11 @@ app.secret_key = app.config['secret_key'] app.config['SERVER_NAME'] = app.config['site_domain'] +@app.errorhandler(403) +def access_denied(e): + return render_template("403_access_denied.html") + + @app.route('/favicon.ico', subdomain=app.config['subdomain']) def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), @@ -323,7 +342,8 @@ def main_post(): @app.route('/login', methods=['POST']) def login(): - if 'admin_hashed_password' not in app.config or app.config['admin_hashed_password'] is None: + if ('admin_hashed_password' not in app.config or app.config['admin_hashed_password'] is None) and ( + 'admin_password' not in app.config or app.config['admin_password'] is None): raise AssertionError("Login is disabled.") if authenticate(request.form['username'], request.form['password']): @@ -361,12 +381,13 @@ def admin(): page = request.args.get('page', '1') try: page = int(page) - if page > page_count: - return make_response(redirect(url_for('admin')+"?page="+str(page_count))) - if page < 1: - return make_response(redirect(url_for('admin')+"?page=1")) except ValueError: page = 1 + if page_count != 0: + if page > page_count: + return make_response(redirect(url_for('admin')+"?page="+str(page_count))) + if page < 1: + return make_response(redirect(url_for('admin')+"?page=1")) urls = list_shortlinks_page(page, app.config['admin_links_per_page']) diff --git a/templates/403_access_denied.html b/templates/403_access_denied.html new file mode 100644 index 0000000000000000000000000000000000000000..8cfb07aa57583bda5a878899c3cd74d57d3af6ec --- /dev/null +++ b/templates/403_access_denied.html @@ -0,0 +1,73 @@ + + + + + + {{ config.site_name }} + + + + + +
+ +
+
+
+
+
+
+

Access Denied

+

+ Sorry, but you can only create new links on this URL shortener if you are connected to + the University of Twente network. +

+

+ Please try again later if you are on that network again, + or please activate a VPN connection to create a new link. +

+

+ If you think this is a mistake, please contact the Website committee of Inter-Actief. +

+
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + {% if category == 'error' %} +
+ ✖ {{ message }} +
+ {% endif %} + {% endfor %} + {% endif %} + {% endwith %} + + +
+
+
+ +