You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/08/04 08:54:48 UTC

[isis] branch master updated: ISIS-3109: properly amend authentication with zone-it post sign-in

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 1275167f00 ISIS-3109: properly amend authentication with zone-it post sign-in
1275167f00 is described below

commit 1275167f00db4fd1b6f69167df5380963abd3e31
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Aug 4 10:54:42 2022 +0200

    ISIS-3109: properly amend authentication with zone-it post sign-in
---
 .../user/UserCurrentSessionTimeZoneHolder.java     |  5 +++
 .../internal/debug/xray/_CallStackMerger.java      |  2 -
 .../core/interaction/session/IsisInteraction.java  | 13 +++---
 .../session/InteractionServiceDefault.java         |  2 +-
 .../UserCurrentSessionTimeZoneHolderDefault.java   |  7 +++-
 .../AuthenticationRequestAbstract.java             |  7 ++--
 .../manager/AuthenticationManager.java             | 13 +++++-
 .../isis/security/AuthenticatorsForTesting.java    | 10 ++---
 .../AuthenticationManager_authenticators_Test.java |  3 ++
 ...rdAuthenticationManager_AuthenticationTest.java |  2 +
 .../authenticator/AuthenticatorSecman.java         |  2 -
 .../AuthenticatorSecmanAutoConfiguration.java      |  3 +-
 .../shiro/authentication/AuthenticatorShiro.java   | 13 +++---
 .../spring/webmodule/SpringSecurityFilter.java     |  5 ++-
 .../model/isis/HasAmendableInteractionContext.java | 36 ++++++-----------
 .../wicket/ui/pages/login/IsisSignInPanel.java     | 47 ++++++++++++++++------
 .../wicket/ui/pages/login/SignInPanelAbstract.java | 21 +++++++++-
 .../AuthenticatedWebSessionForIsis.java            | 28 ++++++++-----
 ...uthenticatedWebSessionForIsis_Authenticate.java |  1 +
 .../AuthenticatedWebSessionForIsis_SignIn.java     |  2 +
 20 files changed, 146 insertions(+), 76 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java
index a52215246a..36463ed982 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java
@@ -49,5 +49,10 @@ public interface UserCurrentSessionTimeZoneHolder {
      */
     Optional<ZoneId> getUserTimeZone();
 
+    /**
+     * Clears the user's current {@link ZoneId}
+     * within the context of the current session.
+     */
+    void clearUserTimeZone();
 
 }
