From 5f6059fa0c946d7356066a0be04933bb9d192f8d Mon Sep 17 00:00:00 2001 From: Dhaval Rajpara Date: Wed, 3 Sep 2025 16:03:20 +0530 Subject: [PATCH] RANGER-5271 : Last login date and time on Ranger Home page --- .../java/org/apache/ranger/biz/SessionMgr.java | 13 +++++++++++++ .../java/org/apache/ranger/biz/UserMgr.java | 2 ++ .../org/apache/ranger/db/XXAuthSessionDao.java | 17 +++++++++++++++++ .../org/apache/ranger/view/VXPortalUser.java | 15 +++++++++++++++ .../resources/META-INF/jpa_named_queries.xml | 5 +++++ .../webapp/react-webapp/src/styles/style.css | 2 +- .../webapp/react-webapp/src/views/Layout.jsx | 11 +++++++++++ .../react-webapp/src/views/SideBar/SideBar.jsx | 2 +- 8 files changed, 65 insertions(+), 2 deletions(-) diff --git a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java index bca2e2ea6f..4ae81cb611 100644 --- a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java +++ b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -326,6 +327,18 @@ public XXAuthSession processFailureLogin(int authStatus, int authType, return gjAuthSession; } + public Date getLastSuccessLoginAuthTimeByUserId(String loginId) { + XXAuthSession xXAuthSession = daoManager.getXXAuthSession().getLastSuccessLoginAuthSessionByUserId(loginId); + + if (xXAuthSession != null) { + return authSessionService.populateViewBean(xXAuthSession).getAuthTime(); + } else { + logger.info("Session cleaned up or User logged in for first time"); + } + + return null; + } + protected boolean validateUserSession(UserSessionBase userSession, String currentLoginId) { if (currentLoginId diff --git a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java index f7433f6f21..14cbfa9072 100644 --- a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java +++ b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java @@ -585,7 +585,9 @@ public VXPortalUser mapXXPortalUserToVXPortalUser(XXPortalUser user, userProfile.setUserRoleList(userRoleList); } + userProfile.setLastLoginTime(sessionMgr.getLastSuccessLoginAuthTimeByUserId(sess.getLoginId())); userProfile.setUserSource(user.getUserSource()); + return userProfile; } diff --git a/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java b/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java index b49d192b53..297546ed12 100644 --- a/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java +++ b/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java @@ -71,6 +71,23 @@ public List getAuthSessionByUserId(Long userId){ } } + public XXAuthSession getLastSuccessLoginAuthSessionByUserId(String loginId) { + if (loginId == null) { + return null; + } + try { + List sessions = getEntityManager() + .createNamedQuery("XXAuthSession.getSuccessAuthSessionsByUserId", tClass) + .setParameter("loginId", loginId) + .setParameter("authStatus", XXAuthSession.AUTH_STATUS_SUCCESS) + .setMaxResults(2) + .getResultList(); + return sessions.size() >= 2 ? sessions.get(1) : null; + } catch (NoResultException ignoreNoResultFound) { + return null; + } + } + public long getRecentAuthFailureCountByLoginId(String loginId, int timeRangezSecond){ Date authWindowStartTime = new Date(DateUtil.getUTCDate().getTime() - timeRangezSecond * 1000); diff --git a/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java b/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java index 27bae7e8a8..6db4d9d9ab 100644 --- a/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java +++ b/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java @@ -20,14 +20,17 @@ package org.apache.ranger.view; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import org.apache.ranger.common.AppConstants; +import org.apache.ranger.json.JsonDateSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; @JsonAutoDetect(getterVisibility=Visibility.NONE, setterVisibility=Visibility.NONE, fieldVisibility=Visibility.ANY) @JsonInclude(JsonInclude.Include.NON_NULL) @@ -95,6 +98,9 @@ public class VXPortalUser extends VXDataObject implements java.io.Serializable { */ protected String syncSource; + @JsonSerialize(using = JsonDateSerializer.class) + protected Date lastLoginTime; + /** * Configuration properties. * @@ -348,6 +354,14 @@ public void setSyncSource(final String syncSource) { this.syncSource = syncSource; } + public Date getLastLoginTime() { + return lastLoginTime; + } + + public void setLastLoginTime(Date lastLoginTime) { + this.lastLoginTime = lastLoginTime; + } + /** * This return the bean content in string format * @return formatedStr @@ -366,6 +380,7 @@ public String toString( ) { str += "userRoleList={" + userRoleList + "} "; str += "otherAttributes={" + otherAttributes + "} "; str += "syncSource={" + syncSource + "} "; + str += "lastLoginTime={" + lastLoginTime + "} "; str += "}"; return str; } diff --git a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml index 6f7108eac3..9db7bf1ca9 100755 --- a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml +++ b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml @@ -48,6 +48,11 @@ DELETE FROM XXAuthSession obj WHERE obj.id in :ids + + SELECT obj FROM XXAuthSession obj WHERE obj.loginId = :loginId and obj.authStatus = :authStatus order by obj.id DESC + + + diff --git a/security-admin/src/main/webapp/react-webapp/src/styles/style.css b/security-admin/src/main/webapp/react-webapp/src/styles/style.css index 646ebebd5f..57adcf354b 100644 --- a/security-admin/src/main/webapp/react-webapp/src/styles/style.css +++ b/security-admin/src/main/webapp/react-webapp/src/styles/style.css @@ -1309,7 +1309,7 @@ header { } .top-scroll { - bottom: 3px; + bottom: 10px; right: 5px; } diff --git a/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx b/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx index 1edb384232..d6199b41f5 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx @@ -40,6 +40,7 @@ import { Loader } from "../components/CommonComponents"; import { Suspense } from "react"; import { PathAssociateWithModule } from "../utils/XAEnums"; import { flatMap, values } from "lodash"; +import dateFormat from "dateformat"; const Layout = () => { let location = useLocation(); @@ -94,6 +95,13 @@ const Layout = () => { activate(); }; + const lastLoginInformation = userProfile?.lastLoginTime + ? `You last logged in on, ${dateFormat( + new Date(userProfile.lastLoginTime), + "dddd, mmm dd, yyyy, hh:MM:ss TT" + )}` + : ""; + useEffect(() => { const interval = setInterval(() => { if (isPrompted()) { @@ -170,6 +178,9 @@ const Layout = () => { > Licensed under the Apache License, Version 2.0 + + {lastLoginInformation} +

diff --git a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx index 19078216b9..6a5db4502c 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx @@ -125,7 +125,7 @@ export const SideBar = () => { const userProps = getUserProfile(); const loginId = ( - {userProps?.loginId} + {userProps?.loginId} ); let location = useLocation();