diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb57a38435..229b542ccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -385,18 +385,24 @@ jobs: pom-dir: tas-email - test-name: "WebDAV TAS tests" pom-dir: tas-webdav - - test-name: "Integration TAS tests" + - test-name: "Integration TAS tests (Java 17)" pom-dir: tas-integration + - test-name: "Integration TAS tests (Java 11)" + pom-dir: tas-integration + jre-version: 11 env: REQUIRES_LOCAL_IMAGES: true steps: - uses: actions/checkout@v3 - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0 + with: + java-version: ${{ matrix.jre-version || '17' }} - name: "Build" timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} run: | bash ./scripts/ci/init.sh + export BUILD_OPTIONS="-Ddocker.buildArg.JRE_VERSION=${{ matrix.jre-version }} ${BUILD_OPTIONS}" bash ./scripts/ci/build.sh - name: "Set up the environment" run: | diff --git a/amps/ags/rm-community/rm-community-repo/.env b/amps/ags/rm-community/rm-community-repo/.env index 443245722d..7cfb90ba9a 100644 --- a/amps/ags/rm-community/rm-community-repo/.env +++ b/amps/ags/rm-community/rm-community-repo/.env @@ -1,3 +1,3 @@ SOLR6_TAG=2.0.7-A5 POSTGRES_TAG=14.4 -ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8 +ACTIVEMQ_TAG=5.17.6-jre17-rockylinux8 diff --git a/amps/ags/rm-community/rm-community-repo/pom.xml b/amps/ags/rm-community/rm-community-repo/pom.xml index 688c91c843..0c9508020d 100644 --- a/amps/ags/rm-community/rm-community-repo/pom.xml +++ b/amps/ags/rm-community/rm-community-repo/pom.xml @@ -436,7 +436,7 @@ - alfresco/alfresco-activemq:${dependency.activemq.version}-jre11-rockylinux8 + alfresco/alfresco-activemq:${dependency.activemq.version}-jre17-rockylinux8 ${activemq.port1}:${activemq.port1} @@ -507,7 +507,7 @@ - alfresco/alfresco-activemq:${dependency.activemq.version}-jre11-rockylinux8 + alfresco/alfresco-activemq:${dependency.activemq.version}-jre17-rockylinux8 ${activemq.port1}:${activemq.port1} diff --git a/packaging/docker-alfresco/Dockerfile b/packaging/docker-alfresco/Dockerfile index c7740d42b4..9b8c0a0999 100644 --- a/packaging/docker-alfresco/Dockerfile +++ b/packaging/docker-alfresco/Dockerfile @@ -1,6 +1,8 @@ +ARG JRE_VERSION=17 + # Fetch image based on Tomcat 9.0, Java 17 and Rocky Linux 8 # More infos about this image: https://github.com/Alfresco/alfresco-docker-base-tomcat -FROM alfresco/alfresco-base-tomcat:tomcat9-jre17-rockylinux8-202303081618 +FROM alfresco/alfresco-base-tomcat:tomcat9-jre${JRE_VERSION}-rockylinux8-202303081618 # Set default docker_context. ARG resource_path=target diff --git a/packaging/tests/environment/.env b/packaging/tests/environment/.env index 443245722d..7cfb90ba9a 100644 --- a/packaging/tests/environment/.env +++ b/packaging/tests/environment/.env @@ -1,3 +1,3 @@ SOLR6_TAG=2.0.7-A5 POSTGRES_TAG=14.4 -ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8 +ACTIVEMQ_TAG=5.17.6-jre17-rockylinux8 diff --git a/pom.xml b/pom.xml index b8fd140a5e..01d15ef2ff 100644 --- a/pom.xml +++ b/pom.xml @@ -62,11 +62,11 @@ 2.15.0-rc1 3.5.5 1.0.0 - 8.44 + 8.47 1.70 4.9.0 3.24.2 - 20230227 + 20231013 2.9.0 2.11.0 2.8.9 @@ -88,7 +88,7 @@ 4.1.87.Final 4.1.82.Final 2.0.56.Final - 5.17.4 + 5.17.6 1.22 1.2.5 4.2.0 @@ -111,8 +111,8 @@ 2.5.0 1.1.4 - 3.4.0 - 1.6.0 + 3.4.2 + 1.6.2 7.4.0 2.2.0 diff --git a/remote-api/src/main/java/org/alfresco/rest/api/NetworkWebScriptGet.java b/remote-api/src/main/java/org/alfresco/rest/api/NetworkWebScriptGet.java index c1a17aa8ab..71c406b79a 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/NetworkWebScriptGet.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/NetworkWebScriptGet.java @@ -109,11 +109,11 @@ public class NetworkWebScriptGet extends ApiWebScript implements ResponseWriter } catch (ApiException | WebScriptException apiException) { - renderException(apiException, res, assistant); + renderException(apiException, res, req, assistant); } catch (RuntimeException runtimeException) { - renderException(runtimeException, res, assistant); + renderException(runtimeException, res, req, assistant); } } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/NetworksWebScriptGet.java b/remote-api/src/main/java/org/alfresco/rest/api/NetworksWebScriptGet.java index e5f4183060..0c82fb2f0e 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/NetworksWebScriptGet.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/NetworksWebScriptGet.java @@ -118,11 +118,11 @@ public class NetworksWebScriptGet extends ApiWebScript implements RecognizedPara } catch (ApiException | WebScriptException apiException) { - renderException(apiException, res, assistant); + renderException(apiException, res, req, assistant); } catch (RuntimeException runtimeException) { - renderException(runtimeException, res, assistant); + renderException(runtimeException, res, req, assistant); } } } \ No newline at end of file diff --git a/remote-api/src/main/java/org/alfresco/rest/api/PublicApiTenantWebScriptServletRuntime.java b/remote-api/src/main/java/org/alfresco/rest/api/PublicApiTenantWebScriptServletRuntime.java index 8df1321ea1..1334e44d70 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/PublicApiTenantWebScriptServletRuntime.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/PublicApiTenantWebScriptServletRuntime.java @@ -143,7 +143,7 @@ public class PublicApiTenantWebScriptServletRuntime extends TenantWebScriptServl else { try { - renderException((Exception)exception, response, apiAssistant); + renderException((Exception)exception, response, request, apiAssistant); } catch (IOException e) { logger.error("Internal error", e); throw new WebScriptException("Internal error", e); diff --git a/remote-api/src/main/java/org/alfresco/rest/api/search/SearchApiWebscript.java b/remote-api/src/main/java/org/alfresco/rest/api/search/SearchApiWebscript.java index c8fe871ddf..8ea59fa1e0 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/search/SearchApiWebscript.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/search/SearchApiWebscript.java @@ -108,7 +108,7 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP renderJsonResponse(webScriptResponse, toRender, assistant.getJsonHelper()); } catch (Exception exception) { - renderException(exception,webScriptResponse,assistant); + renderException(exception,webScriptResponse,webScriptRequest,assistant); } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/search/SearchSQLApiWebscript.java b/remote-api/src/main/java/org/alfresco/rest/api/search/SearchSQLApiWebscript.java index d4b1452031..a783286d3b 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/search/SearchSQLApiWebscript.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/search/SearchSQLApiWebscript.java @@ -102,11 +102,11 @@ public class SearchSQLApiWebscript extends AbstractWebScript implements Recogniz { if (exception instanceof QueryParserException) { - renderException(exception,res,assistant); + renderException(exception,res,webScriptRequest,assistant); } else { - renderException(new WebScriptException(400, exception.getMessage()), res, assistant); + renderException(new WebScriptException(400, exception.getMessage()), res, webScriptRequest, assistant); } } } diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/tools/ResponseWriter.java b/remote-api/src/main/java/org/alfresco/rest/framework/tools/ResponseWriter.java index baa4a0282b..cef2358de9 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/tools/ResponseWriter.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/tools/ResponseWriter.java @@ -26,37 +26,34 @@ package org.alfresco.rest.framework.tools; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.alfresco.rest.framework.Api; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.rest.framework.core.exceptions.DefaultExceptionResolver; import org.alfresco.rest.framework.core.exceptions.ErrorResponse; -import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException; -import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter; -import org.alfresco.rest.framework.jacksonextensions.ExecutionResult; import org.alfresco.rest.framework.jacksonextensions.JacksonHelper; -import org.alfresco.rest.framework.resource.SerializablePagedCollection; -import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.ContentInfo; import org.alfresco.rest.framework.resource.content.ContentInfoImpl; -import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; -import org.alfresco.rest.framework.resource.parameters.Params; import org.alfresco.rest.framework.webscripts.WithResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.simple.JSONObject; -import org.springframework.beans.BeanUtils; import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.extensions.webscripts.*; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Description; +import org.springframework.extensions.webscripts.Format; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.extensions.webscripts.WrappingWebScriptResponse; import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; /* * Writes to the response @@ -193,24 +190,52 @@ public interface ResponseWriter default void renderErrorResponse(final ErrorResponse errorResponse, final WebScriptResponse res, final JacksonHelper jsonHelper) throws IOException { + renderErrorResponse(errorResponse, res, null, jsonHelper); + } - String logId = ""; - - if (Status.STATUS_INTERNAL_SERVER_ERROR == errorResponse.getStatusCode() || resWriterLogger().isDebugEnabled()) - { - logId = org.alfresco.util.GUID.generate(); - resWriterLogger().error(logId + " : " + errorResponse.getStackTrace()); - } - + /** + * Renders a JSON error response + * + * @param errorResponse The error + * @param res web script response + * @param req web script request + * @throws IOException + */ + default void renderErrorResponse(final ErrorResponse errorResponse, final WebScriptResponse res, final WebScriptRequest req, + final JacksonHelper jsonHelper) throws IOException + { String stackMessage = I18NUtil.getMessage(DefaultExceptionResolver.STACK_MESSAGE_ID); + String logId = org.alfresco.util.GUID.generate(); final ErrorResponse errorToWrite = new ErrorResponse(errorResponse.getErrorKey(), errorResponse.getStatusCode(), - errorResponse.getBriefSummary(), stackMessage, logId, errorResponse.getAdditionalState(), DefaultExceptionResolver.ERROR_URL); + errorResponse.getBriefSummary(), stackMessage, logId, errorResponse.getAdditionalState(), + DefaultExceptionResolver.ERROR_URL); + + String reqUrl = (req != null) ? req.getURL() : "unknown"; + String userName = AuthenticationUtil.getFullyAuthenticatedUser() != null ? AuthenticationUtil.getFullyAuthenticatedUser() + : "unauthenticated user"; + + // If internal server error or class in debug then print the stack trace + if (Status.STATUS_INTERNAL_SERVER_ERROR == errorResponse.getStatusCode() || resWriterLogger().isDebugEnabled()) + { + resWriterLogger().error("Exception " + errorToWrite.getLogId() + ". Request " + reqUrl + " executed by " + userName + + " returned status code " + errorResponse.getStatusCode() + " with message: " + + errorResponse.getBriefSummary() + " - Stack Trace: " + errorResponse.getStackTrace()); + } + else + { + resWriterLogger().error("Exception " + errorToWrite.getLogId() + ". Request " + reqUrl + " executed by user " + + userName + " returned status code " + errorResponse.getStatusCode() + " with message: " + + errorResponse.getBriefSummary() + " - Increase logging on " + this.getClass().getName() + + " for stack trace."); + } setContentInfoOnResponse(res, DEFAULT_JSON_CONTENT); - // Status must be set before the response is written by Jackson (which will by default close and commit the response). - // In a r/w txn, web script buffered responses ensure that it doesn't really matter but for r/o txns this is important. + // Status must be set before the response is written by Jackson (which will by default close and commit the + // response). + // In a r/w txn, web script buffered responses ensure that it doesn't really matter but for r/o txns this is + // important. res.setStatus(errorToWrite.getStatusCode()); jsonHelper.withWriter(res.getOutputStream(), new JacksonHelper.Writer() @@ -218,7 +243,7 @@ public interface ResponseWriter @SuppressWarnings("unchecked") @Override public void writeContents(JsonGenerator generator, ObjectMapper objectMapper) - throws JsonGenerationException, JsonMappingException, IOException + throws JsonGenerationException, JsonMappingException, IOException { JSONObject obj = new JSONObject(); obj.put("error", errorToWrite); @@ -236,7 +261,21 @@ public interface ResponseWriter */ default void renderException(final Exception exception, final WebScriptResponse response, final ApiAssistant assistant) throws IOException { - renderErrorResponse(assistant.resolveException(exception), response, assistant.getJsonHelper()); + renderException(exception, response, null, assistant); + } + + /** + * Renders an exception to the output stream as Json. + * + * @param exception + * @param response + * @param request + * @throws IOException + */ + default void renderException(final Exception exception, final WebScriptResponse response, final WebScriptRequest request, + final ApiAssistant assistant) throws IOException + { + renderErrorResponse(assistant.resolveException(exception), response, request, assistant.getJsonHelper()); } /** diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java index bb2a547ab9..32c0180c9c 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java @@ -180,15 +180,15 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements } catch (ContentIOException cioe) { - handleContentIOException(res, cioe); + handleContentIOException(res, req, cioe); } catch (AlfrescoRuntimeException | ApiException | WebScriptException xception ) { - renderException(xception, res, assistant); + renderException(xception, res, req, assistant); } catch (RuntimeException runtimeException) { - renderException(runtimeException, res, assistant); + renderException(runtimeException, res, req, assistant); } finally { @@ -224,17 +224,17 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements return toReturn; } - private void handleContentIOException(final WebScriptResponse res, ContentIOException exception) throws IOException + private void handleContentIOException(final WebScriptResponse res, final WebScriptRequest req, ContentIOException exception) throws IOException { // If the Content-Length is not set back to -1 any client will expect to receive binary and will hang until it times out res.setHeader(HEADER_CONTENT_LENGTH, String.valueOf(-1)); if (exception instanceof ArchivedIOException) { - renderException(new ArchivedContentException(exception.getMsgId(), exception), res, assistant); + renderException(new ArchivedContentException(exception.getMsgId(), exception), res, req, assistant); } else { - renderException(exception, res, assistant); + renderException(exception, res, req, assistant); } } diff --git a/repository/src/main/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBean.java b/repository/src/main/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBean.java index 856c4c2942..bb2809669d 100644 --- a/repository/src/main/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBean.java +++ b/repository/src/main/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBean.java @@ -57,6 +57,7 @@ import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.util.ResourceRetriever; import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; +import com.nimbusds.oauth2.sdk.id.Issuer; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException; @@ -88,7 +89,9 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; @@ -96,7 +99,6 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.web.client.RestOperations; @@ -361,12 +363,18 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean + { + private final String requiredIssuer; + + public JwtIssuerValidator(String issuer) + { + this.requiredIssuer = requireNonNull(issuer, "issuer cannot be null"); + } + + @Override + public OAuth2TokenValidatorResult validate(Jwt token) + { + requireNonNull(token, "token cannot be null"); + final Object issuer = token.getClaim(JwtClaimNames.ISS); + if (issuer != null && requiredIssuer.equals(issuer.toString())) + { + return OAuth2TokenValidatorResult.success(); + } + + final OAuth2Error error = new OAuth2Error( + OAuth2ErrorCodes.INVALID_TOKEN, + String.format("The iss claim is not valid. Expected `%s` but got `%s`.", requiredIssuer, issuer), + "https://tools.ietf.org/html/rfc6750#section-3.1"); + return OAuth2TokenValidatorResult.failure(error); + } + + } + private static boolean isDefined(String value) { return value != null && !value.isBlank(); diff --git a/repository/src/main/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/repository/src/main/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index 388404d15f..76968fea26 100644 --- a/repository/src/main/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/repository/src/main/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -1001,8 +1001,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean private final Map groupsToCreate = new TreeMap(); private final Map> personParentAssocsToCreate = newPersonMap(); private final Map> personParentAssocsToDelete = newPersonMap(); + private final List personToRezone = new LinkedList<>(); private Map> groupParentAssocsToCreate = new TreeMap>(); private final Map> groupParentAssocsToDelete = new TreeMap>(); + private final List groupToRezone = new LinkedList<>(); private final Map> finalGroupChildAssocs = new TreeMap>(); private List personsProcessed = new LinkedList(); private Set allZonePersons = Collections.emptySet(); @@ -1268,7 +1270,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean parents.add(parent); } } - + + private void recordParentAssociationAuthoritiesToRezone(String child) + { + if (child != null) + { + List toRezone = AuthorityType.getAuthorityType(child) == AuthorityType.USER + ? this.personToRezone + : this.groupToRezone; + toRezone.add(child); + } + } + private void validateGroupParentAssocsToCreate() { Iterator>> i = this.groupParentAssocsToCreate.entrySet().iterator(); @@ -1432,36 +1445,55 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean String child = entry.getKey(); if (!toRetain.contains(child)) { - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + if (!shouldRezone(child)) { - if (groupList == null) + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { - groupList = new StringBuilder(1024); - } - else - { - groupList.setLength(0); - } - for (String parent : entry.getValue()) - { - if (groupList.length() > 0) + if (groupList == null) { - groupList.append(", "); + groupList = new StringBuilder(1024); } - groupList.append('\'').append( - ChainingUserRegistrySynchronizer.this.authorityService.getShortName(parent)) - .append('\''); + else + { + groupList.setLength(0); + } + for (String parent : entry.getValue()) + { + if (groupList.length() > 0) + { + groupList.append(", "); + } + groupList.append('\'').append( + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(parent)) + .append('\''); + } + ChainingUserRegistrySynchronizer.logger.debug("Ignoring non-existent member '" + + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) + + "' in groups {" + groupList.toString() + "}"); } - ChainingUserRegistrySynchronizer.logger.debug("Ignoring non-existent member '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) - + "' in groups {" + groupList.toString() + "}"); + i.remove(); + } + else { + recordParentAssociationAuthoritiesToRezone(child); } - i.remove(); } } } + private boolean shouldRezone(String authorityName) + { + boolean exists = authorityService.authorityExists(authorityName); + + if (exists) + { + Set zones = ChainingUserRegistrySynchronizer.this.authorityService.getAuthorityZones(authorityName); + return isInZone(authorityName, zones, AuthorityService.ZONE_AUTH_ALFRESCO) && !isInZone(authorityName, zones, zoneId); + } + + return false; + } + private void processGroups(UserRegistry userRegistry, boolean isFullSync, boolean splitTxns) { // MNT-12454 fix. If syncDelete is false, there is no need to pull all users and all groups from LDAP during the full synchronization. @@ -1634,6 +1666,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean public void process(Map.Entry> entry) throws Throwable { maintainAssociationCreations(entry.getKey()); + maintainAssociationCreationsToRezone(entry.getKey()); } }, splitTxns); } @@ -1667,6 +1700,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean { maintainAssociationDeletions(entry.getKey()); maintainAssociationCreations(entry.getKey()); + maintainAssociationCreationsToRezone(entry.getKey()); } }, splitTxns); } @@ -1742,6 +1776,25 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean } } } + + private void maintainAssociationCreationsToRezone(String authorityName) + { + boolean isPerson = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER; + + List authorities = isPerson ? this.personToRezone : this.groupToRezone; + Map> parentAssocsToCreate = isPerson ? this.personParentAssocsToCreate : this.groupParentAssocsToCreate; + + if (authorities != null && !authorities.isEmpty() && parentAssocsToCreate.containsKey(authorityName)) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger.debug( + "Changing '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(authorityName) + + "' to zone '" + zoneId + "'"); + } + updateAuthorityZones(authorityName, ChainingUserRegistrySynchronizer.this.authorityService.getAuthorityZones(authorityName), zoneSet); + } + } } // end of Analyzer class // Run the first process the Group Analyzer @@ -1906,6 +1959,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean // create cycles) groupAnalyzer.maintainAssociationDeletions(personName); groupAnalyzer.maintainAssociationCreations(personName); + groupAnalyzer.maintainAssociationCreationsToRezone(personName); synchronized (this) { @@ -2118,10 +2172,32 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean zonesToAdd.removeAll(oldZones); if (!zonesToAdd.isEmpty()) { + // Prevents the authority from being added to zones where already is + Set currentZones = this.authorityService.getAuthorityZones(authorityName); + if (currentZones != null && !currentZones.isEmpty()) + { + zonesToAdd.removeAll(currentZones); + } this.authorityService.addAuthorityToZones(authorityName, zonesToAdd); } } - + + /** + * Checks if the supplied authority is part of a certain zone + * + * @param authorityName + * the name of authority to check + * @param authorityZones + * the zones where authority is + * @param zoneToCheck + * the zone to check + * @return true in case the authority is in supplied zone + */ + private boolean isInZone(String authorityName, Set authorityZones, String zoneToCheck) + { + return authorityName != null && authorityZones != null && zoneToCheck != null && authorityZones.contains(zoneToCheck); + } + @Override protected void onBootstrap(ApplicationEvent event) { diff --git a/repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBeanTest.java b/repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBeanTest.java index bf107e68bc..5b07c83db0 100644 --- a/repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBeanTest.java +++ b/repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/IdentityServiceFacadeFactoryBeanTest.java @@ -31,15 +31,20 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Map; +import java.util.UUID; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtDecoderProvider; +import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator; import org.junit.Test; import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; public class IdentityServiceFacadeFactoryBeanTest { + private static final String EXPECTED_ISSUER = "expected-issuer"; @Test public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided() { @@ -62,4 +67,53 @@ public class IdentityServiceFacadeFactoryBeanTest .containsEntry(USERNAME_CLAIM, "piotrek"); } + @Test + public void shouldFailWithNotMatchingIssuerURIs() + { + final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER); + + final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer("different-issuer")); + assertThat(validationResult).isNotNull(); + assertThat(validationResult.hasErrors()).isTrue(); + assertThat(validationResult.getErrors()).hasSize(1); + + final OAuth2Error error = validationResult.getErrors().iterator().next(); + assertThat(error).isNotNull(); + assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "different-issuer"); + } + + @Test + public void shouldFailWithNullIssuerURI() + { + final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER); + + final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(null)); + assertThat(validationResult).isNotNull(); + assertThat(validationResult.hasErrors()).isTrue(); + assertThat(validationResult.getErrors()).hasSize(1); + + final OAuth2Error error = validationResult.getErrors().iterator().next(); + assertThat(error).isNotNull(); + assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "null"); + } + + @Test + public void shouldSucceedWithMatchingIssuerURI() + { + final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER); + + final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(EXPECTED_ISSUER)); + assertThat(validationResult).isNotNull(); + assertThat(validationResult.hasErrors()).isFalse(); + assertThat(validationResult.getErrors()).isEmpty(); + } + + private Jwt tokenWithIssuer(String issuer) + { + return Jwt.withTokenValue(UUID.randomUUID().toString()) + .issuer(issuer) + .header("JUST", "FOR TESTING") + .build(); + } + } \ No newline at end of file diff --git a/scripts/ci/docker-compose/docker-compose-db.yaml b/scripts/ci/docker-compose/docker-compose-db.yaml index 1e74d3f65d..c6f0e328d9 100644 --- a/scripts/ci/docker-compose/docker-compose-db.yaml +++ b/scripts/ci/docker-compose/docker-compose-db.yaml @@ -34,7 +34,7 @@ services: ports: - "3307:3306" activemq: - image: alfresco/alfresco-activemq:5.17.1-jre11-rockylinux8 + image: alfresco/alfresco-activemq:5.17.6-jre17-rockylinux8 ports: - "5672:5672" # AMQP - "61616:61616" # OpenWire \ No newline at end of file diff --git a/scripts/ci/docker-compose/docker-compose.yaml b/scripts/ci/docker-compose/docker-compose.yaml index dc37e4649f..93f132a039 100644 --- a/scripts/ci/docker-compose/docker-compose.yaml +++ b/scripts/ci/docker-compose/docker-compose.yaml @@ -20,7 +20,7 @@ services: - "5433:5432" activemq: profiles: ["default", "with-transform-core-aio", "activemq", "with-mtls-transform-core-aio"] - image: alfresco/alfresco-activemq:5.17.1-jre11-rockylinux8 + image: alfresco/alfresco-activemq:5.17.6-jre17-rockylinux8 ports: - "5672:5672" # AMQP - "61616:61616" # OpenWire