\ No newline at end of file
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/_CallStackMerger.java b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/_CallStackMerger.java
index 7a0fbc3840..22b5b3b39d 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/_CallStackMerger.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/_CallStackMerger.java
@@ -124,8 +124,6 @@ final class _CallStackMerger {
         val executionLanes = new ArrayList<int[]>();
 
         logEntries.forEach(logEntry->{
-            //System.err.printf("joining %s%n", logEntry.getLabel());
-
             val executionLane = new int[logEntry.getData().size()];
             executionLanes.add(executionLane);
 
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/session/IsisInteraction.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/session/IsisInteraction.java
index f4f08190ef..eb14b08222 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/session/IsisInteraction.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/session/IsisInteraction.java
@@ -50,6 +50,7 @@ implements InteractionInternal {
     public IsisInteraction(final @NonNull UUID interactionId) {
         this.startedAtSystemNanos = System.nanoTime(); // used to measure time periods, so not using ClockService here
         this.command = new Command(interactionId);
+        log.debug("new IsisInteraction id={}", interactionId);
     }
 
     @Getter(onMethod_ = {@Override})
@@ -108,7 +109,7 @@ implements InteractionInternal {
         }
     }
 
-    private void pushAndStart(ActionInvocation actionInvocation, ClockService clockService, MetricsService metricsService, Command command) {
+    private void pushAndStart(final ActionInvocation actionInvocation, final ClockService clockService, final MetricsService metricsService, final Command command) {
         push(actionInvocation);
         start(actionInvocation, clockService, metricsService, command);
     }
@@ -130,7 +131,7 @@ implements InteractionInternal {
         }
     }
 
-    private <T extends Execution<?,?>> Object executeInternal(MemberExecutor<T> memberExecutor, T execution) {
+    private <T extends Execution<?,?>> Object executeInternal(final MemberExecutor<T> memberExecutor, final T execution) {
 
         try {
             Object result = memberExecutor.execute(execution);
@@ -236,22 +237,22 @@ implements InteractionInternal {
     private final Map<Class<?>, Object> attributes = new HashMap<>();
 
     @Override
-    public <T> T putAttribute(Class<? super T> type, T value) {
+    public <T> T putAttribute(final Class<? super T> type, final T value) {
         return _Casts.uncheckedCast(attributes.put(type, value));
     }
 
     @Override
-    public <T> T computeAttributeIfAbsent(Class<? super T> type, Function<Class<?>, ? extends T> mappingFunction) {
+    public <T> T computeAttributeIfAbsent(final Class<? super T> type, final Function<Class<?>, ? extends T> mappingFunction) {
         return _Casts.uncheckedCast(attributes.computeIfAbsent(type, mappingFunction));
     }
 
     @Override
-    public <T> T getAttribute(Class<T> type) {
+    public <T> T getAttribute(final Class<T> type) {
         return _Casts.uncheckedCast(attributes.get(type));
     }
 
     @Override
-    public void removeAttribute(Class<?> type) {
+    public void removeAttribute(final Class<?> type) {
         attributes.remove(type);
     }
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
index ec48c4be98..de8ab73e4f 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
@@ -117,7 +117,7 @@ implements
             final InteractionAwareTransactionalBoundaryHandler txBoundaryHandler,
             final ClockService clockService,
             final Provider<CommandPublisher> commandPublisherProvider,
-            Provider<TransactionService> transactionServiceProvider,
+            final Provider<TransactionService> transactionServiceProvider,
             final ConfigurableBeanFactory beanFactory,
             final InteractionIdGenerator interactionIdGenerator) {
         this.runtimeEventService = runtimeEventService;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserCurrentSessionTimeZoneHolderDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserCurrentSessionTimeZoneHolderDefault.java
index cbe3d1c60b..d1cfae7c90 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserCurrentSessionTimeZoneHolderDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserCurrentSessionTimeZoneHolderDefault.java
@@ -48,7 +48,6 @@ implements UserCurrentSessionTimeZoneHolder {
     public void setUserTimeZone(final @NonNull ZoneId zoneId) {
         keyValueSessionStore
             .ifPresent(store->store.put(SESSION_KEY_ZONE_ID, zoneId));
-
     }
 
     @Override
@@ -57,4 +56,10 @@ implements UserCurrentSessionTimeZoneHolder {
             .flatMap(store->store.lookupAs(SESSION_KEY_ZONE_ID, ZoneId.class));
     }
 
+    @Override
+    public void clearUserTimeZone() {
+        keyValueSessionStore
+            .ifPresent(store->store.clear(SESSION_KEY_ZONE_ID));
+    }
+
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
index 5734413a1b..3df2579801 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
@@ -29,7 +29,8 @@ import org.apache.isis.commons.internal.collections._Sets;
 
 import static org.apache.isis.commons.internal.base._NullSafe.stream;
 
-public abstract class AuthenticationRequestAbstract implements AuthenticationRequest {
+public abstract class AuthenticationRequestAbstract
+implements AuthenticationRequest {
 
     private final String name;
     private final Set<String> roles = _Sets.newHashSet();
@@ -60,14 +61,14 @@ public abstract class AuthenticationRequestAbstract implements AuthenticationReq
      * @param role
      * @since 2.0
      */
-    public void addRole(String role) {
+    public void addRole(final String role) {
         if(_Strings.isNullOrEmpty(role)) {
             return; // ignore
         }
         this.roles.add(role);
     }
 
-    public void addRoles(@Nullable Collection<String> roles) {
+    public void addRoles(final @Nullable Collection<String> roles) {
         stream(roles)
                 .forEach(this::addRole);
     }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/manager/AuthenticationManager.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/manager/AuthenticationManager.java
index 69a1aa4343..be6ca8554e 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/manager/AuthenticationManager.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/manager/AuthenticationManager.java
@@ -20,6 +20,7 @@ package org.apache.isis.core.security.authentication.manager;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import javax.annotation.Priority;
 import javax.inject.Inject;
@@ -34,6 +35,7 @@ import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.applib.exceptions.unrecoverable.NoAuthenticatorException;
 import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.applib.services.user.UserCurrentSessionTimeZoneHolder;
 import org.apache.isis.applib.util.ToString;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Timing;
@@ -60,16 +62,19 @@ public class AuthenticationManager {
     private final @NonNull RandomCodeGenerator randomCodeGenerator;
     private final @NonNull Can<Registrar> registrars;
     private final @NonNull List<UserMementoRefiner> userMementoRefiners;
+    private final @NonNull Optional<UserCurrentSessionTimeZoneHolder> userCurrentSessionTimeZoneHolder;
 
     @Inject
     public AuthenticationManager(
             final List<Authenticator> authenticators,
             final InteractionService interactionService,
             final RandomCodeGenerator randomCodeGenerator,
+            final Optional<UserCurrentSessionTimeZoneHolder> userCurrentSessionTimeZoneHolder,
             final List<UserMementoRefiner> userMementoRefiners) {
         this.interactionService = interactionService;
         this.randomCodeGenerator = randomCodeGenerator;
         this.authenticators = Can.ofCollection(authenticators);
+        this.userCurrentSessionTimeZoneHolder = userCurrentSessionTimeZoneHolder;
         this.userMementoRefiners = userMementoRefiners;
         if (this.authenticators.isEmpty()) {
             throw new NoAuthenticatorException("No authenticators specified");
@@ -105,14 +110,20 @@ public class AuthenticationManager {
                 val interactionContext = authenticator.authenticate(request, getUnusedRandomCode());
                 if (interactionContext != null) {
 
+                    val interactionContextWithTimeZone = interactionContext
+                            .withTimeZoneIfAny(userCurrentSessionTimeZoneHolder
+                                    .flatMap(UserCurrentSessionTimeZoneHolder::getUserTimeZone));
+
                     val userRefined = UserMementoRefiner.refine(
                             interactionContext.getUser(),
                             userMementoRefiners);
-                    val interactionContextRefined = interactionContext.withUser(userRefined);
 
                     userByValidationCode.put(
                             userRefined.getAuthenticationCode(),
                             userRefined.getName());
+
+                    val interactionContextRefined = interactionContextWithTimeZone
+                            .withUser(userRefined);
                     return interactionContextRefined;
                 }
             }
diff --git a/core/security/src/test/java/org/apache/isis/security/AuthenticatorsForTesting.java b/core/security/src/test/java/org/apache/isis/security/AuthenticatorsForTesting.java
index 79be745f89..873f4928c1 100644
--- a/core/security/src/test/java/org/apache/isis/security/AuthenticatorsForTesting.java
+++ b/core/security/src/test/java/org/apache/isis/security/AuthenticatorsForTesting.java
@@ -32,7 +32,7 @@ public class AuthenticatorsForTesting {
             public boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
                 return true;
             }
-            
+
             @Override
             public boolean isValid(final AuthenticationRequest request) {
                 return true;
@@ -46,21 +46,21 @@ public class AuthenticatorsForTesting {
             public boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
                 return true;
             }
-            
+
             @Override
             public boolean isValid(final AuthenticationRequest request) {
                 return false;
             }
         };
     }
-    
+
     public static AuthenticatorAbstract authenticatorValidForFoo() {
         return new AuthenticatorAbstract() {
             @Override
             public boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
                 return true;
             }
-            
+
             @Override
             public boolean isValid(final AuthenticationRequest request) {
                 if(!"foo".equals(request.getName())) {
@@ -70,6 +70,6 @@ public class AuthenticatorsForTesting {
             }
         };
     }
-    
+
 
 }
diff --git a/core/security/src/test/java/org/apache/isis/security/authentication/standard/AuthenticationManager_authenticators_Test.java b/core/security/src/test/java/org/apache/isis/security/authentication/standard/AuthenticationManager_authenticators_Test.java
index bfac3d48b2..4df586d23c 100644
--- a/core/security/src/test/java/org/apache/isis/security/authentication/standard/AuthenticationManager_authenticators_Test.java
+++ b/core/security/src/test/java/org/apache/isis/security/authentication/standard/AuthenticationManager_authenticators_Test.java
@@ -19,6 +19,7 @@
 package org.apache.isis.security.authentication.standard;
 
 import java.util.Collections;
+import java.util.Optional;
 
 import org.junit.Test;
 
@@ -46,6 +47,7 @@ public class AuthenticationManager_authenticators_Test {
                 Collections.emptyList(),
                 new InteractionService_forTesting(),
                 new RandomCodeGeneratorDefault(),
+                Optional.empty(),
                 Collections.emptyList());
         authenticationManager.authenticate(new AuthenticationRequestPassword("foo", "bar"));
     }
@@ -59,6 +61,7 @@ public class AuthenticationManager_authenticators_Test {
                 Collections.singletonList(auth),
                 new InteractionService_forTesting(),
                 new RandomCodeGeneratorDefault(),
+                Optional.empty(),
                 Collections.emptyList());
         assertThat(authenticationManager.getAuthenticators().size(), is(1));
         assertThat(authenticationManager.getAuthenticators().getElseFail(0), is(sameInstance(auth)));
diff --git a/core/security/src/test/java/org/apache/isis/security/authentication/standard/StandardAuthenticationManager_AuthenticationTest.java b/core/security/src/test/java/org/apache/isis/security/authentication/standard/StandardAuthenticationManager_AuthenticationTest.java
index fe67917dfd..b643e5260b 100644
--- a/core/security/src/test/java/org/apache/isis/security/authentication/standard/StandardAuthenticationManager_AuthenticationTest.java
+++ b/core/security/src/test/java/org/apache/isis/security/authentication/standard/StandardAuthenticationManager_AuthenticationTest.java
@@ -19,6 +19,7 @@
 package org.apache.isis.security.authentication.standard;
 
 import java.util.Collections;
+import java.util.Optional;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +45,7 @@ public class StandardAuthenticationManager_AuthenticationTest {
                 Collections.singletonList(AuthenticatorsForTesting.authenticatorValidForFoo()),
                 new InteractionService_forTesting(),
                 new RandomCodeGeneratorDefault(),
+                Optional.empty(),
                 Collections.emptyList());
     }
 
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
index a6bcf42c15..b4a83676ca 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
@@ -101,6 +101,4 @@ public class AuthenticatorSecman implements Authenticator {
         // be re-authenticated.
     }
 
-
-
 }
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
index 566cf50c86..7d0ce4ae4f 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
@@ -42,7 +42,8 @@ public class AuthenticatorSecmanAutoConfiguration  {
     public Authenticator authenticatorSecman(
             final ApplicationUserRepository applicationUserRepository,
             final @Qualifier("secman") PasswordEncoder passwordEncoder) {
-        return new AuthenticatorSecman(applicationUserRepository, passwordEncoder);
+        return new AuthenticatorSecman(
+                applicationUserRepository, passwordEncoder);
     }
 
 }
diff --git a/security/shiro/src/main/java/org/apache/isis/security/shiro/authentication/AuthenticatorShiro.java b/security/shiro/src/main/java/org/apache/isis/security/shiro/authentication/AuthenticatorShiro.java
index c1ed8429dd..d03bff1da2 100644
--- a/security/shiro/src/main/java/org/apache/isis/security/shiro/authentication/AuthenticatorShiro.java
+++ b/security/shiro/src/main/java/org/apache/isis/security/shiro/authentication/AuthenticatorShiro.java
@@ -81,14 +81,13 @@ public class AuthenticatorShiro implements Authenticator {
     private final boolean autoLogout;
 
     @Inject
-    public AuthenticatorShiro(IsisConfiguration configuration) {
+    public AuthenticatorShiro(
+            final IsisConfiguration configuration) {
         super();
         this.configuration = configuration;
         this.autoLogout = this.configuration.getSecurity().getShiro().isAutoLogoutIfAlreadyAuthenticated();
     }
 
-
-
     @Override
     public final boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
         if(getSecurityManager() == null) {
@@ -158,10 +157,10 @@ public class AuthenticatorShiro implements Authenticator {
     }
 
     InteractionContext authenticationFor(
-            AuthenticationRequest request,
-            String validationCode,
-            AuthenticationToken token,
-            Subject currentSubject) {
+            final AuthenticationRequest request,
+            final String validationCode,
+            final AuthenticationToken token,
+            final Subject currentSubject) {
 
         final Stream<String> roles = Stream.concat(
                 streamRoles(currentSubject, token),
diff --git a/security/spring/src/main/java/org/apache/isis/security/spring/webmodule/SpringSecurityFilter.java b/security/spring/src/main/java/org/apache/isis/security/spring/webmodule/SpringSecurityFilter.java
index c958e4d45f..1be499c002 100644
--- a/security/spring/src/main/java/org/apache/isis/security/spring/webmodule/SpringSecurityFilter.java
+++ b/security/spring/src/main/java/org/apache/isis/security/spring/webmodule/SpringSecurityFilter.java
@@ -34,6 +34,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
 
 import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.applib.services.user.UserCurrentSessionTimeZoneHolder;
 import org.apache.isis.applib.services.user.UserMemento;
 import org.apache.isis.applib.services.user.UserMemento.AuthenticationSource;
 import org.apache.isis.security.spring.authconverters.AuthenticationConverter;
@@ -48,6 +49,7 @@ public class SpringSecurityFilter implements Filter {
 
     @Autowired private InteractionService interactionService;
     @Inject List<AuthenticationConverter> converters;
+    @Inject private UserCurrentSessionTimeZoneHolder userCurrentSessionTimeZoneHolder;
 
     @Override
     public void doFilter(
@@ -85,7 +87,8 @@ public class SpringSecurityFilter implements Filter {
                 .withAuthenticationSource(AuthenticationSource.EXTERNAL);
 
         interactionService.run(
-                InteractionContext.ofUserWithSystemDefaults(userMemento),
+                InteractionContext.ofUserWithSystemDefaults(userMemento)
+                .withTimeZoneIfAny(userCurrentSessionTimeZoneHolder.getUserTimeZone()),
                 ()->filterChain.doFilter(servletRequest, servletResponse));
     }
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/HasAmendableInteractionContext.java
similarity index 52%
copy from api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java
copy to viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/HasAmendableInteractionContext.java
index a52215246a..d814175b90 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserCurrentSessionTimeZoneHolder.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/HasAmendableInteractionContext.java
@@ -16,38 +16,28 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.user;
+package org.apache.isis.viewer.wicket.model.isis;
 
-import java.time.ZoneId;
-import java.util.Optional;
+import java.util.function.UnaryOperator;
+
+import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 
 import lombok.NonNull;
 
 /**
- * Stores the user's current {@link ZoneId} with session scope.
- * <p>
- * eg. on application login
+ * Introduced, to allow for authenticated sessions to be amended with time-zone information
+ * (post sign-in).
  *
- * @since 2.0 {@index}
+ * @since 2.0
  */
-public interface UserCurrentSessionTimeZoneHolder {
+public interface HasAmendableInteractionContext {
 
     /**
-     * Sets the user's current {@link ZoneId}
-     * within the context of the current session.
-     */
-    void setUserTimeZone(@NonNull ZoneId zoneId);
-
-    /**
-     * Optionally returns the user's current {@link ZoneId},
-     * based on whether it was set before,
-     * within the context of the current session.
+     * Replaces the held {@link InteractionContext} with an amended version.
      *
-     * @apiNote not meant to fallback to system defaults,
-     * instead return {@link Optional#empty()},
-     * if there is no specific time-zone information available
+     * @param updater - operator must allow <code>null</code> argument
+     *      and is allowed to return <code>null</code> (required)
      */
-    Optional<ZoneId> getUserTimeZone();
-
+    void amendInteractionContext(@NonNull UnaryOperator<InteractionContext> updater);
 
-}
\ No newline at end of file
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/IsisSignInPanel.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/IsisSignInPanel.java
index 6c19a27077..6c341c699a 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/IsisSignInPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/IsisSignInPanel.java
@@ -18,12 +18,14 @@
  */
 package org.apache.isis.viewer.wicket.ui.pages.login;
 
+import java.time.ZoneId;
 import java.util.Optional;
 
 import javax.inject.Inject;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.Page;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 
 import org.apache.isis.applib.services.iactnlayer.InteractionService;
@@ -33,9 +35,12 @@ import org.apache.isis.applib.services.user.UserCurrentSessionTimeZoneHolder;
 import org.apache.isis.applib.services.userreg.EmailNotificationService;
 import org.apache.isis.applib.services.userreg.UserRegistrationService;
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.viewer.wicket.model.isis.HasAmendableInteractionContext;
 import org.apache.isis.viewer.wicket.model.models.PageType;
 import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry;
 
+import lombok.NonNull;
 import lombok.val;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
@@ -61,7 +66,7 @@ public class IsisSignInPanel extends SignInPanelAbstract {
 
     private final boolean signUpLink;
     private final boolean passwordResetLink;
-    private final boolean clearOriginalDestination;
+    private final boolean isClearOriginalDestination;
 
     /**
      * Constructor
@@ -83,7 +88,7 @@ public class IsisSignInPanel extends SignInPanelAbstract {
         super(id, rememberMe);
         this.signUpLink = signUpLink;
         this.passwordResetLink = passwordResetLink;
-        this.clearOriginalDestination = !continueToOriginalDestination;
+        this.isClearOriginalDestination = !continueToOriginalDestination;
     }
 
     @Override
@@ -105,16 +110,42 @@ public class IsisSignInPanel extends SignInPanelAbstract {
 
     @Override
     protected void onSignInSucceeded() {
-        signInHook();
+        if(isClearOriginalDestination) {
+            clearOriginalDestination();
+        }
         super.onSignInSucceeded();
     }
 
     @Override
     protected void onSignInRemembered() {
-        signInHook();
+        if(isClearOriginalDestination) {
+            clearOriginalDestination();
+        }
         super.onSignInRemembered();
     }
 
+    @Override
+    protected void storeUserTimeZoneToSession(final @NonNull ZoneId zoneId) {
+        userCurrentSessionTimeZoneHolder.setUserTimeZone(zoneId);
+        // fail early if not wired up correctly: there must be a session available for storage
+        _Assert.assertEquals(zoneId, userCurrentSessionTimeZoneHolder.getUserTimeZone().orElse(null),
+                ()->"no session available to store time-zone data");
+
+        // amend authentication/context with time-zone,
+        // also propagate user's Locale from current InteractionContext to Wicket's session
+        val session = AuthenticatedWebSession.get();
+        ((HasAmendableInteractionContext)session).amendInteractionContext(interactionContext->{
+            Optional.ofNullable(interactionContext.getUser().getLanguageLocale())
+                .ifPresent(session::setLocale);
+            return interactionContext.withTimeZone(zoneId);
+        });
+    }
+
+    @Override
+    protected void clearUserTimeZoneFromSession() {
+        userCurrentSessionTimeZoneHolder.clearUserTimeZone();
+    }
+
     // -- HELPER
 
     private BookmarkablePageLink<Void> addPasswordResetLink() {
@@ -163,12 +194,4 @@ public class IsisSignInPanel extends SignInPanelAbstract {
         }
     }
 
-    private void signInHook() {
-        if(clearOriginalDestination) {
-            clearOriginalDestination();
-        }
-        Optional.ofNullable(getTimezone())
-            .ifPresent(userCurrentSessionTimeZoneHolder::setUserTimeZone);
-    }
-
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/SignInPanelAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
index d26ecdc33d..547cd7eb0b 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
@@ -20,6 +20,7 @@ package org.apache.isis.viewer.wicket.ui.pages.login;
 
 import java.time.ZoneId;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 import org.apache.wicket.RestartResponseException;
@@ -46,6 +47,7 @@ import org.apache.isis.core.runtime.context.IsisAppCommonContext.HasCommonContex
 import org.apache.isis.viewer.wicket.model.util.WktContext;
 
 import lombok.Getter;
+import lombok.NonNull;
 import lombok.Setter;
 import lombok.val;
 
@@ -54,7 +56,7 @@ import lombok.val;
  * with an additional 'timezone' form field.
  * @see org.apache.wicket.authroles.authentication.panel.SignInPanel
  */
-public class SignInPanelAbstract
+public abstract class SignInPanelAbstract
 extends Panel
 implements HasCommonContext {
 
@@ -63,7 +65,6 @@ implements HasCommonContext {
     private static final String SIGN_IN_FORM = "signInForm";
     private static final String TIME_ZONE_SELECT = "timezone-select";
 
-
     /** True if the panel should display a remember-me checkbox */
     private boolean includeRememberMe = true;
 
@@ -181,6 +182,7 @@ implements HasCommonContext {
      * Called when sign in failed
      */
     protected void onSignInFailed() {
+        clearUserTimeZoneFromSession();
         // Try the component based localizer first. If not found try the
         // application localizer. Else use the default
         error(getLocalizer().getString("signInFailed", this, "Sign in failed"));
@@ -190,6 +192,8 @@ implements HasCommonContext {
      * Called when sign in was successful
      */
     protected void onSignInSucceeded() {
+        Optional.ofNullable(getTimezone())
+            .ifPresent(zoneId->storeUserTimeZoneToSession(zoneId));
         // If login has been called because the user was not yet logged in, than continue to the
         // original destination, otherwise to the Home page
         continueToOriginalDestination();
@@ -208,6 +212,9 @@ implements HasCommonContext {
      * @see #onConfigure()
      */
     protected void onSignInRemembered() {
+        Optional.ofNullable(getTimezone())
+            .ifPresent(zoneId->storeUserTimeZoneToSession(zoneId));
+
         // logon successful. Continue to the original destination
         continueToOriginalDestination();
 
@@ -324,4 +331,14 @@ implements HasCommonContext {
 
     }
 
+    /**
+     * Stores user's {@link ZoneId} to their session.
+     */
+    protected abstract void storeUserTimeZoneToSession(@NonNull ZoneId zoneId);
+
+    /**
+     * Clears user's {@link ZoneId} from their session.
+     */
+    protected abstract void clearUserTimeZoneFromSession();
+
 }
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis.java
index 7afc1c69fb..3380fe445a 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis.java
@@ -19,6 +19,7 @@
 package org.apache.isis.viewer.wicket.viewer.integration;
 
 import java.util.UUID;
+import java.util.function.UnaryOperator;
 
 import org.apache.wicket.Session;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
@@ -39,6 +40,7 @@ import org.apache.isis.core.runtime.context.IsisAppCommonContext;
 import org.apache.isis.core.runtime.context.IsisAppCommonContext.HasCommonContext;
 import org.apache.isis.core.security.authentication.AuthenticationRequestPassword;
 import org.apache.isis.core.security.authentication.manager.AuthenticationManager;
+import org.apache.isis.viewer.wicket.model.isis.HasAmendableInteractionContext;
 import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModelProvider;
@@ -55,7 +57,11 @@ import lombok.val;
  */
 public class AuthenticatedWebSessionForIsis
 extends AuthenticatedWebSession
-implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, HasCommonContext {
+implements
+    BreadcrumbModelProvider,
+    BookmarkedPagesModelProvider,
+    HasCommonContext,
+    HasAmendableInteractionContext {
 
     private static final long serialVersionUID = 1L;
 
@@ -142,12 +148,12 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, HasCommonConte
                 ? SessionSubscriber.CausedBy.USER
                 : SessionSubscriber.CausedBy.SESSION_EXPIRATION;
 
-
         log(SessionSubscriber.Type.LOGOUT, userName, causedBy);
     }
 
     /**
-     * If there is an {@link InteractionContext} already (as some authentication mechanisms setup in filters, eg
+     * If there is an {@link InteractionContext} already
+     * (as some authentication mechanisms setup in filters, eg
      * SpringSecurityFilter), then just use it.
      *
      * <p>
@@ -155,10 +161,14 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, HasCommonConte
      * </p>
      */
     public void syncExternalAuthenticationIfAvailable() {
+        getInteractionService()
+            .currentInteractionContext()
+            .ifPresent(interactionContext -> this.authentication = interactionContext);
+    }
 
-        val interactionService = commonContext.lookupServiceElseFail(InteractionService.class);
-        val interactionContextIfAny = interactionService.currentInteractionContext();
-        interactionContextIfAny.ifPresent(interactionContext -> this.authentication = interactionContext);
+    @Override
+    public void amendInteractionContext(final UnaryOperator<InteractionContext> updater) {
+        authentication = updater.apply(authentication);
     }
 
     /**
@@ -231,7 +241,7 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, HasCommonConte
             final SessionSubscriber.CausedBy causedBy) {
 
 
-        val interactionFactory = getInteractionService();
+        val interactionService = getInteractionService();
         val sessionLoggingServices = getSessionLoggingServices();
 
         final Runnable loggingTask = ()->{
@@ -244,8 +254,8 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, HasCommonConte
             );
         };
 
-        if(interactionFactory!=null) {
-            interactionFactory.runAnonymous(loggingTask::run);
+        if(interactionService!=null) {
+            interactionService.runAnonymous(loggingTask::run);
         } else {
             loggingTask.run();
         }
diff --git a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_Authenticate.java b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_Authenticate.java
index fd2152fe80..0eb5a16e1f 100644
--- a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_Authenticate.java
+++ b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_Authenticate.java
@@ -76,6 +76,7 @@ public class AuthenticatedWebSessionForIsis_Authenticate {
                 Collections.singletonList(mockAuthenticator),
                 new InteractionService_forTesting(),
                 new RandomCodeGeneratorDefault(),
+                Optional.empty(),
                 Collections.emptyList());
 
         context.checking(new Expectations() {
diff --git a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_SignIn.java b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_SignIn.java
index 4130b03b0a..7e6975b6bd 100644
--- a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_SignIn.java
+++ b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/integration/AuthenticatedWebSessionForIsis_SignIn.java
@@ -19,6 +19,7 @@
 package org.apache.isis.viewer.wicket.viewer.integration;
 
 import java.util.Locale;
+import java.util.Optional;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
@@ -70,6 +71,7 @@ public class AuthenticatedWebSessionForIsis_SignIn {
                 singletonList(mockAuthenticator),
                 new InteractionService_forTesting(),
                 new RandomCodeGeneratorDefault(),
+                Optional.empty(),
                 emptyList());
     }