From c5d705210d4571f12314a4e986e50c06792f9877 Mon Sep 17 00:00:00 2001 From: Anna-Maria Meriniemi Date: Tue, 5 Sep 2017 01:16:52 +0300 Subject: [PATCH] Read-only maintenance mode setting for config.py (#356) Disables all POSTs, optionally allowing users to log in (without updating last login date) Blocked POSTs will redirect to the GET endpoint if possible, otherwise to referrer or in last case, home page. API requests will get a plaintext message with 405 status code. --- config.example.py | 8 +++++++- nyaa/templates/layout.html | 6 ++++++ nyaa/views/__init__.py | 31 +++++++++++++++++++++++++++++++ nyaa/views/account.py | 9 +++++++-- nyaa/views/main.py | 11 ++++++----- nyaa/views/users.py | 4 ++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/config.example.py b/config.example.py index 53b2a40..9054e44 100644 --- a/config.example.py +++ b/config.example.py @@ -1,7 +1,13 @@ import os - DEBUG = True +# A read-only maintenance mode, in which the database is not modified +MAINTENANCE_MODE = True +# A maintenance message (used in layout.html template) +MAINTENANCE_MODE_MESSAGE = 'Site is currently in read-only maintenance mode.' +# Allow logging in during maintenance (without updating last login date) +MAINTENANCE_MODE_LOGINS = True + USE_RECAPTCHA = False USE_EMAIL_VERIFICATION = False USE_MYSQL = True diff --git a/nyaa/templates/layout.html b/nyaa/templates/layout.html index 9d7feea..737847d 100644 --- a/nyaa/templates/layout.html +++ b/nyaa/templates/layout.html @@ -312,6 +312,12 @@
{% include "flashes.html" %} + {% if config.MAINTENANCE_MODE and config.MAINTENANCE_MODE_MESSAGE %} + + {% endif %} {% block body %}{% endblock %}
diff --git a/nyaa/views/__init__.py b/nyaa/views/__init__.py index ae58c99..32f745b 100644 --- a/nyaa/views/__init__.py +++ b/nyaa/views/__init__.py @@ -1,3 +1,5 @@ +import flask + from nyaa.views import ( # isort:skip account, admin, @@ -8,8 +10,37 @@ from nyaa.views import ( # isort:skip ) +def _maintenance_mode_hook(): + ''' Blocks POSTs, unless MAINTENANCE_MODE_LOGINS is True and the POST is for a login. ''' + if flask.request.method == 'POST': + allow_logins = flask.current_app.config['MAINTENANCE_MODE_LOGINS'] + endpoint = flask.request.endpoint + + if not (allow_logins and endpoint == 'account.login'): + message = 'Site is currently in maintenance mode.' + + # In case of an API request, return a plaintext error message + if endpoint.startswith('api.'): + resp = flask.make_response(message, 405) + resp.headers['Content-Type'] = 'text/plain' + return resp + else: + # Otherwise redirect to the target page and flash a message + flask.flash(flask.Markup(message), 'danger') + try: + target_url = flask.url_for(endpoint) + except: + # Non-GET-able endpoint, try referrer or default to home page + target_url = flask.request.referrer or flask.url_for('main.home') + return flask.redirect(target_url) + + def register_views(flask_app): """ Register the blueprints using the flask_app object """ + # Add our POST blocker first + if flask_app.config['MAINTENANCE_MODE']: + flask_app.before_request(_maintenance_mode_hook) + flask_app.register_blueprint(account.bp) flask_app.register_blueprint(admin.bp) flask_app.register_blueprint(main.bp) diff --git a/nyaa/views/account.py b/nyaa/views/account.py index 5e9615a..003f061 100644 --- a/nyaa/views/account.py +++ b/nyaa/views/account.py @@ -21,6 +21,10 @@ def login(): form = forms.LoginForm(flask.request.form) if flask.request.method == 'POST' and form.validate(): + if app.config['MAINTENANCE_MODE'] and not app.config['MAINTENANCE_MODE_LOGINS']: + flask.flash(flask.Markup('Logins are currently disabled.'), 'danger') + return flask.redirect(flask.url_for('account.login')) + username = form.username.data.strip() password = form.password.data user = models.User.by_username(username) @@ -40,8 +44,9 @@ def login(): user.last_login_date = datetime.utcnow() user.last_login_ip = ip_address(flask.request.remote_addr).packed - db.session.add(user) - db.session.commit() + if not app.config['MAINTENANCE_MODE']: + db.session.add(user) + db.session.commit() flask.g.user = user flask.session['user_id'] = user.id diff --git a/nyaa/views/main.py b/nyaa/views/main.py index d9f9789..21ed75e 100644 --- a/nyaa/views/main.py +++ b/nyaa/views/main.py @@ -41,11 +41,12 @@ def before_request(): flask.session.permanent = True flask.session.modified = True - ip = ip_address(flask.request.remote_addr) - if user.last_login_ip != ip: - user.last_login_ip = ip.packed - db.session.add(user) - db.session.commit() + if not app.config['MAINTENANCE_MODE']: + ip = ip_address(flask.request.remote_addr) + if user.last_login_ip != ip: + user.last_login_ip = ip.packed + db.session.add(user) + db.session.commit() # Check if user is banned on POST if flask.request.method == 'POST': diff --git a/nyaa/views/users.py b/nyaa/views/users.py index 31b9861..48cf861 100644 --- a/nyaa/views/users.py +++ b/nyaa/views/users.py @@ -190,6 +190,10 @@ def view_user(user_name): @bp.route('/user/activate/') def activate_user(payload): + if app.config['MAINTENANCE_MODE']: + flask.flash(flask.Markup('Activations are currently disabled.'), 'danger') + return flask.redirect(flask.url_for('main.home')) + s = get_serializer() try: user_id = s.loads(payload)