From c3b435357ca21823111653e8a6c0181512ba752d Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 11 Jul 2024 17:11:04 +0200 Subject: [PATCH] Optionally assign IMS group administrators This closes #749 --- .github/workflows/maven.yml | 2 + .../tools/actool/ims/IMSUserManagement.java | 28 +++++++++++--- ...mbershipStep.java => AddGroupMembers.java} | 5 ++- .../ims/request/AddGroupMembership.java | 38 +++++++++++++++++++ .../actool/ims/request/UserActionCommand.java | 26 +++++++++++++ .../tools/actool/ims/IMSUserManagementIT.java | 18 ++++++++- 6 files changed, 110 insertions(+), 7 deletions(-) rename accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/{AddMembershipStep.java => AddGroupMembers.java} (82%) create mode 100644 accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddGroupMembership.java create mode 100644 accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/UserActionCommand.java diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index bb61a570..17b7d602 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -50,6 +50,7 @@ jobs: 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 }} + ACTOOL_IMS_IT_USERID: ${{ vars.ACTOOL_IMS_IT_USERID }} 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 @@ -68,4 +69,5 @@ jobs: 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 }} + ACTOOL_IMS_IT_USERID: ${{ vars.ACTOOL_IMS_IT_USERID }} 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 b892485c..01f02e1b 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 @@ -27,6 +27,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -70,8 +71,10 @@ 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.AddGroupMembers; +import biz.netcentric.cq.tools.actool.ims.request.AddGroupMembership; import biz.netcentric.cq.tools.actool.ims.request.CreateGroupStep; +import biz.netcentric.cq.tools.actool.ims.request.UserActionCommand; import biz.netcentric.cq.tools.actool.ims.request.UserGroupActionCommand; import biz.netcentric.cq.tools.actool.ims.response.AccessToken; import biz.netcentric.cq.tools.actool.ims.response.ActionCommandResponse; @@ -107,6 +110,8 @@ public class IMSUserManagement implements ExternalGroupManagement { 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 {}; + @AttributeDefinition(name = "Group Administrators", description = "The given users are automatically added to each synchronized IMS group as administrator. The given user ids must already exist!") + String[] groupAdmins() default {}; } public static final Logger LOG = LoggerFactory.getLogger(IMSUserManagement.class); @@ -199,18 +204,31 @@ public String getLabel() { public void updateGroups(Collection groupConfigs) throws IOException { List actionCommands = new LinkedList<>(); for (AuthorizableConfigBean groupConfig : groupConfigs) { - UserGroupActionCommand actionCommand = new UserGroupActionCommand(groupConfig.getAuthorizableId()); + ActionCommand actionCommand = new UserGroupActionCommand(groupConfig.getAuthorizableId()); 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); + AddGroupMembers addMembers = new AddGroupMembers(); + addMembers.productProfileIds = new HashSet<>(Arrays.asList(config.productProfiles())); + actionCommand.addStep(addMembers); } actionCommands.add(actionCommand); } + // optionally make users group administrators + if (config.groupAdmins() != null && config.groupAdmins().length > 0) { + Set adminGroupNames = groupConfigs.stream() + .map(AuthorizableConfigBean::getAuthorizableId) + .map(id -> "_admin_" + id) // // https://adobe-apiplatform.github.io/umapi-documentation/en/api/ActionsCmds.html#addRemoveAttr + .collect(Collectors.toSet()); + for (String groupAdmin : config.groupAdmins()) { + ActionCommand actionCommand = new UserActionCommand(groupAdmin); + AddGroupMembership addGroupMembership = new AddGroupMembership(adminGroupNames); + actionCommand.addStep(addGroupMembership); + actionCommands.add(actionCommand); + } + } // update in batches of 10 commands AtomicInteger counter = new AtomicInteger(); final Collection> actionCommandsBatches = actionCommands.stream().collect(Collectors.groupingBy 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/AddGroupMembers.java similarity index 82% rename from accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddMembershipStep.java rename to accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddGroupMembers.java index ddcdbac1..b964c8fd 100644 --- 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/AddGroupMembers.java @@ -20,9 +20,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; +/** Maintains members of groups, to be used with {@link UserGroupActionCommand}. + * For maintaining group administrators use {@link AddGroupMembership}. + */ @JsonTypeName("add") @JsonInclude(Include.NON_EMPTY) // neither empty strings nor null values are allowed for the fields -public class AddMembershipStep implements Step { +public class AddGroupMembers implements Step { @JsonProperty("user") public Set userIds; diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddGroupMembership.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddGroupMembership.java new file mode 100644 index 00000000..ed9d5ec0 --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/AddGroupMembership.java @@ -0,0 +1,38 @@ +package biz.netcentric.cq.tools.actool.ims.request; + +/*- + * #%L + * Access Control Tool Bundle + * %% + * Copyright (C) 2015 - 2024 Cognizant Netcentric + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import java.util.Collection; +import java.util.HashSet; +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; + +/** Maintains memberships of users in (admin) groups, to be used with {@link UserActionCommand}. + * @see AddGroupMembers + */ +@JsonTypeName("add") +@JsonInclude(Include.NON_EMPTY) // neither empty strings nor null values are allowed for the fields +public class AddGroupMembership implements Step { + + public AddGroupMembership(Collection group) { + this.group = new HashSet<>(group); + } + + @JsonProperty("group") + public Set group; +} diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/UserActionCommand.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/UserActionCommand.java new file mode 100644 index 00000000..0eb069fa --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ims/request/UserActionCommand.java @@ -0,0 +1,26 @@ +package biz.netcentric.cq.tools.actool.ims.request; + +/*- + * #%L + * Access Control Tool Bundle + * %% + * Copyright (C) 2015 - 2024 Cognizant Netcentric + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class UserActionCommand extends ActionCommand { + + public UserActionCommand(String user) { + this.user = user; + } + + @JsonProperty("user") + String user; +} 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 97eed2dc..8d67cbec 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 @@ -101,7 +101,7 @@ public HttpClientBuilder newBuilder() { @Test void testGroupWithInvalidProductProfileMembership() throws IOException { - properties.put("productProfiles", "Invalid name"); + properties.put("productProfiles", getMandatoryEnvironmentVariable("ACTOOL_IMS_IT_USERID")); Configuration config = Converters.standardConverter().convert(properties).to(Configuration.class); IMSUserManagement imsUserManagement = new IMSUserManagement(config, new HttpClientBuilderFactory() { @Override @@ -116,6 +116,22 @@ public HttpClientBuilder newBuilder() { assertTrue(t.getMessage().contains("error.plc.not_found"), "Exceptions message is supposed to contain 'error.plc.not_found' but was " + t.getMessage()); } + @Test + void testGroupWithAdmin() throws IOException { + properties.put("groupAdmins", "konrad.windszus@netcentric.biz"); + 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)); + } + private static String getMandatoryEnvironmentVariable(String name) { String value = System.getenv(name); if (value == null) {