1010import io .jans .as .model .config .adminui .AdminConf ;
1111import io .jans .as .model .jwt .Jwt ;
1212import io .jans .as .model .jwt .JwtClaims ;
13+ import io .jans .ca .plugin .adminui .service .config .AUIConfigurationService ;
1314import io .jans .ca .plugin .adminui .utils .CommonUtils ;
1415import io .jans .configapi .core .model .adminui .AUIConfiguration ;
1516import io .jans .configapi .core .model .adminui .AdminUISession ;
2930import org .json .JSONArray ;
3031import org .json .JSONObject ;
3132import org .slf4j .Logger ;
33+
3234import java .util .*;
35+ import java .util .concurrent .TimeUnit ;
36+
37+ import static io .jans .ca .plugin .adminui .utils .CommonUtils .addMinutes ;
38+
3339import static io .jans .as .model .util .Util .escapeLog ;
3440
3541@ ApplicationScoped
@@ -54,6 +60,9 @@ public class AdminUISessionService {
5460 @ Inject
5561 ConfigHttpService httpService ;
5662
63+ @ Inject
64+ AUIConfigurationService auiConfigurationService ;
65+
5766 /**
5867 * Builds the LDAP distinguished name (DN) for a session identifier.
5968 *
@@ -89,14 +98,79 @@ public AdminUISession getSession(String sessionId) {
8998 return configApiSession ;
9099 }
91100
101+ /**
102+ * Updates the expiration time of an Admin UI session based on user activity.
103+ * <p>
104+ * After a successful login, an {@link AdminUISession} is persisted in the database
105+ * with an expiration date derived from the configured session timeout. On each
106+ * subsequent request, this method may extend the session expiration to enforce
107+ * an idle-based logout policy.
108+ * </p>
109+ *
110+ * <p>
111+ * The session expiration is refreshed only when:
112+ * <ul>
113+ * <li>The session and its expiration date are not {@code null}</li>
114+ * <li>More than 30 seconds has passed since the session was last updated</li>
115+ * </ul>
116+ * </p>
117+ *
118+ * <p>
119+ * This approach prevents frequent database updates while ensuring that an
120+ * active user session remains valid and a force logout occurs only when the
121+ * application has been idle longer than the configured
122+ * {@code max_idle_time}.
123+ * </p>
124+ *
125+ * @param adminUISession the persisted Admin UI session to be evaluated and updated
126+ */
127+ public void updateSessionExpiryDate (AdminUISession adminUISession ) {
128+ if (adminUISession == null || adminUISession .getExpirationDate () == null ) {
129+ return ;
130+ }
131+ try {
132+ Date lastUpdated = adminUISession .getLastUpdated ();
133+ long nowMillis = System .currentTimeMillis ();
134+
135+ // Update expiry date only if last update was more than 30 sec ago : the intent of the 30-second throttle is to reduce database writes
136+ if (lastUpdated != null ) {
137+ long secondsSinceLastUpdate =
138+ TimeUnit .MILLISECONDS .toSeconds (nowMillis - lastUpdated .getTime ());
139+
140+ if (secondsSinceLastUpdate < 30 ) {
141+ return ;
142+ }
143+ }
144+
145+ AUIConfiguration config = auiConfigurationService .getAUIConfiguration ();
146+ if (config == null || config .getSessionTimeoutInMins () == null ) {
147+ logger .warn ("AUI configuration is null, cannot update session expiry" );
148+ return ;
149+ }
150+ int sessionTimeoutMins = config .getSessionTimeoutInMins ();
151+
152+ Date now = new Date (nowMillis );
153+ // do not update if the sesiion is already expired. AdminUICookieFilter will remove this session.
154+ if (adminUISession .getExpirationDate ().before (now )) {
155+ return ;
156+ }
157+ adminUISession .setExpirationDate (addMinutes (now , sessionTimeoutMins ));
158+ adminUISession .setLastUpdated (now );
159+ persistenceEntryManager .merge (adminUISession );
160+ } catch (Exception e ) {
161+ logger .warn ("Failed to update session expiry for session {}" ,
162+ adminUISession .getSessionId (), e );
163+ }
164+ }
165+
92166 /**
93167 * Removes all AdminUISession entries whose expirationDate is earlier than the current time.
94168 * This method queries sessions under the service's session base DN and deletes any persisted
95169 * AdminUISession whose expiration date has already passed.
96170 */
97171 public void removeAllExpiredSessions () {
98172 final Filter filter = Filter .createPresenceFilter (SID );
99- List <AdminUISession > adminUISessions = persistenceEntryManager .findEntries (SESSION_DN , AdminUISession .class , filter );
173+ List <AdminUISession > adminUISessions = persistenceEntryManager .findEntries (SESSION_DN , AdminUISession .class , filter );
100174 Date currentDate = new Date ();
101175 adminUISessions .stream ().filter (ele ->
102176 ((ele .getExpirationDate ().getTime () - currentDate .getTime ()) < 0 ))
@@ -106,7 +180,7 @@ public void removeAllExpiredSessions() {
106180 /**
107181 * Checks whether a cached token is active by calling the Admin UI introspection endpoint.
108182 *
109- * @param token the token to introspect; may be null or empty
183+ * @param token the token to introspect; may be null or empty
110184 * @param auiConfiguration configuration holding the introspection endpoint URL
111185 * @return `true` if the introspection response contains `"active": true`, `false` otherwise
112186 * @throws JsonProcessingException if the introspection response body cannot be parsed as JSON
@@ -124,7 +198,7 @@ public boolean isCachedTokenValid(String token, AUIConfiguration auiConfiguratio
124198 .executePost (auiConfiguration .getAuiBackendApiServerIntrospectionEndpoint (),
125199 token , CommonUtils .toUrlEncodedString (body ),
126200 ContentType .APPLICATION_FORM_URLENCODED ,
127- "Bearer " );
201+ "Bearer " );
128202 String jsonString = null ;
129203 if (httpServiceResponse .getHttpResponse () != null
130204 && httpServiceResponse .getHttpResponse ().getStatusLine () != null ) {
@@ -133,15 +207,15 @@ public boolean isCachedTokenValid(String token, AUIConfiguration auiConfiguratio
133207 "httpServiceResponse.getHttpResponse():{}, httpServiceResponse.getHttpResponse().getStatusLine():{}, httpServiceResponse.getHttpResponse().getEntity():{}" ,
134208 httpServiceResponse .getHttpResponse (), httpServiceResponse .getHttpResponse ().getStatusLine (),
135209 httpServiceResponse .getHttpResponse ().getEntity ());
136- if (httpServiceResponse .getHttpResponse ().getStatusLine ().getStatusCode () == 200 ) {
210+ if (httpServiceResponse .getHttpResponse ().getStatusLine ().getStatusCode () == 200 ) {
137211 ObjectMapper mapper = new ObjectMapper ();
138212
139213 HttpEntity httpEntity = httpServiceResponse .getHttpResponse ().getEntity ();
140214 if (httpEntity != null ) {
141215 jsonString = httpService .getContent (httpEntity );
142216
143217 HashMap <String , Object > payloadMap = mapper .readValue (jsonString , HashMap .class );
144- if (payloadMap .containsKey ("active" )) {
218+ if (payloadMap .containsKey ("active" )) {
145219 return (boolean ) payloadMap .get ("active" );
146220 }
147221 return false ;
@@ -157,7 +231,7 @@ public boolean isCachedTokenValid(String token, AUIConfiguration auiConfiguratio
157231 *
158232 * @param ujwtString the user-info JWT to include in the token request; must be non-null and non-empty to generate a token
159233 * @param auiConfiguration configuration containing the backend token endpoint, client ID, encrypted client secret, and redirect URI
160- * @return a TokenResponse containing the access token, or `null` if `ujwtString` is null or empty
234+ * @return a TokenResponse containing the access token, or `null` if `ujwtString` is null or empty
161235 * @throws StringEncrypter.EncryptionException if decrypting the client secret fails
162236 * @throws JsonProcessingException if parsing token responses fails
163237 */
@@ -187,14 +261,14 @@ public TokenResponse getApiProtectionToken(String ujwtString, AUIConfiguration a
187261 }
188262
189263 /**
190- * Exchange token request parameters with the authorization server and return parsed token response parameters.
191- *
192- * @param tokenRequest token request details (grant type, client credentials, redirect URI; may include authorization code and PKCE verifier)
193- * @param tokenEndpoint the token endpoint URL to call
194- * @param userInfoJwt optional user-info JWT to include as the `ujwt` parameter
195- * @return a map of token response parameters (for example `access_token`, `expires_in`) with any `token_type` entry removed
196- * @throws ConfigApiApplicationException if the HTTP exchange fails or the response cannot be parsed as JSON
197- */
264+ * Exchange token request parameters with the authorization server and return parsed token response parameters.
265+ *
266+ * @param tokenRequest token request details (grant type, client credentials, redirect URI; may include authorization code and PKCE verifier)
267+ * @param tokenEndpoint the token endpoint URL to call
268+ * @param userInfoJwt optional user-info JWT to include as the `ujwt` parameter
269+ * @return a map of token response parameters (for example `access_token`, `expires_in`) with any `token_type` entry removed
270+ * @throws ConfigApiApplicationException if the HTTP exchange fails or the response cannot be parsed as JSON
271+ */
198272 public Map <String , Object > getToken (TokenRequest tokenRequest , String tokenEndpoint , String userInfoJwt ) throws ConfigApiApplicationException {
199273
200274 try {
@@ -221,7 +295,7 @@ public Map<String, Object> getToken(TokenRequest tokenRequest, String tokenEndpo
221295
222296 HttpServiceResponse httpServiceResponse = httpService
223297 .executePost (tokenEndpoint , tokenRequest .getEncodedCredentials (), CommonUtils .toUrlEncodedString (body ), ContentType .APPLICATION_FORM_URLENCODED ,
224- "Basic " );
298+ "Basic " );
225299 String jsonString = null ;
226300 if (httpServiceResponse .getHttpResponse () != null
227301 && httpServiceResponse .getHttpResponse ().getStatusLine () != null ) {
@@ -230,7 +304,7 @@ public Map<String, Object> getToken(TokenRequest tokenRequest, String tokenEndpo
230304 " FINAL httpServiceResponse.getHttpResponse():{}, httpServiceResponse.getHttpResponse().getStatusLine():{}, httpServiceResponse.getHttpResponse().getEntity():{}" ,
231305 httpServiceResponse .getHttpResponse (), httpServiceResponse .getHttpResponse ().getStatusLine (),
232306 httpServiceResponse .getHttpResponse ().getEntity ());
233- if (httpServiceResponse .getHttpResponse ().getStatusLine ().getStatusCode () == 200 ) {
307+ if (httpServiceResponse .getHttpResponse ().getStatusLine ().getStatusCode () == 200 ) {
234308 ObjectMapper mapper = new ObjectMapper ();
235309
236310 HttpEntity httpEntity = httpServiceResponse .getHttpResponse ().getEntity ();
0 commit comments