diff --git a/pom.xml b/pom.xml index de6e588..729ac6f 100644 --- a/pom.xml +++ b/pom.xml @@ -41,13 +41,13 @@ com.inteligr8.alfresco aps-public-rest-api - 2.0.0 + 2.0.1 aps1 com.inteligr8.alfresco aps-public-rest-client - 2.0.0 + 2.0.1 jersey @@ -159,6 +159,9 @@ ${aps-model.authType} ${aps-model.basicAuth.mavenServerId} ${aps-model.appName} + ${aps-model.share.editors} + ${aps-model.share.readers} + ${aps-model.share.app.editors} diff --git a/src/it/share-models/pom.xml b/src/it/share-models/pom.xml new file mode 100644 index 0000000..f3da8ef --- /dev/null +++ b/src/it/share-models/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + + com.inteligr8.alfresco + aps-model-maven-plugin-aps-info + @pom.version@ + pom + + APS Share Models Plugin Tests + + + + + ${project.groupId} + aps-model-maven-plugin + @pom.version@ + + + share-models + validate + + share-models + + + + + + + + diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java new file mode 100644 index 0000000..e06d788 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java @@ -0,0 +1,203 @@ +package com.inteligr8.maven.aps.modeling.goal; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.codehaus.plexus.component.annotations.Component; + +import com.inteligr8.alfresco.activiti.api.ModelsApi; +import com.inteligr8.alfresco.activiti.model.Datum; +import com.inteligr8.alfresco.activiti.model.GroupLight; +import com.inteligr8.alfresco.activiti.model.PermissionLevel; +import com.inteligr8.alfresco.activiti.model.PermissionLight; +import com.inteligr8.alfresco.activiti.model.ResultList; +import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation; +import com.inteligr8.alfresco.activiti.model.ShareInfoRequest; +import com.inteligr8.alfresco.activiti.model.SharePermission; +import com.inteligr8.alfresco.activiti.model.Tenant; +import com.inteligr8.maven.aps.modeling.util.Index; + +@Mojo( name = "share-models", threadSafe = true ) +@Component( role = org.apache.maven.plugin.Mojo.class ) +public class ApsShareGoal extends ApsAddressibleGoal { + + @Parameter( property = "aps-model.share.readers" ) + protected String readers; + + @Parameter( property = "aps-model.share.editors" ) + protected String editors; + + @Parameter( property = "aps-model.share.app.readers" ) + protected String appReaders; + + @Parameter( property = "aps-model.share.app.editors" ) + protected String appEditors; + + @Parameter( property = "aps-model.share.process.readers" ) + protected String processReaders; + + @Parameter( property = "aps-model.share.process.editors" ) + protected String processEditors; + + @Parameter( property = "aps-model.share.form.readers" ) + protected String formReaders; + + @Parameter( property = "aps-model.share.form.editors" ) + protected String formEditors; + + @Parameter( property = "aps-model.share.doRevoke", defaultValue = "false" ) + protected boolean doRevoke = false; + + protected Set appReaderSet; + protected Set appEditorSet; + protected Set processReaderSet; + protected Set processEditorSet; + protected Set formReaderSet; + protected Set formEditorSet; + + private Index identityIndex = new Index<>(128, true); + + @Override + public void executeEnabled() throws MojoExecutionException, MojoFailureException { + this.getLog().info("editors: " + this.editors); + this.normalizeParameters(); + this.buildIdentityIndex(); + + if (!this.appReaderSet.isEmpty() || !this.appEditorSet.isEmpty()) + this.shareModels(ModelsApi.ModelType.App, this.appReaderSet, this.appEditorSet); + + if (!this.processReaderSet.isEmpty() || !this.processEditorSet.isEmpty()) + this.shareModels(ModelsApi.ModelType.Process, this.processReaderSet, this.processEditorSet); + + if (!this.formReaderSet.isEmpty() || !this.formEditorSet.isEmpty()) + this.shareModels(ModelsApi.ModelType.Form, this.formReaderSet, this.formEditorSet); + } + + private void shareModels(ModelsApi.ModelType modelType, Set readers, Set editors) { + ResultListDataRepresentation models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null); + for (Datum datum : models.getData()) { + Number modelId = (Number)datum.getAdditionalProperties().get("id"); + String modelName = (String)datum.getAdditionalProperties().get("name"); + + Set groupsAddressed = new HashSet<>(); + Set readersUnaddressed = new HashSet<>(readers); + Set editorsUnaddressed = new HashSet<>(editors); + ShareInfoRequest changeRequest = new ShareInfoRequest(); + + ResultList shares = this.getApsApi().getShareApi().getShareInfo(modelId.toString()); + for (SharePermission share : shares.getData()) { + if (share.getGroup() != null) { + groupsAddressed.add(share.getGroup().getName()); + + if (PermissionLevel.Write.equals(share.getPermission())) { + if (editors.contains(share.getGroup().getName())) { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an editor of model '" + modelName + "'"); + // no change + continue; + } else if (readers.contains(share.getGroup().getName())) { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' reverting from editor to reader of model '" + modelName + "'"); + changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Read)); + continue; + } + } else { + if (editors.contains(share.getGroup().getName())) { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' elevating from reader to editor of model '" + modelName + "'"); + changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Write)); + continue; + } else if (readers.contains(share.getGroup().getName())) { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an reader of model '" + modelName + "'"); + // no change + continue; + } + } + + if (this.doRevoke) { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'; revoking ..."); + changeRequest.getRemoved().add(new PermissionLight().withId(share.getId())); + } else { + this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'"); + // not touching extra unnamed permissions + } + } else if (share.getPerson() != null) { + this.getLog().debug("Person-based model sharing not supported at this time; ignoring"); + } + } + + readersUnaddressed.removeAll(groupsAddressed); + for (String reader : readersUnaddressed) { + Long groupId = this.identityIndex.getValue(reader); + if (groupId == null) { + this.getLog().warn("The named group '" + reader + "' does not exist in APS; ignoring ..."); + } else { + this.getLog().debug("The named group '" + reader + "' becoming a reader of model '" + modelName + "'"); + changeRequest.getAdded().add(new PermissionLight().withGroupId(groupId).withPermission(PermissionLevel.Read)); + } + } + + editorsUnaddressed.removeAll(groupsAddressed); + for (String editor : editorsUnaddressed) { + Long groupId = this.identityIndex.getValue(editor); + if (groupId == null) { + this.getLog().warn("The named group '" + editor + "' does not exist in APS; ignoring ..."); + } else { + this.getLog().debug("The named group '" + editor + "' becoming an editor of model '" + modelName + "'"); + changeRequest.getAdded().add(new PermissionLight().withGroupId(groupId).withPermission(PermissionLevel.Write)); + } + } + + if (!changeRequest.getAdded().isEmpty() || !changeRequest.getUpdated().isEmpty() || !changeRequest.getRemoved().isEmpty()) { + this.getLog().info("Sharing model: " + modelType + " => '" + modelName + "'"); + this.getApsApi().getShareApi().setShareInfo(modelId.toString(), changeRequest); + } + } + } + + protected void normalizeParameters() { + Set readerSet = this.normalizeParameter(this.readers); + Set editorSet = this.normalizeParameter(this.editors); + this.appReaderSet = this.normalizeParameter(this.appReaders, readerSet); + this.appEditorSet = this.normalizeParameter(this.appEditors, editorSet); + this.processReaderSet = this.normalizeParameter(this.processReaders, readerSet); + this.processEditorSet = this.normalizeParameter(this.processEditors, editorSet); + this.formReaderSet = this.normalizeParameter(this.formReaders, readerSet); + this.formEditorSet = this.normalizeParameter(this.formEditors, editorSet); + } + + private Set normalizeParameter(String parameter, Collection c) { + Set set = this.normalizeParameter(parameter); + set.addAll(c); + return set; + } + + private Set normalizeParameter(String parameter) { + Set params = new HashSet<>(); + if (parameter == null) + return params; + if (parameter.length() == 0) + return params; + + String[] splitParams = parameter.split(","); + params.addAll(Arrays.asList(splitParams)); + return params; + } + + protected void buildIdentityIndex() { + List tenants = this.getApsApi().getAdminApi().getTenants(); + for (Tenant tenant : tenants) { + List groups = this.getApsApi().getAdminApi().getGroups(tenant.getId(), true, true); + this.getLog().debug("Indexing groups: " + groups.size()); + for (GroupLight group : groups) + this.identityIndex.put(group.getName(), group.getId()); + if (this.getLog().isDebugEnabled()) + this.getLog().debug("Indexed groups: " + this.identityIndex.toString()); + } + } + +}