diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b5134da5..bb61a570 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -35,11 +35,6 @@ jobs: - name: Build with Maven if: '!matrix.isMainBuildEnv' - env: - # Necessary for IMS IT - ACTOOL_IMS_IT_ORGANIZATIONID: ${{ vars.ACTOOL_IMS_IT_ORGANIZATIONID }} - ACTOOL_IMS_IT_CLIENTID: ${{ vars.ACTOOL_IMS_IT_CLIENTID }} - ACTOOL_IMS_IT_CLIENTSECRET: ${{ secrets.ACTOOL_IMS_IT_CLIENTSECRET }} run: mvn -e -B -V -Pintegration-tests clean verify - name: Build and Analyse with Maven if: github.ref != 'refs/heads/develop' && matrix.isMainBuildEnv @@ -54,6 +49,7 @@ jobs: ACTOOL_IMS_IT_ORGANIZATIONID: ${{ vars.ACTOOL_IMS_IT_ORGANIZATIONID }} ACTOOL_IMS_IT_CLIENTID: ${{ vars.ACTOOL_IMS_IT_CLIENTID }} ACTOOL_IMS_IT_CLIENTSECRET: ${{ secrets.ACTOOL_IMS_IT_CLIENTSECRET }} + ACTOOL_IMS_IT_PRODUCTPROFILE: ${{ vars.ACTOOL_IMS_IT_PRODUCTPROFILE }} run: mvn -e -B -V clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=Netcentric_accesscontroltool -Dsonar.organization=netcentric -Dsonar.host.url=https://sonarcloud.io -DnvdApiKeyEnvironmentVariable=NVD_API_KEY -Pdependency-check,coverage-report,integration-tests - name: Build, Analyse and Deploy with Maven @@ -71,4 +67,5 @@ jobs: ACTOOL_IMS_IT_ORGANIZATIONID: ${{ vars.ACTOOL_IMS_IT_ORGANIZATIONID }} ACTOOL_IMS_IT_CLIENTID: ${{ vars.ACTOOL_IMS_IT_CLIENTID }} ACTOOL_IMS_IT_CLIENTSECRET: ${{ secrets.ACTOOL_IMS_IT_CLIENTSECRET }} + ACTOOL_IMS_IT_PRODUCTPROFILE: ${{ vars.ACTOOL_IMS_IT_PRODUCTPROFILE }} run: mvn -e -B -V clean deploy org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=Netcentric_accesscontroltool -Dsonar.organization=netcentric -Dsonar.host.url=https://sonarcloud.io -DnvdApiKeyEnvironmentVariable=NVD_API_KEY -Pdependency-check,coverage-report,integration-tests diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagement.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagement.java index 9571e2f2..f6ec9370 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagement.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagement.java @@ -8,7 +8,9 @@ import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Random; @@ -55,6 +57,7 @@ import biz.netcentric.cq.tools.actool.externalusermanagement.ExternalGroupManagement; import biz.netcentric.cq.tools.actool.ims.IMSUserManagement.Configuration; import biz.netcentric.cq.tools.actool.ims.request.ActionCommand; +import biz.netcentric.cq.tools.actool.ims.request.AddMembershipStep; import biz.netcentric.cq.tools.actool.ims.request.CreateGroupStep; import biz.netcentric.cq.tools.actool.ims.request.UserGroupActionCommand; import biz.netcentric.cq.tools.actool.ims.response.AccessToken; @@ -89,6 +92,8 @@ public class IMSUserManagement implements ExternalGroupManagement { int connectTimeout() default 2000; @AttributeDefinition(name = "Socket Timeout", description = "The time waiting for data – after establishing the connection; maximum time of inactivity between two data packets. Given in milliseconds.") int socketTimeout() default 10000; + @AttributeDefinition(name = "AEM Product Profiles", description = "The given product profile names are automatically added to each synchronized IMS group. The given product profile names must exist for an AEM product!") + String[] productProfiles() default {}; } public static final Logger LOG = LoggerFactory.getLogger(IMSUserManagement.class); @@ -184,6 +189,12 @@ public void updateGroups(Collection groupConfigs) throws CreateGroupStep createGroupStep = new CreateGroupStep(); createGroupStep.description = groupConfig.getDescription(); actionCommand.addStep(createGroupStep); + // optionally maintain product profile memberships in the group as well + if (config.productProfiles() != null && config.productProfiles().length > 0) { + AddMembershipStep addMembershipStep = new AddMembershipStep(); + addMembershipStep.productProfileIds = new HashSet<>(Arrays.asList(config.productProfiles())); + actionCommand.addStep(addMembershipStep); + } actionCommands.add(actionCommand); } // update in batches of 10 commands diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddMembershipStep.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddMembershipStep.java new file mode 100644 index 00000000..02dc579d --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddMembershipStep.java @@ -0,0 +1,19 @@ +package biz.netcentric.cq.tools.actool.ims.request; + +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; + +@JsonTypeName("add") +@JsonInclude(Include.NON_EMPTY) // neither empty strings nor null values are allowed for the fields +public class AddMembershipStep implements Step { + + @JsonProperty("user") + public Set userIds; + + @JsonProperty("productConfiguration") + public Set productProfileIds; +} diff --git a/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagementIT.java b/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagementIT.java index 0ae0ca56..3aa4a96f 100644 --- a/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagementIT.java +++ b/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/ims/IMSUserManagementIT.java @@ -1,6 +1,8 @@ package biz.netcentric.cq.tools.actool.ims; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Collections; @@ -9,6 +11,8 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.osgi.services.HttpClientBuilderFactory; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.osgi.util.converter.Converters; @@ -26,15 +30,22 @@ */ class IMSUserManagementIT { - @Test - void test() throws IOException { - Map properties = new HashMap<>(); + Map properties; + + @BeforeEach + void setUp() { + String orgId = System.getenv("ACTOOL_IMS_IT_ORGANIZATIONID"); + Assumptions.assumeTrue(orgId != null, "Skipping IMS ITs because environment variable 'ACTOOL_IMS_IT_ORGANIZATIONID' is not set!"); + properties = new HashMap<>(); properties.put("organizationId", getMandatoryEnvironmentVariable("ACTOOL_IMS_IT_ORGANIZATIONID")); properties.put("clientId", getMandatoryEnvironmentVariable("ACTOOL_IMS_IT_CLIENTID")); properties.put("clientSecret", getMandatoryEnvironmentVariable("ACTOOL_IMS_IT_CLIENTSECRET")); properties.put("isTestOnly", Boolean.TRUE); + } + + @Test + void testSimpleGroup() throws IOException { Configuration config = Converters.standardConverter().convert(properties).to(Configuration.class); - IMSUserManagement imsUserManagement = new IMSUserManagement(config, new HttpClientBuilderFactory() { @Override public HttpClientBuilder newBuilder() { @@ -59,6 +70,39 @@ public HttpClientBuilder newBuilder() { imsUserManagement.updateGroups(Collections.singleton(group3)); } + @Test + void testGroupWithProductProfileMembership() throws IOException { + properties.put("productProfiles", getMandatoryEnvironmentVariable("ACTOOL_IMS_IT_PRODUCTPROFILE")); + Configuration config = Converters.standardConverter().convert(properties).to(Configuration.class); + IMSUserManagement imsUserManagement = new IMSUserManagement(config, new HttpClientBuilderFactory() { + @Override + public HttpClientBuilder newBuilder() { + return HttpClientBuilder.create(); + } + }); + AuthorizableConfigBean group = new AuthorizableConfigBean(); + group.setAuthorizableId("testGroup"); + group.setDescription("my description"); + imsUserManagement.updateGroups(Collections.singleton(group)); + } + + @Test + void testGroupWithInvalidProductProfileMembership() throws IOException { + properties.put("productProfiles", "Invalid name"); + Configuration config = Converters.standardConverter().convert(properties).to(Configuration.class); + IMSUserManagement imsUserManagement = new IMSUserManagement(config, new HttpClientBuilderFactory() { + @Override + public HttpClientBuilder newBuilder() { + return HttpClientBuilder.create(); + } + }); + AuthorizableConfigBean group = new AuthorizableConfigBean(); + group.setAuthorizableId("testGroup"); + group.setDescription("my description"); + IOException t = assertThrows(IOException.class, () -> { imsUserManagement.updateGroups(Collections.singleton(group)); }); + assertTrue(t.getMessage().contains("error.plc.not_found"), "Exceptions message is supposed to contain 'error.plc.not_found' but was " + t.getMessage()); + } + private static String getMandatoryEnvironmentVariable(String name) { String value = System.getenv(name); if (value == null) {