From 610aef428dba5bb3368d9690f39f6e2371b34629 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 1 Apr 2019 14:06:44 -0400 Subject: [PATCH] Add user's last login to the session data --- atst/domain/auth.py | 11 +++++++++++ atst/domain/users.py | 7 +++++++ atst/models/user.py | 4 +--- atst/routes/__init__.py | 9 +++++++-- atst/routes/dev.py | 6 ++---- tests/domain/test_users.py | 13 +++++++++++++ tests/test_auth.py | 16 ++++++++++++++++ 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/atst/domain/auth.py b/atst/domain/auth.py index 8be8429f..256511f9 100644 --- a/atst/domain/auth.py +++ b/atst/domain/auth.py @@ -22,6 +22,8 @@ def apply_authentication(app): user = get_current_user() if user: g.current_user = user + g.last_login = get_last_login() + if should_redirect_to_user_profile(request, user): return redirect(url_for("users.user", next=request.path)) elif not _unprotected_route(request): @@ -50,9 +52,18 @@ def get_current_user(): return False +def get_last_login(): + last_login = session.get("last_login") + if last_login and session.get("user_id"): + return last_login + else: + return False + + def logout(): if session.get("user_id"): # pragma: no branch del session["user_id"] + del session["last_login"] def _unprotected_route(request): diff --git a/atst/domain/users.py b/atst/domain/users.py index 21a896a3..b276a5bd 100644 --- a/atst/domain/users.py +++ b/atst/domain/users.py @@ -1,5 +1,6 @@ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.exc import IntegrityError +from datetime import datetime from atst.database import db from atst.models import User @@ -82,6 +83,12 @@ class Users(object): return user + @classmethod + def update_last_login(cls, user): + setattr(user, "last_login", datetime.now()) + db.session.add(user) + db.session.commit() + @classmethod def finalize(cls, user): user.provisional = False diff --git a/atst/models/user.py b/atst/models/user.py index eea21268..1a156fec 100644 --- a/atst/models/user.py +++ b/atst/models/user.py @@ -36,9 +36,7 @@ class User( citizenship = Column(String) designation = Column(String) date_latest_training = Column(Date) - last_login = Column( - TIMESTAMP(timezone=True), nullable=True - ) + last_login = Column(TIMESTAMP(timezone=True), nullable=True) provisional = Column(Boolean) diff --git a/atst/routes/__init__.py b/atst/routes/__init__.py index 2f1ad3c2..b4543d07 100644 --- a/atst/routes/__init__.py +++ b/atst/routes/__init__.py @@ -122,6 +122,12 @@ def redirect_after_login_url(): return url_for("atst.home") +def current_user_setup(user): + session["user_id"] = user.id + session["last_login"] = user.last_login + Users.update_last_login(user) + + @bp.route("/login-redirect") def login_redirect(): auth_context = _make_authentication_context() @@ -131,8 +137,7 @@ def login_redirect(): if user.provisional: Users.finalize(user) - session["user_id"] = user.id - + current_user_setup(user) return redirect(redirect_after_login_url()) diff --git a/atst/routes/dev.py b/atst/routes/dev.py index 6a9631d5..72a9b75f 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -1,7 +1,6 @@ from flask import ( Blueprint, request, - session, redirect, render_template, url_for, @@ -9,7 +8,7 @@ from flask import ( ) import pendulum -from . import redirect_after_login_url +from . import redirect_after_login_url, current_user_setup from atst.domain.users import Users from atst.domain.permission_sets import PermissionSets from atst.queue import queue @@ -124,8 +123,7 @@ def login_dev(): user_data, ), ) - session["user_id"] = user.id - + current_user_setup(user) return redirect(redirect_after_login_url()) diff --git a/tests/domain/test_users.py b/tests/domain/test_users.py index c83cbd1e..1c0cdcbc 100644 --- a/tests/domain/test_users.py +++ b/tests/domain/test_users.py @@ -1,4 +1,5 @@ import pytest +from datetime import datetime from uuid import uuid4 from atst.domain.users import Users @@ -65,3 +66,15 @@ def test_update_user_with_dod_id(): Users.update(new_user, {"dod_id": "1234567890"}) assert "dod_id" in str(excinfo.value) + + +def test_update_user_with_last_login(): + new_user = UserFactory.create(last_login=datetime.now()) + Users.update_last_login(new_user) + last_login = new_user.last_login + + with pytest.raises(UnauthorizedError): + Users.update(new_user, {"last_login": datetime.now()}) + + Users.update_last_login(new_user) + assert new_user.last_login > last_login diff --git a/tests/test_auth.py b/tests/test_auth.py index 5b95a9d7..9d89697a 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,6 +1,7 @@ from urllib.parse import urlparse import pytest +from datetime import datetime from flask import session, url_for from .mocks import DOD_SDN_INFO, DOD_SDN, FIXTURE_EMAIL_ADDRESS from atst.domain.users import Users @@ -224,3 +225,18 @@ def test_error_on_invalid_crl(client, monkeypatch): response = _login(client) assert response.status_code == 401 assert "Error Code 008" in response.data.decode() + + +def test_last_login_set_when_user_logs_in(client, monkeypatch): + last_login = datetime.now() + user = UserFactory.create(last_login=last_login) + monkeypatch.setattr( + "atst.domain.authnid.AuthenticationContext.authenticate", lambda *args: True + ) + monkeypatch.setattr( + "atst.domain.authnid.AuthenticationContext.get_user", lambda *args: user + ) + response = _login(client) + assert session["last_login"] + assert user.last_login > session["last_login"] + assert isinstance(session["last_login"], datetime)