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 @@
+
+
+
+
+
+
+ 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)