diff --git a/repository/module.properties b/repository/module.properties
index 535f6c2..184584e 100644
--- a/repository/module.properties
+++ b/repository/module.properties
@@ -5,4 +5,4 @@ module.version=${noSnapshotVersion}
module.repo.version.min=5
-module.depends.acosix-utility-core=1.1.0-*
\ No newline at end of file
+module.depends.acosix-utility=1.1.0-*
\ No newline at end of file
diff --git a/repository/src/main/config/module-context.xml b/repository/src/main/config/module-context.xml
index 7155952..7327a0e 100644
--- a/repository/src/main/config/module-context.xml
+++ b/repository/src/main/config/module-context.xml
@@ -55,6 +55,115 @@
+
+
+
+
+
+
+ org.alfresco.repo.web.auth.AuthenticationListener
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ acosix-utility.web.auth.multipleAuthenticationListeners.enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ acosix-utility.web.auth.multipleAuthenticationListeners.enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ acosix-utility.web.auth.multipleAuthenticationListeners.enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ acosix-utility.web.auth.multipleAuthenticationListeners.enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml
index dbbdcf2..fd43f65 100644
--- a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml
+++ b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml
@@ -148,6 +148,12 @@
+
+
+
+
+
+
diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java
index 69b99f2..46d791a 100644
--- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java
+++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java
@@ -292,6 +292,8 @@ public class KeycloakAuthenticationComponent extends AbstractAuthenticationCompo
if (result != null || ticketToken.isActive())
{
+ // this may be triggered later via KeycloakAuthenticationListener anyway but since Alfresco is inconsistent about when
+ // AuthenticationListener's are called, do it manually
this.handleUserTokens(result != null ? result.getAccessToken() : ticketToken.getAccessToken(),
result != null ? result.getIdToken() : ticketToken.getIdToken(), false);
}
diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java
index 03113bf..786956f 100644
--- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java
+++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java
@@ -492,7 +492,6 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
final SessionUser sessionUser = this.createUserEnvironment(session, userId);
this.keycloakAuthenticationComponent.handleUserTokens(accessToken, keycloakSecurityContext.getIdToken(), true);
-
this.authenticationListener.userAuthenticated(new KeycloakCredentials(accessToken));
// store tokens in cache as well for ticket validation
@@ -667,6 +666,15 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
"Skipping processKeycloakAuthenticationAndActions as Bearer authorization header for {} has already been processed by remote user mapper",
AlfrescoCompatibilityUtil.maskUsername(accessToken.getPreferredUsername()));
this.keycloakAuthenticationComponent.handleUserTokens(accessToken, accessToken, session.isNew());
+
+ // sessionUser should be guaranteed here, but still check - we need it for the cache key
+ if (sessionUser != null)
+ {
+ final String bearerToken = authHeader.substring("bearer ".length());
+ this.keycloakTicketTokenCache.put(sessionUser.getTicket(),
+ new RefreshableAccessTokenHolder(accessToken, accessToken, bearerToken, null));
+ }
+
skip = true;
}
else
@@ -719,6 +727,7 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
LOGGER.trace(
"Skipping processKeycloakAuthenticationAndActions as access token in session from previous Bearer authorization for {} is still valid",
AlfrescoCompatibilityUtil.maskUsername(sessionUser.getUserName()));
+ // accessToken may have already been handled by getSessionUser(), but don't count on it
this.keycloakAuthenticationComponent.handleUserTokens(accessToken, accessToken, false);
skip = true;
}
diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationListener.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationListener.java
new file mode 100644
index 0000000..a0c24c3
--- /dev/null
+++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationListener.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019 - 2020 Acosix GmbH
+ *
+ * Licensed 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 de.acosix.alfresco.keycloak.repo.authentication;
+
+import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.repo.web.auth.AuthenticationListener;
+import org.alfresco.repo.web.auth.TicketCredentials;
+import org.alfresco.repo.web.auth.WebCredentials;
+import org.alfresco.service.cmr.security.AuthenticationService;
+import org.alfresco.util.PropertyCheck;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+import de.acosix.alfresco.keycloak.repo.util.AlfrescoCompatibilityUtil;
+import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder;
+
+/**
+ * This class provides a central listener for ticket-based authentications to ensure that any ticket-associated Keycloak access tokens are
+ * processed. This is made necessary by the fact that the Alfresco web script framework may discard any global authentication and
+ * re-authenticate the user by validating the ticket in the HTTP session, thus losing any effect of the access token processing in the
+ * global authentication. Additionally, in some cases the web script framework does not even check the authenticated session user and just
+ * sets a remotely authenticated user as the current user - in that case at least it informs of a pseudo-ticket-based authentication, which
+ * - due to Alfresco standard behaviour of one-ticket-per-user - reuses the same ticket the user already had been assigned.
+ *
+ * In short, Alfresco authentication is extremely inconsistent and this listener class helps to plug one more hole.
+ *
+ * @author Axel Faust
+ */
+public class KeycloakAuthenticationListener implements InitializingBean, AuthenticationListener
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAuthenticationListener.class);
+
+ protected AuthenticationService authenticationService;
+
+ protected KeycloakAuthenticationComponent keycloakAuthenticationComponent;
+
+ protected SimpleCache keycloakTicketTokenCache;
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void afterPropertiesSet()
+ {
+ PropertyCheck.mandatory(this, "authenticationService", this.authenticationService);
+ PropertyCheck.mandatory(this, "keycloakAuthenticationCompoennt", this.keycloakAuthenticationComponent);
+ PropertyCheck.mandatory(this, "keycloakTicketTokenCache", this.keycloakTicketTokenCache);
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void userAuthenticated(final WebCredentials credentials)
+ {
+ if (credentials instanceof TicketCredentials)
+ {
+ // for whatever reason, the credentials don't expose the ticket
+ final String ticket = this.authenticationService.getCurrentTicket();
+ if (this.keycloakTicketTokenCache.contains(ticket))
+ {
+ final RefreshableAccessTokenHolder token = this.keycloakTicketTokenCache.get(ticket);
+ LOGGER.debug("Processing access token for {} after ticket-based authentication",
+ AlfrescoCompatibilityUtil.maskUsername(token.getAccessToken().getPreferredUsername()));
+ // any ticket-based authentication is not a fresh login as it reuses obtained authentications
+ this.keycloakAuthenticationComponent.handleUserTokens(token.getAccessToken(), token.getIdToken(), false);
+ }
+ }
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void authenticationFailed(final WebCredentials credentials, final Exception ex)
+ {
+ // NO-OP
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void authenticationFailed(final WebCredentials credentials)
+ {
+ // NO-OP
+ }
+
+ /**
+ * @param authenticationService
+ * the authenticationService to set
+ */
+ public void setAuthenticationService(final AuthenticationService authenticationService)
+ {
+ this.authenticationService = authenticationService;
+ }
+
+ /**
+ * @param keycloakAuthenticationComponent
+ * the keycloakAuthenticationComponent to set
+ */
+ public void setKeycloakAuthenticationComponent(final KeycloakAuthenticationComponent keycloakAuthenticationComponent)
+ {
+ this.keycloakAuthenticationComponent = keycloakAuthenticationComponent;
+ }
+
+ /**
+ * @param keycloakTicketTokenCache
+ * the keycloakTicketTokenCache to set
+ */
+ public void setKeycloakTicketTokenCache(final SimpleCache keycloakTicketTokenCache)
+ {
+ this.keycloakTicketTokenCache = keycloakTicketTokenCache;
+ }
+
+}
diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakRemoteUserMapper.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakRemoteUserMapper.java
index 0e43390..8c68ebe 100644
--- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakRemoteUserMapper.java
+++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakRemoteUserMapper.java
@@ -139,6 +139,7 @@ public class KeycloakRemoteUserMapper implements RemoteUserMapper, ActivateableB
() -> this.personService.personExists(preferredUsername) ? this.personService.getUserIdentifier(preferredUsername)
: preferredUsername);
+ // normally Alfresco masks user names in logging, but in this case it would run counter to the purpose of logging
LOGGER.debug("Authenticated user {} via bearer token, normalised as {}", preferredUsername, normalisedUserName);
remoteUser = normalisedUserName;
diff --git a/repository/src/test/docker/repository-it.xml b/repository/src/test/docker/repository-it.xml
index 9bf5ec4..748fe4e 100644
--- a/repository/src/test/docker/repository-it.xml
+++ b/repository/src/test/docker/repository-it.xml
@@ -92,6 +92,7 @@
de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz1:*
de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:*
de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:*
+ de.acosix.alfresco.utility:de.acosix.alfresco.utility.repo:jar:installable:*
test
diff --git a/share/src/test/docker/repository-it.xml b/share/src/test/docker/repository-it.xml
index 62e2e4c..12d24bd 100644
--- a/share/src/test/docker/repository-it.xml
+++ b/share/src/test/docker/repository-it.xml
@@ -70,7 +70,6 @@
de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:*
${project.groupId}:de.acosix.alfresco.keycloak.repo.deps:*
de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:*
-
de.acosix.alfresco.utility:de.acosix.alfresco.utility.repo:jar:installable:*
${project.groupId}:de.acosix.alfresco.keycloak.repo:jar:installable:*