diff --git a/pagure/flask_app.py b/pagure/flask_app.py index 75d558a..ddb1e98 100644 --- a/pagure/flask_app.py +++ b/pagure/flask_app.py @@ -235,6 +235,18 @@ def set_request(): if pagure.utils.authenticated(): flask.g.forkbuttonform = pagure.forms.ConfirmationForm() + # Force logout if current session started before users' + # refuse_sessions_before + login_time = flask.g.fas_user.login_time + # This is because flask_fas_openid will store this as a posix timestamp + if not isinstance(login_time, datetime.datetime): + login_time = datetime.datetime.utcfromtimestamp(login_time) + user = _get_user(username=flask.g.fas_user.username) + if (user.refuse_sessions_before + and login_time < user.refuse_sessions_before): + logout() + return flask.redirect(flask.url_for('ui_ns.index')) + flask.g.justlogedout = flask.session.get('_justloggedout', False) if flask.g.justlogedout: flask.session['_justloggedout'] = None diff --git a/pagure/lib/model.py b/pagure/lib/model.py index 8da2fc8..f7a8ac4 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -211,6 +211,11 @@ class User(BASE): default=sa.func.now(), onupdate=sa.func.now()) + refuse_sessions_before = sa.Column( + sa.DateTime, + nullable=True, + default=None) + # Relations group_objs = relation( "PagureGroup", diff --git a/pagure/templates/user_settings.html b/pagure/templates/user_settings.html index bde2cf9..b1e4640 100644 --- a/pagure/templates/user_settings.html +++ b/pagure/templates/user_settings.html @@ -251,6 +251,29 @@ + +
+
+
+ Force logout +
+
+

+ Click the "Force logout everywhere" to forcefully log out from every + current open session. +

+
+ +
+
+ {% endblock %} diff --git a/pagure/ui/app.py b/pagure/ui/app.py index 786b6e7..8cfc548 100644 --- a/pagure/ui/app.py +++ b/pagure/ui/app.py @@ -960,3 +960,24 @@ def revoke_api_user_token(token_id): 'error') return flask.redirect(flask.url_for('ui_ns.user_settings')) + + +@UI_NS.route('/settings/forcelogout/', methods=('POST', )) +@UI_NS.route('/settings/forcelogout', methods=('POST', )) +@login_required +def force_logout(): + """ Set refuse_sessions_before, logging the user out everywhere + """ + if admin_session_timedout(): + if flask.request.method == 'POST': + flask.flash('Action canceled, try it again', 'error') + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + # Ensure the user is in the DB at least + user = _get_user(username=flask.g.fas_user.username) + + user.refuse_sessions_before = datetime.datetime.utcnow() + flask.g.session.commit() + flask.flash('All active sessions logged out') + return flask.redirect(flask.url_for('ui_ns.user_settings')) diff --git a/tests/test_pagure_flask_ui_login.py b/tests/test_pagure_flask_ui_login.py index 50bfe55..577e7e6 100644 --- a/tests/test_pagure_flask_ui_login.py +++ b/tests/test_pagure_flask_ui_login.py @@ -785,5 +785,34 @@ class PagureFlaskLogintests(tests.SimplePagureTest): self.assertFalse(pagure.flask_app.admin_session_timedout()) + @patch.dict('pagure.config.config', {'PAGURE_AUTH': 'local'}) + def test_force_logout(self): + """ Test forcing logout. """ + user = tests.FakeUser(username='foo') + with tests.user_set(self.app.application, user, keep_get_user=True): + # Test that accessing settings works + output = self.app.get('/settings') + self.assertEqual(output.status_code, 200) + + # Now logout everywhere + output = self.app.post('/settings/forcelogout/') + self.assertEqual(output.status_code, 302) + self.assertEqual(output.headers['Location'], + 'http://localhost/settings') + + # We should now get redirected to index, because our session became + # invalid + output = self.app.get('/settings') + self.assertEqual(output.status_code, 302) + self.assertEqual(output.headers['Location'], + 'http://localhost/') + + # After changing the login_time to now, the session should again be + # valid + user.login_time = datetime.datetime.utcnow() + output = self.app.get('/') + self.assertEqual(output.status_code, 200) + + if __name__ == '__main__': unittest.main(verbosity=2)