diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java index 4297584009..0b8d2eabb9 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java @@ -114,4 +114,9 @@ public class WebUser { public String getDisableReason() { return user.getDisableReason(); } + + @Nullable + public Instant getLastLoginTime() { + return user.getLastLoginTime(); + } } diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index 526d3ef5d9..65ff78e00c 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -88,6 +88,8 @@ type AdminUserInfo { disabledBy: String @since(version: "25.0.2") "Reason why the user was disabled" disableReason: String @since(version: "25.0.2") + "Date of the user's last successful login" + lastLoginTime: DateTime @since(version: "26.1.1") } diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java index ad67175441..f2e359c088 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java @@ -142,4 +142,9 @@ public class AdminUserInfo { public String getDisableReason() { return user.getDisableReason(); } + + @Nullable + public String getLastLoginTime() { + return user.getLastLoginTime() == null ? null : CBModelConstants.ISO_DATE_FORMAT.format(user.getLastLoginTime()); + } } diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql index 360bc5226a..fb5aa1a673 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql @@ -102,6 +102,7 @@ CREATE TABLE {table_prefix}CB_USER CHANGE_DATE TIMESTAMP NULL, DISABLED_BY VARCHAR(128) NULL, DISABLE_REASON VARCHAR(128) NULL, + LAST_LOGIN_TIME TIMESTAMP NULL, PRIMARY KEY (USER_ID), FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE, diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_29.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_29.sql new file mode 100644 index 0000000000..a668673c56 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_29.sql @@ -0,0 +1 @@ +ALTER TABLE {table_prefix}CB_USER ADD LAST_LOGIN_TIME TIMESTAMP NULL; \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index a7a779f0bb..175d6da96d 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -709,7 +709,7 @@ public class CBEmbeddedSecurityController SMUser user; try (PreparedStatement dbStat = dbCon.prepareStatement( """ - SELECT U.USER_ID,U.IS_ACTIVE,U.DEFAULT_AUTH_ROLE,S.IS_SECRET_STORAGE,U.CHANGE_DATE,U.DISABLED_BY,U.DISABLE_REASON + SELECT U.USER_ID,U.IS_ACTIVE,U.DEFAULT_AUTH_ROLE,S.IS_SECRET_STORAGE,U.CHANGE_DATE,U.DISABLED_BY,U.DISABLE_REASON,U.LAST_LOGIN_TIME FROM {table_prefix}CB_USER U, {table_prefix}CB_AUTH_SUBJECT S WHERE U.USER_ID=? AND U.USER_ID=S.SUBJECT_ID""") ) { @@ -783,7 +783,7 @@ public class CBEmbeddedSecurityController // Read users try (PreparedStatement dbStat = dbCon.prepareStatement( "SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE,CHANGE_DATE,DISABLED_BY," - + "DISABLE_REASON FROM {table_prefix}CB_USER" + + "DISABLE_REASON,LAST_LOGIN_TIME FROM {table_prefix}CB_USER" + buildUsersFilter(filter) + "\nORDER BY USER_ID " + getOffsetLimitPart(filter))) { setUsersFilterValues(dbStat, filter, 1); @@ -1484,11 +1484,23 @@ public class CBEmbeddedSecurityController ); } + private void updateUserLastLoginTime(@NotNull Connection dbCon, @NotNull String userId) throws SQLException { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "UPDATE {table_prefix}CB_USER SET LAST_LOGIN_TIME=? WHERE USER_ID=?" + )) { + dbStat.setTimestamp(1, Timestamp.from(Instant.now())); + dbStat.setString(2, userId); + dbStat.executeUpdate(); + } + } + @NotNull private SMUser fetchUser(ResultSet dbResult, boolean checkSecretStorage) throws SQLException { Timestamp timestamp = dbResult.getTimestamp("CHANGE_DATE"); Instant disableDate = timestamp != null ? timestamp.toInstant() : null; - return new SMUser( + Timestamp lastLoginTimestamp = dbResult.getTimestamp("LAST_LOGIN_TIME"); + Instant lastLoginTime = lastLoginTimestamp != null ? lastLoginTimestamp.toInstant() : null; + SMUser user = new SMUser( dbResult.getString("USER_ID"), stringToBoolean(dbResult.getString("IS_ACTIVE")), dbResult.getString("DEFAULT_AUTH_ROLE"), @@ -1497,6 +1509,8 @@ public class CBEmbeddedSecurityController dbResult.getString("DISABLED_BY"), dbResult.getString("DISABLE_REASON") ); + user.setLastLoginTime(lastLoginTime); + return user; } @Override @@ -2643,6 +2657,9 @@ public class CBEmbeddedSecurityController permissions = new SMAuthPermissions( activeUserId, smSessionId, getUserPermissions(activeUserId, tokenAuthRole) ); + if (CommonUtils.isNotEmpty(activeUserId)) { + updateUserLastLoginTime(dbCon, activeUserId); + } txn.commit(); } } catch (SQLException e) { diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java index ddfcdaaf01..1b977bb514 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java @@ -72,7 +72,7 @@ import javax.sql.DataSource; public class CBDatabase extends InternalDB { private static final Log log = Log.getLog(CBDatabase.class); - private static final int CURRENT_SCHEMA_VERSION = 28; + private static final int CURRENT_SCHEMA_VERSION = 29; private static final String SCHEMA_ID = "CB_CE"; private static final SQLSchemaConfig SCHEMA_CREATE_CONFIG = new SQLSchemaConfig(