diff --git a/HISTORY b/HISTORY index 80c8160f..80121cdf 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,9 @@ +2020-06-30 3.2.0 + - Allow to limit access to AECU via group permissions. Attention: This requires configuration for non-admin users. + - Autocomplete added, all AECU methods are listed. + - Added parameter "newName" to doCopyResourceToRelativePath action + - Added doJoinProperty() methods to binding + 2020-04-21 3.1.1 - JMX: added executeWithHistory() diff --git a/Readme.md b/Readme.md index e667601b..fac2782c 100644 --- a/Readme.md +++ b/Readme.md @@ -38,12 +38,13 @@ Table of contents 3. [Group Specification](#test_group_spec) 4. [Tests](#test_list) 5. [Execute Tests](#test_execution) -7. [JMX Interface](#jmx) -8. [Health Checks](#healthchecks) -9. [API Documentation](#api) -10. [License](#license) -11. [Changelog](#changelog) -12. [Developers](#developers) +7. [Limit Access to AECU](#limitAccess) +8. [JMX Interface](#jmx) +9. [Health Checks](#healthchecks) +10. [API Documentation](#api) +11. [License](#license) +12. [Changelog](#changelog) +13. [Developers](#developers) @@ -350,7 +351,7 @@ aecu.contentUpgradeBuilder() ### Execute Options -#### Update Single-value Properies +#### Update Single-value Properties * doSetProperty(String name, Object value): sets the given property to the value. Any existing value is overwritten. * doDeleteProperty(String name): removes the property with the given name if existing. @@ -371,6 +372,9 @@ aecu.contentUpgradeBuilder() * doAddValuesToMultiValueProperty(String name, String[] values): adds the list of values to a property. The property is created if it does not yet exist. * doRemoveValuesOfMultiValueProperty(String name, String[] values): removes the list of values from a given property. * doReplaceValuesOfMultiValueProperty(String name, String[] oldValues, String[] newValues): removes the old values and adds the new values in a given property. +* doJoinProperty(String name): joins values of a property into a single value. Uses "," to join multiple values. Deletes properties with empty array values. +* doJoinProperty(String name, Object fallback): joins values of a property into a single value. Uses "," to join multiple values. Sets the fallback for properties having an empty array as a value. +* doJoinProperty(String name, Object fallback, String separator): joins values of a property into a single value. Uses the given separator to join multiple values. Sets the fallback for properties having an empty array as a value. ```java aecu.contentUpgradeBuilder() @@ -379,6 +383,9 @@ aecu.contentUpgradeBuilder() .doAddValuesToMultiValueProperty("name", (String[])["value1", "value2"]) .doRemoveValuesOfMultiValueProperty("name", (String[])["value1", "value2"]) .doReplaceValuesOfMultiValueProperty("name", (String[])["old1", "old2"], (String[])["new1", "new2"]) + .doJoinProperty("name") + .doJoinProperty("name", "fallbackValue") + .doJoinProperty("name", "fallbackValue", ",") .run() ``` @@ -423,6 +430,7 @@ The matching nodes can be copied/moved to a new location. You can use ".." if yo * doRename(String newName): renames the resource to the given name * doCopyResourceToRelativePath(String relativePath): copies the node to the given target path +* doCopyResourceToRelativePath(String relativePath, String newName): copies the node to the given target path under the new name * doMoveResourceToRelativePath(String relativePath): moves the node to the given target path * doMoveResourceToPathRegex(String matchPattern, String replacementExpr): moves a resource if its path matches the pattern to the target path obtained by applying the replacement expression. You can use group references such as $1 (hint: "$" needs to be escaped with "\" in Groovy). @@ -432,7 +440,7 @@ aecu.contentUpgradeBuilder() .filterByNodeName("jcr:content") .doRename("newNodeName") .doCopyResourceToRelativePath("subNode") - .doCopyResourceToRelativePath("../subNode") + .doCopyResourceToRelativePath("../subNode", "newName") .doMoveResourceToRelativePath("../subNode") .doMoveResourceToPathRegex("/content/we-retail/(\\w+)/(\\w+)/(\\w+)", "/content/somewhere/\$1/and/\$2") .run() @@ -847,6 +855,19 @@ aecu ``` + + +# Limit Access to AECU (since 3.2) +For production systems it is recommended to limit the access to specific user groups. +This can be done via OSGI configuration. Here you can specify groups for read and execute access. + +Please not that user "admin" always has full access. If no groups are specified then nobody except admin has access. + +PID for OSGI config: de.valtech.aecu.core.security.AccessValidationService + + + + # JMX Interface diff --git a/api/pom.xml b/api/pom.xml index dcc71f3f..767bf95e 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -4,7 +4,7 @@ de.valtech.aecu aecu - 3.1.1 + 3.2.0 aecu.api @@ -98,6 +98,10 @@ junit-addons junit-addons + + com.google.code.gson + gson + com.google.code.findbugs jsr305 diff --git a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/AecuBinding.java b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/AecuBinding.java index c6767e27..2a453fd5 100644 --- a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/AecuBinding.java +++ b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/AecuBinding.java @@ -28,6 +28,11 @@ @ProviderType public interface AecuBinding { + /** + * AECU Groovy binding name. + */ + public static final String BINDING_NAME = "aecu"; + /** * Returns a content upgrade builder. This is the starting point for the migrations. * diff --git a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java index 0abea3af..c9517b2c 100644 --- a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java +++ b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java @@ -18,17 +18,17 @@ */ package de.valtech.aecu.api.groovy.console.bindings; -import de.valtech.aecu.api.groovy.console.bindings.filters.FilterBy; -import de.valtech.aecu.api.service.AecuException; +import java.util.Map; import org.apache.sling.api.resource.PersistenceException; import org.osgi.annotation.versioning.ProviderType; -import java.util.Map; +import de.valtech.aecu.api.groovy.console.bindings.filters.FilterBy; +import de.valtech.aecu.api.service.AecuException; /** * This class provides the builder methods to perform a content upgrade. - * + * * @author Roxana Muresan * @author Roland Gruber */ @@ -37,7 +37,7 @@ public interface ContentUpgrade { /** * Loops for given list of resources. - * + * * @param paths list of paths * @return upgrade object **/ @@ -45,7 +45,7 @@ public interface ContentUpgrade { /** * Loops for all child resources of the given path. The path itself is not included. - * + * * @param path path * @return upgrade object **/ @@ -70,7 +70,7 @@ public interface ContentUpgrade { /** * Loops over resources found by SQL2 query. - * + * * @param query query string * @return upgrade object */ @@ -78,7 +78,7 @@ public interface ContentUpgrade { /** * Filters by existence of a single property. - * + * * @param name property name * @return upgrade object */ @@ -86,7 +86,7 @@ public interface ContentUpgrade { /** * Filters by a single property. - * + * * @param name property name * @param value property value * @return upgrade object @@ -96,7 +96,7 @@ public interface ContentUpgrade { /** * Filters by a single property using a regular expression for the value. This is intended for * single value properties. - * + * * @param name property name * @param regex regular expression to match value * @return upgrade object @@ -106,7 +106,7 @@ public interface ContentUpgrade { /** * Filters by checking if any property matches the given regular expression for the value. This * is intended for single value properties. - * + * * @param regex regular expression to match value * @return upgrade object */ @@ -114,7 +114,7 @@ public interface ContentUpgrade { /** * Filters by properties. Can be used also for Multi-value properties. - * + * * @param conditionProperties properties to filter * @return upgrade object **/ @@ -131,7 +131,7 @@ public interface ContentUpgrade { /** * Filters by node name exact match. - * + * * @param nodeName node name * @return upgrade object */ @@ -139,7 +139,7 @@ public interface ContentUpgrade { /** * Filters by node name using regular expression. - * + * * @param regex regular expression (Java standard pattern) * @return upgrade object */ @@ -155,7 +155,7 @@ public interface ContentUpgrade { /** * Filters by using the given filter. - * + * * @param filter filter * @return upgrade object */ @@ -163,16 +163,44 @@ public interface ContentUpgrade { /** * Sets a property value. - * + * * @param name property name * @param value property value * @return upgrade object **/ ContentUpgrade doSetProperty(String name, Object value); + /** + * Joins a property value into a single value. Uses "," to join multiple values. Deletes + * properties with empty array values. + * + * @param name property name + * @return upgrade object + **/ + ContentUpgrade doJoinProperty(String name); + + /** + * Joins a property value into a single value. Uses "," to join multiple values. + * + * @param name property name + * @param value property value fall back for empty arrays. Use null to delete the property. + * @return upgrade object + **/ + ContentUpgrade doJoinProperty(String name, Object value); + + /** + * Joins a property value into a single value using the given separator. + * + * @param name property name + * @param value property value fall back for empty arrays. Use null to delete the property. + * @param name separator (e.g. ",") + * @return upgrade object + **/ + ContentUpgrade doJoinProperty(String name, Object value, String separator); + /** * Deletes a property if existing. - * + * * @param name property name * @return upgrade object */ @@ -180,7 +208,7 @@ public interface ContentUpgrade { /** * Renames a property if existing. - * + * * @param oldName old property name * @param newName new property name * @return upgrade object @@ -189,7 +217,7 @@ public interface ContentUpgrade { /** * Copies a property to a relative path. - * + * * @param name property name * @param newName new property name * @param relativeResourcePath relative path @@ -199,7 +227,7 @@ public interface ContentUpgrade { /** * Moves a property to a relative path. - * + * * @param name property name * @param newName new property name * @param relativeResourcePath relative path @@ -209,7 +237,7 @@ public interface ContentUpgrade { /** * Adds values to a multivalue property. - * + * * @param name property name * @param values values * @return upgrade object @@ -218,7 +246,7 @@ public interface ContentUpgrade { /** * Removes values of a multivalue property. - * + * * @param name property name * @param values values to remove * @return upgrade object @@ -227,7 +255,7 @@ public interface ContentUpgrade { /** * Replaces values in a multivalue property. - * + * * @param name property name * @param oldValues values to remove * @param newValues values to add @@ -238,7 +266,7 @@ public interface ContentUpgrade { /** * Replaces a substring in all properties of the matching resource. Only applies to String * properties. - * + * * @param oldValue old value * @param newValue new value * @return upgrade object @@ -248,7 +276,7 @@ public interface ContentUpgrade { /** * Replaces a substring in specific properties of the matching resource. Only applies to String * properties. - * + * * @param oldValue old value * @param newValue new value * @param propertyNames property names that should be checked @@ -259,7 +287,7 @@ public interface ContentUpgrade { /** * Replaces a substring in all properties of the matching resource using a regular expression. * Only applies to String properties. - * + * * @param searchRegex regex to match old value * @param replacement new value, may contain matcher groups (e.g. $1) * @return upgrade object @@ -269,7 +297,7 @@ public interface ContentUpgrade { /** * Replaces a substring in specific properties of the matching resource using a regular * expression. Only applies to String properties. - * + * * @param searchRegex regex to match old value * @param replacement new value, may contain matcher groups (e.g. $1) * @param propertyNames property names that should be checked @@ -279,7 +307,7 @@ public interface ContentUpgrade { /** * Renames a resource to the given name. - * + * * @param newName path * @return newName new name */ @@ -287,15 +315,25 @@ public interface ContentUpgrade { /** * Copies a resource to a relative path. - * + * * @param relativePath path * @return upgrade object */ ContentUpgrade doCopyResourceToRelativePath(String relativePath); + /** + * Copies a resource to a relative path. + * + * @param relativePath path + * @param newName name for the new resource. If not provided the name of the source + * resource will be used. + * @return upgrade object + */ + ContentUpgrade doCopyResourceToRelativePath(String relativePath, String newName); + /** * Moves a resource to a relative path. - * + * * @param relativePath path * @return upgrade object */ @@ -315,14 +353,14 @@ public interface ContentUpgrade { /** * Deletes the child resources if supplied. If no children are specified it deletes the resource * itself. - * + * * @return upgrade object */ ContentUpgrade doDeleteResource(String... children); /** * Creates a new resource under the current one. - * + * * @param name resource name * @param primaryType jcr:primaryType * @return upgrade object @@ -331,7 +369,7 @@ public interface ContentUpgrade { /** * Creates a new resource under the current one. - * + * * @param name resource name * @param primaryType jcr:primaryType * @param properties properties excl. jcr:primaryType @@ -341,7 +379,7 @@ public interface ContentUpgrade { /** * Creates a new resource under the current one. - * + * * @param name resource name * @param primaryType jcr:primaryType * @param relativePath relative path @@ -351,7 +389,7 @@ public interface ContentUpgrade { /** * Creates a new resource under the current one. - * + * * @param name resource name * @param primaryType jcr:primaryType * @param properties properties excl. jcr:primaryType @@ -362,21 +400,21 @@ public interface ContentUpgrade { /** * Activates the resource. - * + * * @return upgrade object */ ContentUpgrade doActivateResource(); /** * Deactivates the resource. - * + * * @return upgrade object */ ContentUpgrade doDeactivateResource(); /** * Performs a custom action with providing a function. - * + * * @param action action to perform on resource * @return upgrade object */ @@ -384,21 +422,21 @@ public interface ContentUpgrade { /** * Activates the page where the resource is located. - * + * * @return upgrade object */ ContentUpgrade doActivateContainingPage(); /** * Activates the page tree where the resource is located. - * + * * @return upgrade object */ ContentUpgrade doTreeActivateContainingPage(); /** * Activates the page tree where the resource is located. - * + * * @param skipDeactivated skip pages that are deactivated * @return upgrade object */ @@ -406,7 +444,7 @@ public interface ContentUpgrade { /** * Deactivates the page where the resource is located. - * + * * @return upgrade object */ ContentUpgrade doDeactivateContainingPage(); @@ -414,14 +452,14 @@ public interface ContentUpgrade { /** * Deletes the page where the resource is located. This will not work if called multiple times * for the same page. - * + * * @return upgrade object */ ContentUpgrade doDeleteContainingPage(); /** * Adds tags to the containing page of the matching resource. - * + * * @param tags tag IDs or paths * @return upgrade object */ @@ -430,7 +468,7 @@ public interface ContentUpgrade { /** * Sets tags for the containing page of the matching resource. All existing tags are * overwritten. - * + * * @param tags tag IDs or paths * @return upgrade object */ @@ -438,7 +476,7 @@ public interface ContentUpgrade { /** * Removes tags from the containing page of the matching resource. - * + * * @param tags tag IDs or paths * @return upgrade object */ @@ -446,14 +484,14 @@ public interface ContentUpgrade { /** * Checks if the containing page renders with status code 200. - * + * * @return upgrade object */ ContentUpgrade doCheckPageRendering(); /** * Checks if the containing page renders with given status code. - * + * * @param code status code * @return upgrade object */ @@ -461,7 +499,7 @@ public interface ContentUpgrade { /** * Checks if the containing page renders with status code 200 and contains given text. - * + * * @param textPresent page content must include this text * @return upgrade object */ @@ -469,7 +507,7 @@ public interface ContentUpgrade { /** * Checks if the containing page renders with status code 200 and (not) contains given text. - * + * * @param textPresent page content must include this text (can be null) * @param textNotPresent page content must not include this text (can be null) * @return upgrade object @@ -478,7 +516,7 @@ public interface ContentUpgrade { /** * Print path - * + * * @return upgrade object */ ContentUpgrade printPath(); @@ -500,7 +538,7 @@ public interface ContentUpgrade { /** * Saves all changes to repository. - * + * * @throws PersistenceException error during execution * @throws AecuException other error */ @@ -508,7 +546,7 @@ public interface ContentUpgrade { /** * Performs a dry-run. No changes are written to CRX. - * + * * @throws PersistenceException error doing dry-run * @throws AecuException other error */ diff --git a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java index 06f1d8f1..3560675c 100644 --- a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java +++ b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java @@ -22,7 +22,7 @@ * * @author Roxana Muresan */ -@Version("4.1.0") +@Version("4.2.0") package de.valtech.aecu.api.groovy.console.bindings; import org.osgi.annotation.versioning.Version; diff --git a/bundle/pom.xml b/bundle/pom.xml index f6b4866d..7df26ac8 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -5,7 +5,7 @@ de.valtech.aecu aecu - 3.1.1 + 3.2.0 aecu.bundle diff --git a/core/pom.xml b/core/pom.xml index 5b30fba3..1fc1c79f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ de.valtech.aecu aecu - 3.1.1 + 3.2.0 aecu.core @@ -79,24 +79,6 @@ org.owasp dependency-check-maven - - org.jacoco - jacoco-maven-plugin - - - prepare-agent - - prepare-agent - - - - report - - report - - - - diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinProperty.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinProperty.java new file mode 100644 index 00000000..a1405e3c --- /dev/null +++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinProperty.java @@ -0,0 +1,117 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.aecu.core.groovy.console.bindings.actions.properties; + +import javax.annotation.Nonnull; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ModifiableValueMap; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; + +import de.valtech.aecu.core.groovy.console.bindings.actions.Action; + +/** + * Joins a multi-value property to single values. + * + * @author Yves De Bruyne + */ +public class JoinProperty implements Action { + + protected String name; + protected String separator; + protected Object emptyValue; + + /** + * Constructor + * + * @param name property name + */ + public JoinProperty(@Nonnull String name) { + this(name, null, ","); + } + + /** + * Constructor + * + * @param name property name + * @param emptyValue value to set for empty arrays + */ + public JoinProperty(@Nonnull String name, Object emptyValue) { + this(name, emptyValue, ","); + } + + /** + * Constructor + * + * @param name property name + * @param emptyValue value to set for empty arrays + * @param separator separator text for joining + */ + public JoinProperty(@Nonnull String name, Object emptyValue, @Nonnull String separator) { + this.name = name; + this.separator = separator; + this.emptyValue = emptyValue; + } + + @Override + public String doAction(@Nonnull Resource resource) throws PersistenceException { + ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class); + if (properties == null) { + return "WARNING: could not get ModifiableValueMap for resource " + resource.getPath(); + } + if (!properties.containsKey(name)) { + return StringUtils.EMPTY; + } + + Object value = properties.get(name); + + if (!value.getClass().isArray()) { + return StringUtils.EMPTY; + } + + Object[] values = (Object[]) value; + + if (values.length > 0) { + Node node = resource.adaptTo(Node.class); + try { + node.getProperty(name).remove(); + node.setProperty(name, StringUtils.join(values, separator)); + } catch (RepositoryException e) { + throw new PersistenceException(e.getMessage(), e); + } + return "Joined " + value.getClass().getSimpleName() + " property " + name + " for resource " + resource.getPath(); + } + + if (this.emptyValue == null) { + properties.remove(name); + return "Removed empty " + value.getClass().getSimpleName() + " property " + name + " for resource " + + resource.getPath(); + } + + // replace empty array with fallback + properties.remove(name); + properties.put(name, this.emptyValue); + return "Replaced empty " + value.getClass().getSimpleName() + " property " + name + " with " + this.emptyValue + + " for resource " + resource.getPath(); + } + +} diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java index 6f303f05..3164f0c1 100644 --- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java +++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java @@ -19,7 +19,11 @@ package de.valtech.aecu.core.groovy.console.bindings.actions.resource; import javax.annotation.Nonnull; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; @@ -29,45 +33,60 @@ import de.valtech.aecu.core.groovy.console.bindings.actions.Action; import de.valtech.aecu.core.groovy.console.bindings.actions.util.PageUtil; +import de.valtech.aecu.core.groovy.console.bindings.impl.BindingContext; /** + * Copies a resource to the given path. + * * @author Roxana Muresan */ public class CopyResourceToRelativePath implements Action { private String relativePath; - private ResourceResolver resourceResolver; + private String newName; + private BindingContext context; /** * Constructor * - * @param relativePath relative path - * @param resourceResolver resource resolver + * @param relativePath relative path + * @param newName new name + * @param context binding context */ - public CopyResourceToRelativePath(@Nonnull String relativePath, @Nonnull ResourceResolver resourceResolver) { + public CopyResourceToRelativePath(@Nonnull String relativePath, String newName, @Nonnull BindingContext context) { this.relativePath = relativePath; - this.resourceResolver = resourceResolver; + this.newName = newName; + this.context = context; } @Override public String doAction(@Nonnull Resource resource) throws PersistenceException { - Resource destinationResource = resourceResolver.getResource(resource, relativePath); - if (destinationResource != null) { - String sourceAbsPAth = resource.getPath(); - String destinationAsPath = destinationResource.getPath(); + ResourceResolver resourceResolver = context.getResolver(); + Resource destinationParentResource = resourceResolver.getResource(resource, relativePath); + if (destinationParentResource != null) { + String sourcePath = resource.getPath(); + String destinationName = StringUtils.isNotEmpty(newName) ? newName : resource.getName(); + String destinationPath = destinationParentResource.getPath() + FileSystem.SEPARATOR + destinationName; + PageUtil pageUtil = new PageUtil(); if (pageUtil.isPageResource(resource)) { PageManager pageManager = resourceResolver.adaptTo(PageManager.class); try { - pageManager.copy(resource, destinationAsPath + "/" + resource.getName(), null, false, false, false); + pageManager.copy(resource, destinationPath, null, false, false, false); } catch (WCMException | IllegalArgumentException e) { - throw new PersistenceException("Unable to copy " + sourceAbsPAth + ": " + e.getMessage()); + throw new PersistenceException( + "Unable to copy " + sourcePath + " as " + destinationPath + ": " + e.getMessage()); + } + } else if (!context.isDryRun()) { + try { + Session session = resourceResolver.adaptTo(Session.class); + session.getWorkspace().copy(sourcePath, destinationPath); + } catch (RepositoryException e) { + throw new PersistenceException( + "Unable to copy " + sourcePath + " as " + destinationPath + ": " + e.getMessage()); } - } else { - resourceResolver.copy(sourceAbsPAth, destinationAsPath); } - - return "Copied " + sourceAbsPAth + " to path " + destinationAsPath; + return "Copied " + sourcePath + " to " + destinationPath; } return "WARNING: could not read copy destination resource " + relativePath; } diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java index 792f32dc..a1a95da6 100644 --- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java +++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java @@ -66,6 +66,7 @@ import de.valtech.aecu.core.groovy.console.bindings.actions.print.PrintProperty; import de.valtech.aecu.core.groovy.console.bindings.actions.properties.CopyPropertyToRelativePath; import de.valtech.aecu.core.groovy.console.bindings.actions.properties.DeleteProperty; +import de.valtech.aecu.core.groovy.console.bindings.actions.properties.JoinProperty; import de.valtech.aecu.core.groovy.console.bindings.actions.properties.MovePropertyToRelativePath; import de.valtech.aecu.core.groovy.console.bindings.actions.properties.RenameProperty; import de.valtech.aecu.core.groovy.console.bindings.actions.properties.SetProperty; @@ -87,7 +88,7 @@ /** * Implements the content upgrade API. - * + * * @author Roxana Muresan * @author Roland Gruber */ @@ -105,7 +106,7 @@ public class ContentUpgradeImpl implements ContentUpgrade { /** * Constructor - * + * * @param resourceResolver resolver * @param scriptContext Groovy context */ @@ -206,7 +207,7 @@ public ContentUpgrade filterWith(@Nonnull FilterBy filter) { /** * Adds another filter. If there is already a filter then an AND filter will be created. - * + * * @param filter filter */ private void addFilter(@Nonnull FilterBy filter) { @@ -227,6 +228,24 @@ public ContentUpgrade doSetProperty(@Nonnull String name, Object value) { return this; } + @Override + public ContentUpgrade doJoinProperty(@Nonnull String name) { + actions.add(new JoinProperty(name)); + return this; + } + + @Override + public ContentUpgrade doJoinProperty(@Nonnull String name, Object value) { + actions.add(new JoinProperty(name, value)); + return this; + } + + @Override + public ContentUpgrade doJoinProperty(@Nonnull String name, Object value, String separator) { + actions.add(new JoinProperty(name, value, separator)); + return this; + } + @Override public ContentUpgrade doDeleteProperty(@Nonnull String name) { actions.add(new DeleteProperty(name)); @@ -304,7 +323,13 @@ public ContentUpgrade doRename(String newName) { @Override public ContentUpgrade doCopyResourceToRelativePath(@Nonnull String relativePath) { - actions.add(new CopyResourceToRelativePath(relativePath, context.getResolver())); + actions.add(new CopyResourceToRelativePath(relativePath, null, context)); + return this; + } + + @Override + public ContentUpgrade doCopyResourceToRelativePath(@Nonnull String relativePath, String newName) { + actions.add(new CopyResourceToRelativePath(relativePath, newName, context)); return this; } diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java index fd7e7051..71a03a3e 100644 --- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java +++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java @@ -65,7 +65,7 @@ public Map getBindingVariables(ScriptContext context) { resourceResolverService.getAdminResourceResolver(), resourceResolverFactory, replicator, context); BindingVariable aecuVar = new BindingVariable(aecuBinding, AecuBinding.class, "https://github.com/valtech/aem-easy-content-upgrade"); - variables.put("aecu", aecuVar); + variables.put(AecuBinding.BINDING_NAME, aecuVar); } catch (LoginException e) { LOG.error( "Failed to get resource resolver for aecu-content-migrator or aecu-admin, make sure you all the configurations needed for this system user are deployed."); diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServlet.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServlet.java new file mode 100644 index 00000000..a21855f8 --- /dev/null +++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServlet.java @@ -0,0 +1,127 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package de.valtech.aecu.core.groovy.console.bindings.servlet; + + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.servlets.SlingAllMethodsServlet; +import org.osgi.service.component.annotations.Component; + +import com.google.gson.Gson; + +import de.valtech.aecu.api.groovy.console.bindings.AecuBinding; + +/** + * Provides auto-complete suggestions for AECU. + * + * @author Roxana Muresan + * @author Roland Gruber + */ +@Component(immediate = true, service = {Servlet.class}, + property = {"sling.servlet.paths=/bin/public/valtech/aecu/ace_autocomplete", "sling.servlet.extensions=json", + "sling.servlet.methods=GET"}) +public class AceAutocompleteServlet extends SlingAllMethodsServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) + throws ServletException, IOException { + + try (PrintWriter responseWriter = response.getWriter()) { + Set methodsSet = new TreeSet<>(); + methodsSet.add(AecuBinding.BINDING_NAME); + + List methods = getPublicMethodsOfClass(AecuBinding.class); + for (Method method : methods) { + methodsSet.add(getCompletion(method)); + + Class extensionClass = method.getReturnType(); + List publicMethods = getPublicMethodsOfClass(extensionClass); + publicMethods.stream().forEach(n -> methodsSet.add(getCompletion(n))); + } + + String responseString = new Gson().toJson(methodsSet.toArray(new String[] {})); + responseWriter.write(responseString); + response.setStatus(HttpStatus.SC_OK); + } + } + + /** + * Returns the completion text for the method. + * + * @param method method + * @return completion value + */ + private String getCompletion(Method method) { + return method.getName() + "(" + getCompletionForArguments(method) + ")"; + } + + /** + * Returns the auto-completion data for the method arguments. + * + * @param method method + * @return completion data + */ + private String getCompletionForArguments(Method method) { + if (method.getParameterCount() == 0) { + return StringUtils.EMPTY; + } + Parameter[] parameters = method.getParameters(); + List parameterValues = new ArrayList<>(); + for (Parameter parameter : parameters) { + if (parameter.isNamePresent()) { + parameterValues.add(parameter.getName() + " " + parameter.getType().getSimpleName()); + } else { + parameterValues.add(parameter.getType().getSimpleName()); + } + } + return String.join(", ", parameterValues); + } + + /** + * Returns the public methods of the given class. + * + * @param clazz class + * @return methods + */ + protected List getPublicMethodsOfClass(Class clazz) { + return Arrays.stream(clazz.getDeclaredMethods()).filter(m -> (m.getModifiers() & Modifier.PUBLIC) != 0) + .collect(Collectors.toList()); + } +} diff --git a/core/src/main/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModel.java b/core/src/main/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModel.java new file mode 100644 index 00000000..0bafad7c --- /dev/null +++ b/core/src/main/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModel.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.aecu.core.model.accessvalidation; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.OSGiService; +import org.apache.sling.models.annotations.injectorspecific.SlingObject; + +import de.valtech.aecu.core.security.AccessValidationService; + +/** + * Model for access validation. + * + * @author Roland Gruber + */ +@Model(adaptables = SlingHttpServletRequest.class) +public class AccessValidatorModel { + + @SlingObject + private SlingHttpServletRequest request; + + @OSGiService + private AccessValidationService accessValidationServce; + + /** + * Returns if the current user may read the history. + * + * @return can read + */ + public boolean isAbleToReadHistory() { + return accessValidationServce.canReadHistory(request); + } + + /** + * Returns if the current user may execute scripts. + * + * @return can execute + */ + public boolean isAbleToExecute() { + return accessValidationServce.canExecute(request); + } + +} diff --git a/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java b/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java index 2d0589e7..509f4f63 100644 --- a/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java +++ b/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -37,6 +37,7 @@ import de.valtech.aecu.api.service.AecuException; import de.valtech.aecu.api.service.AecuService; +import de.valtech.aecu.core.security.AccessValidationService; /** @@ -56,8 +57,14 @@ public class ExecuteDataSource { @OSGiService private AecuService aecuService; + @OSGiService + private AccessValidationService accessValidationService; + @PostConstruct public void setup() throws AecuException { + if (!accessValidationService.canExecute(request)) { + return; + } String path = request.getParameter("searchPath"); List entries = new ArrayList<>(); diff --git a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java index 470c1a5e..aad4c40b 100644 --- a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java +++ b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2019 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -42,6 +42,7 @@ import de.valtech.aecu.api.service.AecuException; import de.valtech.aecu.api.service.AecuService; import de.valtech.aecu.api.service.HistoryEntry; +import de.valtech.aecu.core.security.AccessValidationService; /** * Datasource model for history overview page. @@ -57,13 +58,19 @@ public class HistoryDataSource { private static final Logger LOG = LoggerFactory.getLogger(DataSource.class); @SlingObject - SlingHttpServletRequest request; + private SlingHttpServletRequest request; @OSGiService - AecuService aecuService; + private AecuService aecuService; + + @OSGiService + private AccessValidationService accessValidationService; @PostConstruct public void setup() { + if (!accessValidationService.canReadHistory(request)) { + return; + } String[] selectors = request.getRequestPathInfo().getSelectors(); int offset = 0; int limit = 50; diff --git a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java index 489d60a0..7eb0479b 100644 --- a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java +++ b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -39,6 +39,7 @@ import de.valtech.aecu.api.service.ExecutionState; import de.valtech.aecu.api.service.HistoryEntry; import de.valtech.aecu.core.history.HistoryUtil; +import de.valtech.aecu.core.security.AccessValidationService; /** * Sling model for history overview area. @@ -57,6 +58,9 @@ public class HistoryOverview { @OSGiService private HistoryUtil historyUtil; + @OSGiService + private AccessValidationService accessValidationService; + private HistoryEntry historyEntry; private final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -66,6 +70,9 @@ public class HistoryOverview { */ @PostConstruct public void init() { + if (!accessValidationService.canReadHistory(request)) { + return; + } RequestParameter entryParam = request.getRequestParameter("entry"); if (entryParam == null) { return; diff --git a/core/src/main/java/de/valtech/aecu/core/security/AccessValidationService.java b/core/src/main/java/de/valtech/aecu/core/security/AccessValidationService.java new file mode 100644 index 00000000..2888bd07 --- /dev/null +++ b/core/src/main/java/de/valtech/aecu/core/security/AccessValidationService.java @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.aecu.core.security; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.sling.api.SlingHttpServletRequest; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Service to check if an action is allowed. + * + * @author Roland Gruber + */ +@Component(service = AccessValidationService.class) +@Designate(ocd = AccessValidationServiceConfiguration.class) +public class AccessValidationService { + + private static final Logger LOG = LoggerFactory.getLogger(AccessValidationService.class); + + private AccessValidationServiceConfiguration config; + + @Activate + public void activate(AccessValidationServiceConfiguration config) { + this.config = config; + } + + /** + * Checks if the current user is allowed to read the AECU history. + * + * @param request request + * @return read allowed + */ + public boolean canReadHistory(SlingHttpServletRequest request) { + return isAdminOrInAllowedList(request, config.readers()); + } + + /** + * Checks if the user is allowed to execute scripts. + * + * @param request request + * @return execute allowed + */ + public boolean canExecute(SlingHttpServletRequest request) { + return isAdminOrInAllowedList(request, config.executers()); + } + + /** + * Returns if the user is either admin or part of the provided groups. + * + * @param request request + * @param groups groups that are allowed + * @return is allowed + */ + private boolean isAdminOrInAllowedList(SlingHttpServletRequest request, String[] groups) { + String userName = getUserName(request); + if (isAdmin(userName)) { + return true; + } + if (groups == null) { + return false; + } + List userGroups = getUserGroupNames(request); + for (String group : groups) { + if (userGroups.contains(group)) { + return true; + } + } + return false; + } + + /** + * Extracts the user name from the request. + * + * @param request request + * @return user name + */ + private String getUserName(SlingHttpServletRequest request) { + return request.getUserPrincipal().getName(); + } + + /** + * Returns the AEM groups that belong to the user. + * + * @param user user + * @return group names + */ + private List getUserGroupNames(SlingHttpServletRequest request) { + List groupList = new ArrayList<>(); + UserManager userManager = request.getResourceResolver().adaptTo(UserManager.class); + try { + Authorizable authorizable = userManager.getAuthorizable(request.getUserPrincipal()); + Iterator groupIt = authorizable.memberOf(); + while (groupIt.hasNext()) { + groupList.add(groupIt.next().getID()); + } + } catch (RepositoryException e) { + LOG.error("Unable to get groups", e); + } + return groupList; + } + + /** + * Returns if the user is admin. + * + * @param userName user name + * @return is admin + */ + private boolean isAdmin(String userName) { + return UserConstants.DEFAULT_ADMIN_ID.equals(userName); + } + +} diff --git a/core/src/main/java/de/valtech/aecu/core/security/AccessValidationServiceConfiguration.java b/core/src/main/java/de/valtech/aecu/core/security/AccessValidationServiceConfiguration.java new file mode 100644 index 00000000..dd1823a9 --- /dev/null +++ b/core/src/main/java/de/valtech/aecu/core/security/AccessValidationServiceConfiguration.java @@ -0,0 +1,25 @@ +package de.valtech.aecu.core.security; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Configuration for access validation service. + * + * @author Roland Gruber + */ +@ObjectClassDefinition(name = "AECU Access Validation Service Configuration") +@ProviderType +public @interface AccessValidationServiceConfiguration { + + @AttributeDefinition(type = AttributeType.STRING, cardinality = 1000, name = "Read access", + description = "The configured group and user names have read access to AECU. Admin user always has access.") + String[] readers(); + + @AttributeDefinition(type = AttributeType.STRING, cardinality = 1000, name = "Execute access", + description = "The configured group and user names have execute access to AECU. Admin user always has access.") + String[] executers(); + +} diff --git a/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java b/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java index 7dcd2765..1f516c6b 100644 --- a/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java +++ b/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -38,6 +38,7 @@ import de.valtech.aecu.api.service.ExecutionState; import de.valtech.aecu.api.service.HistoryEntry; import de.valtech.aecu.core.history.HistoryUtil; +import de.valtech.aecu.core.security.AccessValidationService; /** * @author Bryan Chavez @@ -53,15 +54,21 @@ public class ExecutionServlet extends BaseServlet { "ExecutionServlet :: Make sure your are sending the correct parameters."; @Reference - private AecuService aecuService; + private transient AecuService aecuService; @Reference - private HistoryUtil historyUtil; + private transient HistoryUtil historyUtil; + + @Reference + private transient AccessValidationService accessValidationService; @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { + if (!accessValidationService.canExecute(request)) { + return; + } this.setNoCache(response); diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/print/PrintJsonTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/print/PrintJsonTest.java index 4525f3ea..babad2a2 100644 --- a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/print/PrintJsonTest.java +++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/print/PrintJsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -19,7 +19,11 @@ package de.valtech.aecu.core.groovy.console.bindings.actions.print; -import com.adobe.granite.rest.utils.ModifiableMappedValueMapDecorator; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; @@ -29,11 +33,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; +import com.adobe.granite.rest.utils.ModifiableMappedValueMapDecorator; /** * Tests PrintJson @@ -64,7 +64,6 @@ public void init() { public void test_doAction() { PrintJson printJson = new PrintJson(); String result = printJson.doAction(resource); - System.out.print(result); assertTrue(result.contains("\"sling:resourceType\": \"weretail/components/content/heroimage\"")); assertTrue(result.contains("\"number\": 123")); diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinPropertyTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinPropertyTest.java new file mode 100644 index 00000000..f4ce28f6 --- /dev/null +++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/JoinPropertyTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.aecu.core.groovy.console.bindings.actions.properties; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.ValueFormatException; +import javax.jcr.lock.LockException; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.version.VersionException; + +import org.apache.sling.api.resource.ModifiableValueMap; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Tests JoinProperty + * + * @author Yves De Bruyne + */ +@RunWith(MockitoJUnitRunner.class) +public class JoinPropertyTest { + + private static final String VAL1 = "val1"; + private static final String EMPTY_VALUE = "emptyValue"; + + private static final String ATTR = "attr"; + public static final String SEPARATOR = ";|;"; + + @Mock + private Resource resource; + + @Mock + private ModifiableValueMap valueMap; + + @Mock + private Node node; + + @Mock + private Property property; + + @Before + public void setup() throws PathNotFoundException, RepositoryException { + when(resource.adaptTo(ModifiableValueMap.class)).thenReturn(valueMap); + when(resource.adaptTo(Node.class)).thenReturn(node); + when(node.getProperty(ATTR)).thenReturn(property); + } + + @Test + public void doAction() throws PersistenceException, ValueFormatException, VersionException, LockException, + ConstraintViolationException, RepositoryException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(new String[] {VAL1}); + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE); + action.doAction(resource); + + verify(property, times(1)).remove(); + verify(node, times(1)).setProperty(ATTR, VAL1); + } + + @Test + public void doActionNewProperty() throws PersistenceException { + // empty resource: attribute is missing + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE); + action.doAction(resource); + + // NOP + verify(valueMap, never()).remove(""); + verify(valueMap, never()).put("", ""); + } + + @Test + public void doActionReplaceArrayWithMultipleValues() throws PersistenceException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(new String[] {VAL1, VAL1}); + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE); + action.doAction(resource); + } + + @Test + public void doActionReplaceEmptyArray() throws PersistenceException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(new Boolean[] {}); + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE); + action.doAction(resource); + + verify(valueMap, times(1)).remove(ATTR); + verify(valueMap, times(1)).put(ATTR, EMPTY_VALUE); + } + + @Test + public void doActionNoArgs() throws PersistenceException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(new Boolean[] {}); + + JoinProperty action = new JoinProperty(ATTR); + action.doAction(resource); + + verify(valueMap, times(1)).remove(ATTR); + } + + @Test + public void doActionCustomSeparator() throws PersistenceException, AccessDeniedException, VersionException, LockException, + ConstraintViolationException, RepositoryException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(new String[] {VAL1, VAL1}); + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE, SEPARATOR); + action.doAction(resource); + + verify(property, times(1)).remove(); + verify(node, times(1)).setProperty(ATTR, VAL1 + SEPARATOR + VAL1); + } + + @Test + public void doActionReplaceExistingFlat() throws PersistenceException { + // setup test resource + when(valueMap.containsKey(ATTR)).thenReturn(true); + when(valueMap.get(ATTR)).thenReturn(10); + + JoinProperty action = new JoinProperty(ATTR, EMPTY_VALUE); + action.doAction(resource); + + } + +} diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePathTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePathTest.java new file mode 100644 index 00000000..8fca3979 --- /dev/null +++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePathTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package de.valtech.aecu.core.groovy.console.bindings.actions.resource; + +import com.day.cq.wcm.api.NameConstants; +import com.day.cq.wcm.api.PageManager; +import com.day.cq.wcm.api.WCMException; + +import de.valtech.aecu.core.groovy.console.bindings.impl.BindingContext; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Workspace; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test CopyResourceToRelativePathTest + * @author Roxana Muresan + */ +@RunWith(MockitoJUnitRunner.class) +public class CopyResourceToRelativePathTest { + + private static final String SOURCE_PATH = "/content/source"; + private static final String SOURCE_NAME = "source"; + private static final String DESTINATION_PARENT_PATH = "/content/to/relative"; + + @Mock + private BindingContext contextMock; + @Mock + private ResourceResolver resourceResolverMock; + @Mock + private Resource resourceMock; + @Mock + private Resource destinationParentResourceMock; + @Mock + private PageManager pageManagerMock; + @Mock + private Session sessionMock; + @Mock + private Workspace workspaceMock; + @Mock + private ValueMap valueMapMock; + + @Before + public void setup() { + when(contextMock.getResolver()).thenReturn(resourceResolverMock); + when(contextMock.isDryRun()).thenReturn(false); + when(resourceResolverMock.getResource(eq(resourceMock), anyString())).thenReturn(destinationParentResourceMock); + when(resourceMock.getPath()).thenReturn(SOURCE_PATH); + when(resourceMock.getName()).thenReturn(SOURCE_NAME); + when(resourceMock.getValueMap()).thenReturn(valueMapMock); + when(destinationParentResourceMock.getPath()).thenReturn(DESTINATION_PARENT_PATH); + when(resourceResolverMock.adaptTo(eq(PageManager.class))).thenReturn(pageManagerMock); + when(resourceResolverMock.adaptTo(eq(Session.class))).thenReturn(sessionMock); + when(sessionMock.getWorkspace()).thenReturn(workspaceMock); + } + + @Test + public void testDoAction_copyResource_differentPath_withNewName() throws PersistenceException, RepositoryException { + testDoAction_onResource("newName", DESTINATION_PARENT_PATH + "/newName"); + } + + @Test + public void testDoAction_copyResource_differentPath_noNewName() throws PersistenceException, RepositoryException { + testDoAction_onResource(null, DESTINATION_PARENT_PATH + "/" + SOURCE_NAME); + } + + @Test + public void testDoAction_copyResource_samePath_withNewName() throws PersistenceException, RepositoryException { + when(destinationParentResourceMock.getPath()).thenReturn("/content"); + testDoAction_onResource("newName", "/content/newName"); + } + + @Test + public void testDoAction_copyResource_dryRun() throws PersistenceException, RepositoryException { + CopyResourceToRelativePath copyAction = new CopyResourceToRelativePath("to/relative", "newName", contextMock); + when(valueMapMock.get(JcrConstants.JCR_PRIMARYTYPE, String.class)).thenReturn(JcrConstants.NT_UNSTRUCTURED); + when(contextMock.isDryRun()).thenReturn(true); + + copyAction.doAction(resourceMock); + + verify(workspaceMock, never()).copy(anyString(), anyString()); + } + + private void testDoAction_onResource(String newName, String expectedDestinationPath) throws PersistenceException, RepositoryException { + CopyResourceToRelativePath copyAction = new CopyResourceToRelativePath("to/relative", newName, contextMock); + when(valueMapMock.get(JcrConstants.JCR_PRIMARYTYPE, String.class)).thenReturn(JcrConstants.NT_UNSTRUCTURED); + + copyAction.doAction(resourceMock); + + verify(workspaceMock, times(1)).copy(eq(SOURCE_PATH), eq(expectedDestinationPath)); + } + + @Test + public void testDoAction_copyPage_differentPath_withNewName() throws WCMException, PersistenceException { + testDoAction_onPage("newName", DESTINATION_PARENT_PATH + "/newName"); + } + + @Test + public void testDoAction_copyPage_differentPath_noNewName() throws WCMException, PersistenceException { + testDoAction_onPage(null, DESTINATION_PARENT_PATH + "/" + SOURCE_NAME); + } + + @Test + public void testDoAction_copyPage_samePath_withNewName() throws WCMException, PersistenceException { + when(destinationParentResourceMock.getPath()).thenReturn("/content"); + testDoAction_onPage("newName", "/content/newName"); + } + + private void testDoAction_onPage(String newName, String expectedDestinationPath) throws PersistenceException, WCMException { + CopyResourceToRelativePath copyAction = new CopyResourceToRelativePath("to/relative", newName, contextMock); + when(valueMapMock.get(JcrConstants.JCR_PRIMARYTYPE, String.class)).thenReturn(NameConstants.NT_PAGE); + + copyAction.doAction(resourceMock); + + verify(pageManagerMock, times(1)).copy(eq(resourceMock), eq(expectedDestinationPath), eq(null), eq(false), eq(false), eq(false)); + } +} diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServletTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServletTest.java new file mode 100644 index 00000000..a52b2803 --- /dev/null +++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/servlet/AceAutocompleteServletTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package de.valtech.aecu.core.groovy.console.bindings.servlet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +import javax.servlet.ServletException; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; + +import de.valtech.aecu.api.groovy.console.bindings.AecuBinding; + +@RunWith(MockitoJUnitRunner.class) +public class AceAutocompleteServletTest { + + @Spy + private AceAutocompleteServlet underTest = new AceAutocompleteServlet(); + + @Test + public void test_getPublicMethodsOfClass_ofAecuBinding() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + List result = underTest.getPublicMethodsOfClass(AecuBinding.class); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("contentUpgradeBuilder", result.get(0).getName()); + assertEquals("validateAccessRights", result.get(1).getName()); + } + + @Test + public void test_doGet_response() throws IOException, ServletException { + SlingHttpServletResponse responseMock = mock(SlingHttpServletResponse.class); + PrintWriter writerMock = mock(PrintWriter.class); + when(responseMock.getWriter()).thenReturn(writerMock); + + underTest.doGet(mock(SlingHttpServletRequest.class), responseMock); + + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writerMock, times(1)).write(captor.capture()); + String actualJsonContent = captor.getValue(); + + assertNotNull(actualJsonContent); + JsonArray actualJson = new Gson().fromJson(actualJsonContent, JsonArray.class); + assertNotNull(actualJson); + assertTrue(actualJson.size() > 5); + assertTrue(actualJson.contains(new JsonPrimitive("aecu"))); + assertTrue(actualJson.contains(new JsonPrimitive("contentUpgradeBuilder()"))); + assertTrue(actualJson.contains(new JsonPrimitive("run()"))); + } +} diff --git a/core/src/test/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModelTest.java b/core/src/test/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModelTest.java new file mode 100644 index 00000000..7951a662 --- /dev/null +++ b/core/src/test/java/de/valtech/aecu/core/model/accessvalidation/AccessValidatorModelTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.valtech.aecu.core.model.accessvalidation; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import de.valtech.aecu.core.security.AccessValidationService; + +/** + * Tests AccessValidatorModel + * + * @author Roland Gruber + */ +@RunWith(MockitoJUnitRunner.class) +public class AccessValidatorModelTest { + + @Mock + private AccessValidationService accessValidatorService; + + @Mock + private SlingHttpServletRequest request; + + @InjectMocks + private AccessValidatorModel model; + + @Before + public void setup() { + when(accessValidatorService.canExecute(request)).thenReturn(true); + when(accessValidatorService.canReadHistory(request)).thenReturn(false); + } + + @Test + public void isAbleToReadHistory() { + assertFalse(model.isAbleToReadHistory()); + } + + @Test + public void isAbleToExecute() { + assertTrue(model.isAbleToExecute()); + } + +} diff --git a/core/src/test/java/de/valtech/aecu/core/model/history/HistoryOverviewTest.java b/core/src/test/java/de/valtech/aecu/core/model/history/HistoryOverviewTest.java index 5a37c960..a6ed7728 100644 --- a/core/src/test/java/de/valtech/aecu/core/model/history/HistoryOverviewTest.java +++ b/core/src/test/java/de/valtech/aecu/core/model/history/HistoryOverviewTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Valtech GmbH + * Copyright 2018 - 2020 Valtech GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, @@ -43,6 +43,7 @@ import de.valtech.aecu.api.service.HistoryEntry; import de.valtech.aecu.core.history.HistoryUtil; import de.valtech.aecu.core.model.history.HistoryOverview.DonutData; +import de.valtech.aecu.core.security.AccessValidationService; import de.valtech.aecu.core.service.HistoryEntryImpl; /** @@ -70,12 +71,26 @@ public class HistoryOverviewTest { @Mock private SlingHttpServletRequest request; + @Mock + private AccessValidationService accessValidationService; + @Before public void setup() { RequestParameter param = mock(RequestParameter.class); when(request.getRequestParameter("entry")).thenReturn(param); when(param.getString()).thenReturn(PATH); when(resolver.getResource(PATH)).thenReturn(historyResource); + when(accessValidationService.canReadHistory(request)).thenReturn(true); + } + + @Test + public void getHistory_noAccess() { + when(accessValidationService.canReadHistory(request)).thenReturn(false); + + overview.init(); + HistoryEntry history = overview.getHistory(); + + assertNull(history); } @Test diff --git a/core/src/test/java/de/valtech/aecu/core/security/AccessValidationServiceTest.java b/core/src/test/java/de/valtech/aecu/core/security/AccessValidationServiceTest.java new file mode 100644 index 00000000..aff36fce --- /dev/null +++ b/core/src/test/java/de/valtech/aecu/core/security/AccessValidationServiceTest.java @@ -0,0 +1,149 @@ +package de.valtech.aecu.core.security; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Tests AccessValidationService + * + * @author Roland Gruber + */ +@RunWith(MockitoJUnitRunner.class) +public class AccessValidationServiceTest { + + + private static final String VALID_GROUP = "validGroup"; + + private AccessValidationService service; + + @Mock + private SlingHttpServletRequest request; + + @Mock + private Principal principal; + + @Mock + private Authorizable authorizable; + + @Mock + private Group validGroup; + + @Mock + private ResourceResolver resolver; + + @Mock + private UserManager userManager; + + @Mock + private AccessValidationServiceConfiguration config; + + @Before + public void setup() throws RepositoryException { + service = new AccessValidationService(); + when(request.getUserPrincipal()).thenReturn(principal); + when(principal.getName()).thenReturn("user"); + when(userManager.getAuthorizable(principal)).thenReturn(authorizable); + List groups = new ArrayList<>(); + groups.add(validGroup); + when(validGroup.getID()).thenReturn(VALID_GROUP); + when(authorizable.memberOf()).thenReturn(groups.iterator()); + when(request.getResourceResolver()).thenReturn(resolver); + when(resolver.adaptTo(UserManager.class)).thenReturn(userManager); + } + + @Test + public void canReadHistory_Admin() { + when(principal.getName()).thenReturn(UserConstants.DEFAULT_ADMIN_ID); + + service.activate(config); + + assertTrue(service.canReadHistory(request)); + } + + @Test + public void canReadHistory_NotAllowedNoGroupConfigured() { + when(principal.getName()).thenReturn("user"); + + service.activate(config); + + assertFalse(service.canReadHistory(request)); + } + + @Test + public void canReadHistory_NotAllowedWrongGroup() { + when(principal.getName()).thenReturn("user"); + when(config.readers()).thenReturn(new String[] {"group"}); + + service.activate(config); + + assertFalse(service.canReadHistory(request)); + } + + @Test + public void canReadHistory_AllowedValidGroup() { + when(principal.getName()).thenReturn("user"); + when(config.readers()).thenReturn(new String[] {VALID_GROUP}); + + service.activate(config); + + assertTrue(service.canReadHistory(request)); + } + + @Test + public void canExecute_Admin() { + when(principal.getName()).thenReturn(UserConstants.DEFAULT_ADMIN_ID); + + service.activate(config); + + assertTrue(service.canExecute(request)); + } + + @Test + public void canExecute_NotAllowedNoGroupConfigured() { + when(principal.getName()).thenReturn("user"); + + service.activate(config); + + assertFalse(service.canExecute(request)); + } + + @Test + public void canExecute_NotAllowedWrongGroup() { + when(principal.getName()).thenReturn("user"); + when(config.executers()).thenReturn(new String[] {"group"}); + + service.activate(config); + + assertFalse(service.canExecute(request)); + } + + @Test + public void canExecute_AllowedValidGroup() { + when(principal.getName()).thenReturn("user"); + when(config.executers()).thenReturn(new String[] {VALID_GROUP}); + + service.activate(config); + + assertTrue(service.canExecute(request)); + } + +} diff --git a/docs/images/limitAccess.png b/docs/images/limitAccess.png new file mode 100644 index 00000000..c86f730d Binary files /dev/null and b/docs/images/limitAccess.png differ diff --git a/examples/pom.xml b/examples/pom.xml index cea68cb9..0c09dfa2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ de.valtech.aecu aecu - 3.1.1 + 3.2.0 aecu.examples diff --git a/pom.xml b/pom.xml index c07ea6d4..db021d39 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.valtech.aecu aecu pom - 3.1.1 + 3.2.0 AECU AEM Easy Content Upgrade https://github.com/valtech/aem-easy-content-upgrade @@ -19,7 +19,7 @@ localhost - 5704 + 5602 localhost 5705 admin @@ -73,6 +73,9 @@ 1.8 1.8 + + -parameters + @@ -120,7 +123,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.8.1 org.apache.maven.plugins diff --git a/sonar-project.properties b/sonar-project.properties index 082c32fb..89c2e037 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=aecu # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. sonar.projectName=AEM Easy Content Upgrade -sonar.projectVersion=3.1.1-SNAPSHOT +sonar.projectVersion=3.2.0-SNAPSHOT # Encoding of the source code. Default is default system encoding sonar.sourceEncoding=UTF-8 diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml index 060077b4..3ee3b4f2 100644 --- a/ui.apps/pom.xml +++ b/ui.apps/pom.xml @@ -5,7 +5,7 @@ de.valtech.aecu aecu - 3.1.1 + 3.2.0 aecu.ui.apps diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu.editor/css/aecu.css b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu.editor/css/aecu.css index 475e252e..10a0852c 100644 --- a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu.editor/css/aecu.css +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu.editor/css/aecu.css @@ -278,3 +278,7 @@ pre { font-weight: bold; color: #FA0A0A; } + +.aecu-access-error { + color: #FA0A0A; +} diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/.content.xml new file mode 100644 index 00000000..9180dd45 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/.content.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js.txt b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js.txt new file mode 100644 index 00000000..e1bcb27b --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js.txt @@ -0,0 +1,2 @@ +#base=js +ace_autocompletion.js \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js/ace_autocompletion.js b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js/ace_autocompletion.js new file mode 100644 index 00000000..1a64ea3d --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/clientlibs/aecu/groovyconsole/js/ace_autocompletion.js @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Valtech GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +$(document).ready(function(){ + + var langTools = ace.require("ace/ext/language_tools"); + + $.get('/bin/public/valtech/aecu/ace_autocomplete.json', function(data) { + if (data != null) { + var namesJsonArray = JSON.parse(data) + initAecuAutocomplete(namesJsonArray); + } + }); + + initAecuAutocomplete = function(namesJsonArray) { + var aecuCompleter = { + identifierRegexps: [/[^\.\s]+/], + getCompletions: function(editor, session, pos, prefix, callback) { + callback(null, getOptions(namesJsonArray, prefix)); + } + } + + function getOptions(namesJsonArray, prefix) { + return namesJsonArray.filter(entry=>{ + return entry.includes(prefix); + }).map(entry=>{ + return { + caption: entry, + value: entry, + meta: "aecu" + }; + }); + }; + + langTools.addCompleter(aecuCompleter); + }; +}); \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/.content.xml new file mode 100644 index 00000000..4a8780f9 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/title.html b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/title.html new file mode 100644 index 00000000..045e9d4f --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/execute/title/title.html @@ -0,0 +1,3 @@ + +AEM Easy Content Upgrade - Execute +You are not allowed to execute scripts diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/.content.xml new file mode 100644 index 00000000..4a8780f9 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/title.html b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/title.html new file mode 100644 index 00000000..34fde053 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/components/content/history/title/title.html @@ -0,0 +1,3 @@ + +AEM Easy Content Upgrade - History +You are not allowed to read the history diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/execute/page/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/execute/page/.content.xml index 04b91394..7bc0fc9b 100644 --- a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/execute/page/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/execute/page/.content.xml @@ -21,6 +21,10 @@ sling:resourceType="granite/ui/components/foundation/includeclientlibs" categories="[coralui3,granite.ui.coral.foundation,aecu.editor]"/> + <rails jcr:primaryType="nt:unstructured"> <search granite:class="cq-rail-components-search" diff --git a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/history/page/.content.xml b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/history/page/.content.xml index 8385fb8d..b27b3b0c 100644 --- a/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/history/page/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/valtech/aecu/tools/history/page/.content.xml @@ -25,8 +25,7 @@ </head> <title jcr:primaryType="nt:unstructured" - text="AEM Easy Content Upgrade - History" - sling:resourceType="granite/ui/components/coral/foundation/text" + sling:resourceType="valtech/aecu/components/content/history/title" /> <views jcr:primaryType="nt:unstructured">