|
26 | 26 | import java.util.Collections;
|
27 | 27 | import java.util.HashMap;
|
28 | 28 | import java.util.HashSet;
|
| 29 | +import java.util.LinkedHashSet; |
29 | 30 | import java.util.LinkedList;
|
30 | 31 | import java.util.List;
|
| 32 | +import java.util.ListIterator; |
31 | 33 | import java.util.Locale;
|
32 | 34 | import java.util.Map;
|
33 | 35 | import java.util.Random;
|
| 36 | +import java.util.Set; |
34 | 37 | import java.util.concurrent.atomic.AtomicInteger;
|
35 | 38 | import java.util.function.Function;
|
36 | 39 | import java.util.stream.Collectors;
|
|
80 | 83 | import biz.netcentric.cq.tools.actool.ims.request.AddGroupMembers;
|
81 | 84 | import biz.netcentric.cq.tools.actool.ims.request.AddGroupMembership;
|
82 | 85 | import biz.netcentric.cq.tools.actool.ims.request.CreateGroupStep;
|
| 86 | +import biz.netcentric.cq.tools.actool.ims.request.RemoveGroupMembership; |
| 87 | +import biz.netcentric.cq.tools.actool.ims.request.Step; |
83 | 88 | import biz.netcentric.cq.tools.actool.ims.request.UserActionCommand;
|
84 | 89 | import biz.netcentric.cq.tools.actool.ims.request.UserGroupActionCommand;
|
85 | 90 | import biz.netcentric.cq.tools.actool.ims.response.AccessToken;
|
@@ -231,6 +236,115 @@ private boolean requireGroupUpdate(Map<String, IMSGroup> existingImsGroups, Auth
|
231 | 236 | }
|
232 | 237 | }
|
233 | 238 |
|
| 239 | + private static final class UserMembershipChanges { |
| 240 | + private final String userName; |
| 241 | + private final Set<String> groupsToRemove; |
| 242 | + private final Set<String> groupsToAdd; |
| 243 | + |
| 244 | + public UserMembershipChanges(String userName) { |
| 245 | + this.userName = userName; |
| 246 | + this.groupsToRemove = new LinkedHashSet<>(); |
| 247 | + this.groupsToAdd = new LinkedHashSet<>(); |
| 248 | + } |
| 249 | + |
| 250 | + public boolean removeGroup(String group) { |
| 251 | + return groupsToRemove.add(group); |
| 252 | + } |
| 253 | + |
| 254 | + public boolean addGroup(String group) { |
| 255 | + return groupsToAdd.add(group); |
| 256 | + } |
| 257 | + |
| 258 | + public boolean addGroups(Collection<String> groups) { |
| 259 | + return groupsToAdd.addAll(groups); |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + /** |
| 264 | + * |
| 265 | + * @param token the api token |
| 266 | + * @param groups the groups whose administrators should be updated |
| 267 | + * @param isAddOnly whether only new group administrators should be added or also existing ones bound to managed groups but no longer configured as admins should be removed |
| 268 | + * @return the list of action commands to be executed to perform the changes |
| 269 | + * @throws IOException |
| 270 | + */ |
| 271 | + List<ActionCommand> updateGroupAdminsCommands(String token, Collection<String> groups, boolean isAddOnly) throws IOException { |
| 272 | + List<ActionCommand> actionCommands = new LinkedList<>(); |
| 273 | + // optionally make users group administrators |
| 274 | + if (config.groupAdmins() != null && config.groupAdmins().length > 0) { |
| 275 | + Set<String> groupAdmins = new LinkedHashSet<>(Arrays.asList(config.groupAdmins())); |
| 276 | + Map<String, UserMembershipChanges> usersMembershipChanges = new HashMap<>(); |
| 277 | + // for all admin groups collect users to remove and to add |
| 278 | + Set<String> adminGroupNames = groups.stream().map(n -> "_admin_" + n).collect(Collectors.toSet()); |
| 279 | + if (!isAddOnly) { |
| 280 | + // check which membership changes are necessary per user |
| 281 | + for (String adminGroupName : adminGroupNames) { |
| 282 | + Map<String, IMSUser> usersInAdminGroup = getUsersInGroup(token, adminGroupName); |
| 283 | + // diff against configured admins and remove/add memberhip accordingly |
| 284 | + Set<String> usersToRemove = new LinkedHashSet<>(usersInAdminGroup.keySet()); |
| 285 | + usersToRemove.removeAll(groupAdmins); |
| 286 | + for (String userToRemove : usersToRemove) { |
| 287 | + UserMembershipChanges changes = usersMembershipChanges.computeIfAbsent(userToRemove, UserMembershipChanges::new); |
| 288 | + changes.removeGroup(adminGroupName); |
| 289 | + } |
| 290 | + |
| 291 | + Set<String> usersToAdd = new LinkedHashSet<>(groupAdmins); |
| 292 | + usersToAdd.removeAll(usersInAdminGroup.keySet()); |
| 293 | + for (String userToAdd : usersToAdd) { |
| 294 | + UserMembershipChanges changes = usersMembershipChanges.computeIfAbsent(userToAdd, UserMembershipChanges::new); |
| 295 | + changes.addGroup(adminGroupName); |
| 296 | + } |
| 297 | + } |
| 298 | + } else { |
| 299 | + // just add all adminGroupNames to all admin users |
| 300 | + for (String admin : config.groupAdmins()) { |
| 301 | + UserMembershipChanges userMembershipChanges = new UserMembershipChanges(admin); |
| 302 | + userMembershipChanges.addGroups(adminGroupNames); |
| 303 | + usersMembershipChanges.put(admin, userMembershipChanges); |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + // iterate over all usersMembershipChanges and create user action commands per affected user |
| 308 | + for (UserMembershipChanges changes : usersMembershipChanges.values()) { |
| 309 | + int actionCommandsIndex = actionCommands.size(); |
| 310 | + addMembershipSteps(changes.userName, actionCommands.listIterator(actionCommandsIndex), false, changes.groupsToRemove); |
| 311 | + addMembershipSteps(changes.userName, actionCommands.listIterator(actionCommandsIndex), true, changes.groupsToAdd); |
| 312 | + } |
| 313 | + |
| 314 | + } |
| 315 | + return actionCommands; |
| 316 | + } |
| 317 | + |
| 318 | + /** |
| 319 | + * Adds the necessary user memberhips steps to the next action command from the given iterator. If there is no next action command, a new one is created and added to the list. |
| 320 | + * It makes sure that per action command there are at most {@link #MAX_NUM_GROUPS_PER_ADD_STEP} groups added or removed. |
| 321 | + * @param userName used when new ActionCommand is created |
| 322 | + * @param actionCommandsIterator the ListIterator which is potentially extended with new action commands |
| 323 | + * @param isAdd {@code} true if the group membership should be added, {@code false} if it should be removed |
| 324 | + * @param groupNames the group names to add/remove for the user |
| 325 | + */ |
| 326 | + private void addMembershipSteps(String userName, ListIterator<ActionCommand> actionCommandsIterator, boolean isAdd, Collection<String> groupNames) { |
| 327 | + AtomicInteger groupCounter = new AtomicInteger(); |
| 328 | + Collection<List<String>> groupNamesBatches = groupNames.stream().collect(Collectors.groupingBy |
| 329 | + (it->groupCounter.getAndIncrement() / MAX_NUM_GROUPS_PER_ADD_STEP)).values(); |
| 330 | + for (List<String> groupNamesBatch : groupNamesBatches) { |
| 331 | + ActionCommand actionCommand; |
| 332 | + if (actionCommandsIterator.hasNext()) { |
| 333 | + actionCommand = actionCommandsIterator.next(); |
| 334 | + } else { |
| 335 | + actionCommand = new UserActionCommand(userName); |
| 336 | + actionCommandsIterator.add(actionCommand); |
| 337 | + } |
| 338 | + final Step step; |
| 339 | + if (isAdd) { |
| 340 | + step = new AddGroupMembership(groupNamesBatch); |
| 341 | + } else { |
| 342 | + step = new RemoveGroupMembership(groupNamesBatch); |
| 343 | + } |
| 344 | + actionCommand.addStep(step); |
| 345 | + } |
| 346 | + } |
| 347 | + |
234 | 348 | @Override
|
235 | 349 | public int updateGroups(Collection<AuthorizableConfigBean> groupConfigs) throws IOException {
|
236 | 350 | String token = getOAuthServer2ServerToken();
|
@@ -259,23 +373,9 @@ public int updateGroups(Collection<AuthorizableConfigBean> groupConfigs) throws
|
259 | 373 | updatedGroupNames.add(groupConfig.getAuthorizableId());
|
260 | 374 | actionCommands.add(actionCommand);
|
261 | 375 | }
|
262 |
| - // optionally make users group administrators |
263 |
| - if (config.groupAdmins() != null && config.groupAdmins().length > 0) { |
264 |
| - // at most 10 groups per add command |
265 |
| - AtomicInteger groupCounter = new AtomicInteger(); |
266 |
| - Collection<List<String>> adminGroupNameBatches = updatedGroupNames.stream() |
267 |
| - .map(id -> "_admin_" + id) // https://adobe-apiplatform.github.io/umapi-documentation/en/api/ActionsCmds.html#addRemoveAttr |
268 |
| - .collect(Collectors.groupingBy |
269 |
| - (it->groupCounter.getAndIncrement() / MAX_NUM_GROUPS_PER_ADD_STEP)).values(); |
270 |
| - for (List<String> adminGroupNames : adminGroupNameBatches) { |
271 |
| - for (String groupAdmin : config.groupAdmins()) { |
272 |
| - ActionCommand actionCommand = new UserActionCommand(groupAdmin); |
273 |
| - AddGroupMembership addGroupMembership = new AddGroupMembership(adminGroupNames); |
274 |
| - actionCommand.addStep(addGroupMembership); |
275 |
| - actionCommands.add(actionCommand); |
276 |
| - } |
277 |
| - } |
278 |
| - } |
| 376 | + |
| 377 | + actionCommands.addAll(updateGroupAdminsCommands(token, updatedGroupNames, false)); |
| 378 | + |
279 | 379 | // update in batches of 10 commands
|
280 | 380 | AtomicInteger counter = new AtomicInteger();
|
281 | 381 | final Collection<List<ActionCommand>> actionCommandsBatches = actionCommands.stream().collect(Collectors.groupingBy
|
@@ -370,7 +470,7 @@ public GroupResponse handleResponse(
|
370 | 470 | if (entity == null) {
|
371 | 471 | throw new ClientProtocolException("Response contains no content for request " + getRequestInfo(httpGet));
|
372 | 472 | }
|
373 |
| - //System.out.println(EntityUtils.toString(entity)); |
| 473 | + System.out.println(EntityUtils.toString(entity)); |
374 | 474 | GroupResponse groupResponse = objectMapper.readValue(entity.getContent(), GroupResponse.class);
|
375 | 475 | groupResponse.associatedRequest = httpGet;
|
376 | 476 | return groupResponse;
|
@@ -401,7 +501,7 @@ Map<String, IMSUser> getUsersInGroup(String token, String name) throws IOExcepti
|
401 | 501 | return users;
|
402 | 502 | }
|
403 | 503 |
|
404 |
| - private UsersInGroupResponse getUsersInGroup(String token, String name, int page) throws IOException { |
| 504 | + UsersInGroupResponse getUsersInGroup(String token, String name, int page) throws IOException { |
405 | 505 | ObjectMapper objectMapper = new ObjectMapper();
|
406 | 506 | HttpGet httpGet;
|
407 | 507 | try {
|
|
0 commit comments