From c95ae02ed09a856df42515796eab24a86a13a4e5 Mon Sep 17 00:00:00 2001 From: Dmitry Volodin Date: Wed, 14 Aug 2019 14:28:08 +0300 Subject: [PATCH 1/2] login: register_last_login option --- aaa/migrations/0005_user_clear_fields.py | 5 ++--- aaa/migrations/0007_user_last_login.py | 21 +++++++++++++++++++++ aaa/models/user.py | 13 +++++++++++++ config.py | 1 + docs/src/en/admin/config-login.rst | 12 ++++++++++++ services/login/backends/base.py | 3 +++ services/login/service.py | 6 ++++++ ui/web/aaa/user/Application.js | 12 ++++++++++-- ui/web/aaa/user/Model.js | 5 +++++ 9 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 aaa/migrations/0007_user_last_login.py diff --git a/aaa/migrations/0005_user_clear_fields.py b/aaa/migrations/0005_user_clear_fields.py index 0090d481f9..9f5c66ca7e 100644 --- a/aaa/migrations/0005_user_clear_fields.py +++ b/aaa/migrations/0005_user_clear_fields.py @@ -12,6 +12,5 @@ from noc.core.migration.base import BaseMigration class Migration(BaseMigration): def migrate(self): - for field in ("is_staff", "last_login"): - if self.db.has_column("auth_user", field): - self.db.delete_column("auth_user", field) + if self.db.has_column("auth_user", "is_staff"): + self.db.delete_column("auth_user", "is_staff") diff --git a/aaa/migrations/0007_user_last_login.py b/aaa/migrations/0007_user_last_login.py new file mode 100644 index 0000000000..d065c843ff --- /dev/null +++ b/aaa/migrations/0007_user_last_login.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------- +# Restore User.last_login +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2019 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# Third-party modules +from django.db.models import DateTimeField + +# NOC modules +from noc.core.migration.base import BaseMigration + + +class Migration(BaseMigration): + def migrate(self): + if not self.db.has_column("auth_user", "last_login"): + self.db.add_column( + "auth_user", "last_login", DateTimeField("Last Login", blank=True, null=True) + ) diff --git a/aaa/models/user.py b/aaa/models/user.py index f87f99b622..f4fa44c92f 100644 --- a/aaa/models/user.py +++ b/aaa/models/user.py @@ -100,6 +100,8 @@ class User(NOCModel): heatmap_lon = models.FloatField("Longitude", blank=True, null=True) heatmap_lat = models.FloatField("Latitude", blank=True, null=True) heatmap_zoom = models.IntegerField("Zoom", blank=True, null=True) + # Last login (Populated by login service) + last_login = models.DateTimeField("Last Login", blank=True, null=True) _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) _name_cache = cachetools.TTLCache(maxsize=100, ttl=60) @@ -170,3 +172,14 @@ class User(NOCModel): """ full_name = "%s %s" % (self.first_name, self.last_name) return full_name.strip() + + def register_login(self, ts=None): + """ + Register user login + + :param ts: Login timestamp + :return: + """ + ts = ts or datetime.datetime.now() + self.last_login = ts + self.save(update_fields=["last_login"]) diff --git a/config.py b/config.py index 12af099acd..1ae71e4456 100644 --- a/config.py +++ b/config.py @@ -330,6 +330,7 @@ class Config(BaseConfig): radius_secret = SecretParameter(default="noc") radius_server = StringParameter() user_cookie_ttl = IntParameter(default=1) + register_last_login = BooleanParameter(default=True) class mailsender(ConfigSection): smtp_server = StringParameter() diff --git a/docs/src/en/admin/config-login.rst b/docs/src/en/admin/config-login.rst index 40b089b922..c45cf7eaf1 100644 --- a/docs/src/en/admin/config-login.rst +++ b/docs/src/en/admin/config-login.rst @@ -146,4 +146,16 @@ user_cookie_ttl **Default Value** 1 ================== ========================= +.. _config-login-register_last_login: +register_last_login +~~~~~~~~~~~~~~~~~~~ + +================== ============================= +**YAML Path** login.register_last_login +**Key-Value Path** login/register_last_login +**Environment** NOC_LOGIN_REGISTER_LAST_LOGIN +**Default Value** True +================== ============================= + +Write each successful login into User's last_login field diff --git a/services/login/backends/base.py b/services/login/backends/base.py index 01ac88d038..2afc710266 100644 --- a/services/login/backends/base.py +++ b/services/login/backends/base.py @@ -34,6 +34,9 @@ class BaseAuthBackend(object): """ Authenticate user using given credentials. Raise LoginError when failed + + :return: User name + :raises LoginError: Authentication error """ raise self.LoginError() diff --git a/services/login/service.py b/services/login/service.py index cdd25d2fce..8199fda558 100755 --- a/services/login/service.py +++ b/services/login/service.py @@ -19,6 +19,7 @@ from noc.services.login.auth import AuthRequestHandler from noc.services.login.logout import LogoutRequestHandler from noc.services.login.api.login import LoginAPI from noc.services.login.backends.base import BaseAuthBackend +from noc.aaa.models.user import User from noc.aaa.models.apikey import APIKey from noc.core.perf import metrics from noc.config import config @@ -77,6 +78,11 @@ class LoginService(UIService): metrics["auth_success", ("method", method)] += 1 # Set cookie handler.set_secure_cookie("noc_user", user, expires_days=config.login.session_ttl) + # Register last login + if config.login.register_last_login: + u = User.get_by_username(user) + if u: + u.register_login() return True self.logger.error("Login failed for %s: %s", c, le) return False diff --git a/ui/web/aaa/user/Application.js b/ui/web/aaa/user/Application.js index 20c129bc27..56076b6be3 100644 --- a/ui/web/aaa/user/Application.js +++ b/ui/web/aaa/user/Application.js @@ -53,12 +53,20 @@ Ext.define("NOC.aaa.user.Application", { { text: __("Active"), dataIndex: "is_active", - renderer: NOC.render.Bool + renderer: NOC.render.Bool, + width: 50 }, { text: __("Superuser"), dataIndex: "is_superuser", - renderer: NOC.render.Bool + renderer: NOC.render.Bool, + width: 50 + }, + { + text: __("Last Login"), + dataIndex: "last_login", + renderer: NOC.render.DateTime, + flex: 1 } ], fields: [ diff --git a/ui/web/aaa/user/Model.js b/ui/web/aaa/user/Model.js index 1d004de1cc..cf1607359e 100644 --- a/ui/web/aaa/user/Model.js +++ b/ui/web/aaa/user/Model.js @@ -60,6 +60,11 @@ Ext.define("NOC.aaa.user.Model", { { name: "user_permissions", type: "auto" + }, + { + name: "last_login", + type: "date", + persist: false } ] }); -- GitLab From ca8ad6138a06db692bc34429e9294c5284a6c8ca Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Wed, 14 Aug 2019 18:33:42 +0500 Subject: [PATCH 2/2] Fix replace password when save. --- services/web/apps/aaa/user/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/apps/aaa/user/views.py b/services/web/apps/aaa/user/views.py index 42f58caa34..046148f043 100644 --- a/services/web/apps/aaa/user/views.py +++ b/services/web/apps/aaa/user/views.py @@ -53,6 +53,7 @@ class UserApplication(ExtModelApplication): "last_name": StringParameter(default=""), "email": StringParameter(default=""), } + ignored_fields = {"id", "bi_id", "password"} custom_m2m_fields = {"permissions": Permission} @classmethod @@ -151,6 +152,7 @@ class UserApplication(ExtModelApplication): return self.response_forbidden("Permission denied") user = self.get_object_or_404(self.model, pk=object_id) user.set_password(password) + user.save() return self.response({"result": "Password changed", "status": True}, self.OK) def can_delete(self, user, obj=None): -- GitLab