You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2026/10/20 18:30:20 UTC
(causeway) branch main updated: CAUSEWAY-4054: [Wicket Viewer] Signin Remember Me Feature is now deprecated
This is an automated email from the ASF dual-hosted git repository.
andi-huber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new 6b50a784be5 CAUSEWAY-4054: [Wicket Viewer] Signin Remember Me Feature is now deprecated
6b50a784be5 is described below
commit 6b50a784be54b1a083f65099728a4c4fe5f57209
Author: andi-huber <ah...@apache.org>
AuthorDate: Tue Oct 20 20:29:28 2026 +0200
CAUSEWAY-4054: [Wicket Viewer] Signin Remember Me Feature is now
deprecated
Redefines the meaning of RememberMe, that is only remember the
'username' of the last login.Let the browser handle passwords.
---
.../core/config/CausewayConfiguration.java | 47 ++----
.../icontitle/ObjectIconAndTitlePanelFactory.java | 1 -
.../components/widgets/bootstrap/ModalDialog.java | 2 +-
.../wicket/ui/pages/login/CausewaySignInPanel.html | 5 +-
.../wicket/ui/pages/login/CausewaySignInPanel.java | 10 +-
.../wicket/ui/pages/login/SignInPanelAbstract.java | 171 ++++++++++-----------
.../wicketapp/CausewayWicketApplication.java | 71 +++------
.../wicket/viewer/wicketapp/_CryptFactory.java | 111 -------------
.../wicket/viewer/wicketapp/CryptFactoryTest.java | 161 -------------------
9 files changed, 121 insertions(+), 458 deletions(-)
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
index bcb4e86b507..53eab840e1c 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
@@ -90,6 +90,8 @@
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._StableValue;
import org.apache.causeway.commons.internal.context._Context;
+import org.apache.causeway.core.config.CausewayConfiguration.Core;
+import org.apache.causeway.core.config.CausewayConfiguration.Viewer;
import org.apache.causeway.core.config.metamodel.facets.ActionConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.AssociationLayoutConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.CollectionLayoutConfigOptions;
@@ -2648,10 +2650,9 @@ public record Collections(
int maxWindowSize) {
public Collections {
- if (defaultWindowSize > maxWindowSize) {
- throw new IllegalArgumentException(
+ if (defaultWindowSize > maxWindowSize)
+ throw new IllegalArgumentException(
"causeway.viewer.graphql.collections.default-window-size must not exceed max-window-size");
- }
}
}
@@ -2673,10 +2674,9 @@ public record Autocomplete(
int maxWindowSize) {
public Autocomplete {
- if (defaultWindowSize > maxWindowSize) {
- throw new IllegalArgumentException(
+ if (defaultWindowSize > maxWindowSize)
+ throw new IllegalArgumentException(
"causeway.viewer.graphql.autocomplete.default-window-size must not exceed max-window-size");
- }
}
}
@@ -3390,43 +3390,20 @@ public record RememberMe(
* Whether the sign-in page should have a 'remember me' link (the default), or if it should
* be suppressed.
*
- * <p>
- * If 'remember me' is available and checked, then the viewer will allow users to login
- * based on encrypted credentials stored in a cookie. An {@link #getEncryptionKey() encryption key}
- * can optionally be specified.
- *
+ * <p> If 'remember me' is available and checked, then the viewer will remember 'username' and 'timezone'
+ * for the login page using cookies. This does not include any secrets nor requires encryption.
*/
@DefaultValue("false")
boolean suppress,
/**
- * If the 'remember me' feature is available, specifies the key to hold the encrypted
- * credentials in the cookie.
+ * If the 'remember me' feature is available, specifies the cookie name prefix that is used for cookies to store timezone and username data.
*/
@DefaultValue("causewayWicketRememberMe")
String cookieKey,
- /**
- * If the 'remember me' feature is available, optionally specifies an encryption key
- * (a complex string acting as salt to the encryption algorithm) for computing the encrypted
- * credentials.
- *
- * <p>
- * If not set, then (in production mode) the Wicket viewer will compute a random key each time it
- * is started. This will mean that any credentials stored between sessions will become invalid.
- *
- *
- * <p>
- * Conversely, if set then (in production mode) then the same salt will be used each time the app
- * is started, meaning that cached credentials can continue to be used across restarts.
- *
- *
- * <p>
- * In prototype mode this setting is effectively ignored, because the same key will always be
- * provided (either as set, or a fixed literal otherwise).
- *
- */
- @DefaultValue
- Optional<String> encryptionKey) {
+
+ @DefaultValue("Remember 'username' and 'timezone' for the next login.")
+ String description) {
}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java
index 35be61c5a5f..9a18fd1c33d 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java
@@ -99,7 +99,6 @@ public Component createComponent(final String id, final IModel<?> model) {
/**
* refactoring hint: go through proper ComponentFactory channels instead
*/
- @Deprecated
public static Component entityIconAndTitlePanel(
final String componentId,
final ObjectAdapterModel objectAdapterModel) {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/bootstrap/ModalDialog.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/bootstrap/ModalDialog.java
index 0634472c776..4b984ef4d02 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/bootstrap/ModalDialog.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/bootstrap/ModalDialog.java
@@ -47,7 +47,7 @@ public ModalDialog(final String markupId) {
public ModalDialog(final String id, final IModel<T> model) {
super(id, model);
setFadeIn(false);
- setUseKeyboard(true);
+ setCloseOnEscapeKey(true);
setDisableEnforceFocus(true);
setOutputMarkupPlaceholderTag(true);
Wkt.containerAdd(this, getContentId()); // initial empty content
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.html b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.html
index 3fa80d1d8f1..683ad9014f2 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.html
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.html
@@ -33,10 +33,13 @@
<label wicket:for="timezone"><wicket:message key="timezoneLabel"/></label>
<select class="form-control" wicket:id="timezone" name="timezone" data-noreset="true"/>
</div>
- <div class="form-check mb-3" wicket:id="rememberMeContainer">
+ <div class="form-check mb-3" wicket:id="rememberMeContainer" style="padding-left: 0px;">
<label>
<input type="checkbox" wicket:id="rememberMe"/> <wicket:message key="rememberMeLabel"/>
</label>
+ <small class="d-block text-muted mt-1">
+ <label wicket:id="rememberMeDescription"></label>
+ </small>
</div>
<button type="submit" class="btn btn-primary"><wicket:message key="signInButtonLabel"/></button>
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.java
index 07448e6ab38..d4fed14f0c9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/CausewaySignInPanel.java
@@ -27,6 +27,7 @@
import org.apache.wicket.Page;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.jspecify.annotations.NonNull;
import org.apache.causeway.applib.services.iactn.InteractionService;
import org.apache.causeway.applib.services.inject.ServiceInjector;
@@ -40,7 +41,6 @@
import org.apache.causeway.viewer.wicket.model.models.PageType;
import org.apache.causeway.viewer.wicket.ui.pages.PageClassRegistry;
-import org.jspecify.annotations.NonNull;
import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
/**
@@ -114,14 +114,6 @@ protected void onSignInSucceeded() {
super.onSignInSucceeded();
}
- @Override
- protected void onSignInRemembered() {
- if(isClearOriginalDestination) {
- clearOriginalDestination();
- }
- super.onSignInRemembered();
- }
-
@Override
protected void storeUserTimeZoneToSession(final @NonNull ZoneId zoneId) {
userCurrentSessionTimeZoneHolder.setUserTimeZone(zoneId);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/SignInPanelAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
index a8ea93c58e2..aaf3bcbf2c1 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/login/SignInPanelAbstract.java
@@ -21,10 +21,11 @@
import java.time.ZoneId;
import java.util.Optional;
-import org.apache.wicket.RestartResponseException;
-import org.apache.wicket.authentication.IAuthenticationStrategy;
+import jakarta.servlet.http.Cookie;
+
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.StatelessForm;
@@ -36,14 +37,18 @@
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.cookies.CookieUtils;
+import org.jspecify.annotations.NonNull;
+
+import org.springframework.util.StringUtils;
+import org.apache.causeway.applib.services.i18n.TranslationContext;
import org.apache.causeway.applib.value.semantics.TimeZoneChoiceProvider;
import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.config.CausewayConfiguration.Viewer.Wicket.RememberMe;
import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
import org.apache.causeway.viewer.wicket.ui.util.Wkt;
import lombok.Getter;
-import org.jspecify.annotations.NonNull;
import lombok.Setter;
/**
@@ -63,7 +68,10 @@ public abstract class SignInPanelAbstract
/** True if the panel should display a remember-me checkbox */
private boolean includeRememberMe = true;
- /** True if the user should be remembered via form persistence (cookies) */
+ /**
+ * True if the username and timezone should be remembered via form persistence (cookies).
+ * This does not include any secrets.
+ */
@Getter @Setter
private boolean rememberMe = true;
@@ -115,32 +123,11 @@ protected SignInFormWithTimeZone getSignInFormWithTimeZone() {
protected void onConfigure() {
// logged in already?
if (isSignedIn() == false) {
- var authenticationStrategy = authenticationStrategy();
- // get username, password and zoneID from persistence store
- final String[] data = authenticationStrategy.load();
-
- if ((data != null) && (data.length > 1)) {
- // try to sign in the user
- if (signIn(data[0], data[1])) {
- username = data[0];
- password = data[1];
-
- var tzMememnto = recoverTimezone();
- if(_Strings.isNotEmpty(tzMememnto)) {
- try {
- timezone = ZoneId.of(tzMememnto);
- } catch (Exception e) {
- timezone = null;
- e.printStackTrace();
- }
- }
-
- onSignInRemembered();
- } else {
- // the loaded credentials are wrong. erase them.
- authenticationStrategy.remove();
- }
- }
+ // get username and zoneID from cookies (if available)
+ username = usernameCookie().recoverUsername()
+ .orElse(null);
+ timezone = timezoneCookie().recoverTimezone()
+ .orElse(null);
}
super.onConfigure();
@@ -181,63 +168,17 @@ protected void onSignInFailed() {
*/
protected void onSignInSucceeded() {
Optional.ofNullable(getTimezone())
- .ifPresent(zoneId->storeUserTimeZoneToSession(zoneId));
+ .ifPresent(this::storeUserTimeZoneToSession);
// 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();
setResponsePage(getApplication().getHomePage());
}
- /**
- * Called when sign-in was remembered.
- * <p>
- * By default tries to continue to the original destination or switches to the application's
- * home page.
- * <p>
- * Note: This method will be called during rendering of this panel, thus a
- * {@link RestartResponseException} has to be used to switch to a different page.
- *
- * @see #onConfigure()
- */
- protected void onSignInRemembered() {
- Optional.ofNullable(getTimezone())
- .ifPresent(zoneId->storeUserTimeZoneToSession(zoneId));
-
- // logon successful. Continue to the original destination
- continueToOriginalDestination();
-
- // Ups, no original destination. Go to the home page
- throw new RestartResponseException(getApplication().getHomePage());
- }
-
@Getter(lazy = true) private static final ResourceReference jsForTimezoneSelectDefault =
new JavaScriptResourceReference(SignInPanelAbstract.class,
"js/client-side-timezone-select.js");
- private IAuthenticationStrategy authenticationStrategy() {
- return getApplication().getSecuritySettings()
- .getAuthenticationStrategy();
- }
-
- // -- TIME ZONE COOKIES
-
- private void rememberTimezone(final String tzMemento) {
- new CookieUtils().save(timezoneCookieName(), tzMemento);
- }
-
- private String recoverTimezone() {
- var cookie = new CookieUtils().getCookie(timezoneCookieName());
- return cookie!=null
- ? cookie.getValue()
- : null;
- }
-
- private String timezoneCookieName() {
- var rememberMe = getConfiguration().viewer().wicket().rememberMe();
- var cookieName = rememberMe.cookieKey()+"_tz";
- return cookieName;
- }
-
/**
* Sign in form.
*/
@@ -268,11 +209,15 @@ public SignInFormWithTimeZone(final String id) {
.setMarkupId(TIME_ZONE_SELECT));
// container for remember me checkbox
- WebMarkupContainer rememberMeContainer = new WebMarkupContainer("rememberMeContainer");
+ var rememberMeContainer = new WebMarkupContainer("rememberMeContainer");
add(rememberMeContainer);
// Add rememberMe checkbox
rememberMeContainer.add(new CheckBox("rememberMe"));
+ rememberMeContainer.add(new Label("rememberMeDescription",
+ getTranslationService().translate(
+ TranslationContext.named("Login"),
+ getConfiguration().viewer().wicket().rememberMe().description())));
// Show remember me checkbox?
rememberMeContainer.setVisible(includeRememberMe);
@@ -283,26 +228,23 @@ public SignInFormWithTimeZone(final String id) {
*/
@Override
public final void onSubmit() {
- var authenticationStrategy = authenticationStrategy();
-
if (signIn(username, password)) {
- if (rememberMe) {
- authenticationStrategy.save(
- username,
- password);
-
- // remember time-zone selection
- rememberTimezone(timezone!=null
- ? timezone.getId()
- : "");
- } else {
- authenticationStrategy.remove();
+ if (isRememberMe()
+ && StringUtils.hasLength(username)) {
+ // remember latest username
+ usernameCookie()
+ .rememberUsername(username);
}
+ if (isRememberMe()
+ && timezone!=null) {
+ // remember time-zone selection
+ timezoneCookie()
+ .rememberTimezone(timezone);
+ }
onSignInSucceeded();
} else {
onSignInFailed();
- authenticationStrategy.remove();
}
}
@@ -323,4 +265,49 @@ private TimeZoneChoiceProvider getTimeZoneChoiceProvider() {
*/
protected abstract void clearUserTimeZoneFromSession();
+ // -- USER NAME COOKIES
+
+ private record UsernameCookie(String cookieName) {
+ UsernameCookie(final RememberMe conf) {
+ this(conf.cookieKey() + "_usr");
+ }
+ void rememberUsername(final String username) {
+ new CookieUtils().save(cookieName, username);
+ }
+ Optional<String> recoverUsername() {
+ return Optional.ofNullable(new CookieUtils().getCookie(cookieName))
+ .map(Cookie::getValue)
+ .map(_Strings::emptyToNull);
+ }
+ }
+
+ private UsernameCookie usernameCookie() {
+ return new UsernameCookie(getConfiguration().viewer().wicket().rememberMe());
+ }
+
+ // -- TIME ZONE COOKIES
+
+ private record TimezoneCookie(String cookieName) {
+ TimezoneCookie(final RememberMe conf) {
+ this(conf.cookieKey() + "_tz");
+ }
+ void rememberTimezone(final ZoneId zoneId) {
+ new CookieUtils().save(cookieName, zoneId.getId());
+ }
+ Optional<ZoneId> recoverTimezone() {
+ try {
+ return Optional.ofNullable(new CookieUtils().getCookie(cookieName))
+ .map(Cookie::getValue)
+ .map(_Strings::emptyToNull)
+ .map(ZoneId::of);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Optional.empty();
+ }
+ }
+ }
+
+ private TimezoneCookie timezoneCookie() {
+ return new TimezoneCookie(getConfiguration().viewer().wicket().rememberMe());
+ }
}
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
index dcff1904d92..323bda68155 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
@@ -21,9 +21,32 @@
import java.time.Duration;
import java.util.Collections;
import java.util.List;
-import java.util.UUID;
import java.util.function.Function;
+import jakarta.inject.Inject;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.ConverterLocator;
+import org.apache.wicket.IConverterLocator;
+import org.apache.wicket.IPageFactory;
+import org.apache.wicket.Page;
+import org.apache.wicket.RuntimeConfigurationType;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
+import org.apache.wicket.core.request.mapper.MountedMapper;
+import org.apache.wicket.markup.head.ResourceAggregator;
+import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.request.cycle.PageRequestHandlerTracker;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.settings.RequestCycleSettings;
+import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
import org.apache.causeway.applib.services.inject.ServiceInjector;
import org.apache.causeway.applib.services.metrics.MetricsService;
import org.apache.causeway.commons.internal.concurrent._ConcurrentContext;
@@ -50,30 +73,7 @@
import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStartHandler;
import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStopHandler;
import org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway;
-import org.apache.wicket.Application;
-import org.apache.wicket.ConverterLocator;
-import org.apache.wicket.IConverterLocator;
-import org.apache.wicket.IPageFactory;
-import org.apache.wicket.Page;
-import org.apache.wicket.RuntimeConfigurationType;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.authentication.IAuthenticationStrategy;
-import org.apache.wicket.authentication.strategy.DefaultAuthenticationStrategy;
-import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
-import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
-import org.apache.wicket.core.request.mapper.MountedMapper;
-import org.apache.wicket.markup.head.ResourceAggregator;
-import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.request.cycle.PageRequestHandlerTracker;
-import org.apache.wicket.request.resource.CssResourceReference;
-import org.apache.wicket.settings.RequestCycleSettings;
-import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
-import org.springframework.context.ApplicationContext;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.support.WebApplicationContextUtils;
-import jakarta.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -252,35 +252,12 @@ protected IPageFactory newPageFactory() {
protected void configureSecurity(final CausewayConfiguration configuration) {
// since Wicket 9, CSP is enabled by default [https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP]
getCspSettings().blocking().disabled();
- getSecuritySettings().setAuthenticationStrategy(newAuthenticationStrategy(configuration));
// TODO CAUSEWAY-987 Either make the API better (no direct access to the map) or use DB records
final int maxEntries = 1000;
setMetaData(AccountConfirmationMap.KEY, new AccountConfirmationMap(maxEntries, Duration.ofDays(1)));
}
- /**
- * protected visibility to allow ad-hoc overriding of some other authentication strategy.
- */
- protected IAuthenticationStrategy newAuthenticationStrategy(final CausewayConfiguration configuration) {
- var rememberMe = configuration.viewer().wicket().rememberMe();
- var cookieKey = rememberMe.cookieKey();
- var encryptionKey = rememberMe.encryptionKey().orElse(defaultEncryptionKey());
- return new DefaultAuthenticationStrategy(cookieKey, _CryptFactory.sunJceCrypt(encryptionKey));
- }
-
- /**
- * As called by {@link #newAuthenticationStrategy(CausewayConfiguration)}.
- * If an encryption key for the 'rememberMe' cookie hasn't been configured,
- * then use a different encryption key for the 'rememberMe'
- * cookie each time the app is restarted.
- */
- protected String defaultEncryptionKey() {
- return systemEnvironment.isPrototyping()
- ? _CryptFactory.FIXED_SALT_FOR_PROTOTYPING
- : UUID.randomUUID().toString();
- }
-
// //////////////////////////////////////
protected static final Function<ComponentFactory, Iterable<CssResourceReference>> getCssResourceReferences =
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/_CryptFactory.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/_CryptFactory.java
deleted file mode 100644
index 05cb2b76dae..00000000000
--- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/_CryptFactory.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.causeway.viewer.wicket.viewer.wicketapp;
-
-import java.security.SecureRandom;
-import java.security.spec.KeySpec;
-import java.util.Random;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.apache.wicket.core.random.DefaultSecureRandomSupplier;
-import org.apache.wicket.core.random.ISecureRandomSupplier;
-import org.apache.wicket.core.util.crypt.AESCrypt;
-import org.apache.wicket.util.crypt.ICrypt;
-import org.apache.wicket.util.crypt.NoCrypt;
-import org.apache.wicket.util.crypt.SunJceCrypt;
-
-import org.apache.causeway.commons.internal.os._OsUtil;
-
-import lombok.SneakyThrows;
-import lombok.experimental.UtilityClass;
-
-@UtilityClass
-class _CryptFactory {
-
- static final String FIXED_SALT_FOR_PROTOTYPING = "PrototypingEncryptionKey";
-
- ICrypt sunJceCrypt(final String encryptionKey) {
- final byte[] salt = getSalt(8, encryptionKey);
- var crypt = new SunJceCrypt(salt, 1000);
- crypt.setKey(encryptionKey);
- return crypt;
- }
-
- @SneakyThrows
- ICrypt aesCrypt(final String encryptionKey) {
-
- final byte[] salt = getSalt(8, encryptionKey);
-
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- KeySpec spec = new PBEKeySpec(encryptionKey.toCharArray(), salt, 65536, 256);
- SecretKey tmp = factory.generateSecret(spec);
- SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-
- ISecureRandomSupplier rGen = new DefaultSecureRandomSupplier();
-
- return new AESCrypt(secret, rGen);
- }
-
- ICrypt noCrypt(final String encryptionKey) {
- return new NoCrypt();
- }
-
- //XXX what about BCryptPasswordEncoder (Spring);
-
- // -- HELPER
-
- /**
- * @param size
- * must be 8 bytes - for anything else PBES1Core throws
- * InvalidAlgorithmParameterException: Salt must be 8 bytes long
- */
- private byte[] getSalt(final int size, final String encryptionKey) {
- final byte[] salt = FIXED_SALT_FOR_PROTOTYPING.equals(encryptionKey)
- ? machineFixedSalt(size)
- : secureSalt(size);
- return salt;
- }
-
- /**
- * Cloned from {@link SunJceCrypt#randomSalt()},
- * but using {@link SecureRandom} instead of {@link Random}.
- */
- private byte[] secureSalt(final int size) {
- var salt = new byte[size];
- new SecureRandom().nextBytes(salt);
- return salt;
- }
-
- private byte[] machineFixedSalt(final int size) {
- var machineFixedSeed = _OsUtil.machineId();
- if(machineFixedSeed.isEmpty()){
- return secureSalt(size);
- }
- var salt = new byte[size];
- /*sonar-ignore-on*/
- new Random(machineFixedSeed.getAsLong()).nextBytes(salt); // not required to be secure
- /*sonar-ignore-off*/
- return salt;
- }
-
-}
diff --git a/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java b/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java
deleted file mode 100644
index 3a292b85c9d..00000000000
--- a/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.causeway.viewer.wicket.viewer.wicketapp;
-
-import java.util.function.Function;
-import java.util.stream.Stream;
-
-import jakarta.servlet.http.Cookie;
-import jakarta.servlet.http.HttpServletRequest;
-
-import org.apache.wicket.ThreadContext;
-import org.apache.wicket.authentication.strategy.DefaultAuthenticationStrategy;
-import org.apache.wicket.mock.MockWebResponse;
-import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
-import org.apache.wicket.request.IExceptionMapper;
-import org.apache.wicket.request.IRequestMapper;
-import org.apache.wicket.request.Url;
-import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.cycle.RequestCycleContext;
-import org.apache.wicket.util.crypt.ICrypt;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.Mockito.mock;
-
-import org.apache.causeway.core.internaltestsupport.annotations.DisabledIfRunningWithSurefire;
-
-/**
- * Requires resource {@code jakarta/servlet/http/LocalStrings.properties} for Cookie testing.
- */
-class CryptFactoryTest {
-
- private ServletWebRequest servletWebRequest;
- private MockWebResponse mockWebResponse;
- private HttpServletRequest mockHttpServletRequest;
-
- @BeforeEach
- void setUp() throws Exception {
-
- // quite a bit of ceremony to allow Cookie testing ...
-
- mockHttpServletRequest = new org.springframework.mock.web.MockHttpServletRequest() {
- @Override
- public Cookie[] getCookies() {
- return mockWebResponse.getCookies().toArray(new Cookie[] {});
- }
- };
-
- servletWebRequest = new ServletWebRequest(mockHttpServletRequest, "", Url.parse("/"));
- mockWebResponse = new MockWebResponse();
-
- ThreadContext.setRequestCycle(new RequestCycle(new RequestCycleContext(
- servletWebRequest,
- mockWebResponse,
- mock(IRequestMapper.class),
- mock(IExceptionMapper.class))));
-
- }
-
- @AfterEach
- void cleanUp() throws Exception {
- ThreadContext.setRequestCycle(null);
- }
-
- @DisabledIfRunningWithSurefire
- @ParameterizedTest(name = "{index} {0}")
- @MethodSource("provideCryptoCandidates")
- void authenticationStrategyRoundtrip_whenProduction(
- final String displayName,
- final Function<String, ICrypt> cryptFactory) {
-
- var encryptionKey = "any other than FIXED_SALT_FOR_PROTOTYPING";
-
- var strategy =
- new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
- strategy.save("hello", "world", "appendix");
-
- var data = strategy.load();
-
- assertNotNull(data);
- assertEquals(2, data.length);
- assertEquals("hello", data[0]);
- assertEquals("world", data[1]);
-
- // simulated application restart
-
- var strategy2 =
- new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
- var data2 = strategy2.load();
-
- //XXX occasionally fails with surefire
- assertNull(data2);
- }
-
- @ParameterizedTest(name = "{index} {0}")
- @MethodSource("provideCryptoCandidates")
- void authenticationStrategyRoundtrip_whenPrototyping(
- final String displayName,
- final Function<String, ICrypt> cryptFactory) {
-
- var encryptionKey = _CryptFactory.FIXED_SALT_FOR_PROTOTYPING;
-
- var strategy1 =
- new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
- strategy1.save("hello", "world", "appendix");
-
- // simulated application restart
-
- var strategy2 =
- new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
- var data = strategy2.load();
-
- assertNotNull(data);
- assertEquals(2, data.length);
- assertEquals("hello", data[0]);
- assertEquals("world", data[1]);
- }
-
- // -- HELPER
-
- private static Stream<Arguments> provideCryptoCandidates() {
- return Stream.of(
- scenario(
- "sunJceCrypt",
- _CryptFactory::sunJceCrypt),
- scenario(
- "aesCrypt",
- _CryptFactory::aesCrypt)
- );
- }
-
- private static Arguments scenario(
- final String displayName,
- final Function<String, ICrypt> cryptFactory) {
- return Arguments.of(
- displayName, cryptFactory);
- }
-
-}