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.aecuaecu
- 3.1.1
+ 3.2.0aecu.api
@@ -98,6 +98,10 @@
junit-addonsjunit-addons
+
+ com.google.code.gson
+ gson
+ com.google.code.findbugsjsr305
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.aecuaecu
- 3.1.1
+ 3.2.0aecu.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.aecuaecu
- 3.1.1
+ 3.2.0aecu.core
@@ -79,24 +79,6 @@
org.owaspdependency-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.aecuaecu
- 3.1.1
+ 3.2.0aecu.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.aecuaecupom
- 3.1.1
+ 3.2.0AECUAEM Easy Content Upgradehttps://github.com/valtech/aem-easy-content-upgrade
@@ -19,7 +19,7 @@
localhost
- 5704
+ 5602localhost5705admin
@@ -73,6 +73,9 @@
1.8
+
+ -parameters
+
@@ -120,7 +123,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.6.1
+ 3.8.1org.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.aecuaecu
- 3.1.1
+ 3.2.0aecu.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]"/>
+