You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2019/10/31 15:32:27 UTC
[sling-org-apache-sling-app-cms] branch master updated: Fixes
SLING-8817 - Adding a User / Group console
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git
The following commit(s) were added to refs/heads/master by this push:
new f34cd6d Fixes SLING-8817 - Adding a User / Group console
f34cd6d is described below
commit f34cd6d53c1c4389f8255fa85063f539044febd9
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Oct 31 11:32:18 2019 -0400
Fixes SLING-8817 - Adding a User / Group console
---
.../org/apache/sling/cms/AuthorizableWrapper.java | 98 ++++++++
.../java/org/apache/sling/cms/CurrentUser.java | 49 ----
builder/src/main/provisioning/composum.txt | 1 -
.../sling/cms/core/internal/CommonUtils.java | 48 ++++
.../sling/cms/core/internal/SimplePrincipal.java | 37 +++
.../internal/models/AuthorizableWrapperImpl.java | 161 ++++++++++++
.../core/internal/models/CMSJobManagerImpl.java | 7 +-
.../cms/core/internal/models/CurrentUserImpl.java | 91 -------
.../operations/ChangePasswordOperation.java | 77 ++++++
.../internal/operations/CreateGroupOperation.java | 83 +++++++
.../internal/operations/CreateUserOperation.java | 87 +++++++
.../core/internal/operations/MembersOperation.java | 108 ++++++++
.../internal/operations/MembershipOperation.java | 106 ++++++++
.../operations/TouchLastModifiedPostOperation.java | 2 +-
.../internal/operations/UpdateStatusOperation.java | 80 ++++++
.../cms/core/helpers/SlingCMSContextHelper.java | 42 ----
.../sling/cms/core/helpers/SlingCMSTestHelper.java | 158 ++++++++++++
.../models/AuthorizableWrapperImplTest.java | 271 +++++++++++++++++++++
.../operations/ChangePasswordOperationTest.java | 87 +++++++
.../operations/CreateGroupOperationTest.java | 103 ++++++++
.../operations/CreateUserOperationTest.java | 105 ++++++++
.../internal/operations/MembersOperationTest.java | 122 ++++++++++
.../operations/MembershipOperationTest.java | 122 ++++++++++
.../operations/UpdateStatusOperationTest.java | 110 +++++++++
.../internal/servlets/DownloadFileServletTest.java | 4 +-
core/src/test/resources/auth.json | 142 +++++++++++
docs/admin-tools.md | 2 +-
docs/img/users-groups.png | Bin 31634 -> 41752 bytes
.../components/cms/staticnav/staticnav.jsp | 8 +-
.../components/editor/fields/auth/members.json | 6 +
.../auth/members/options.jsp} | 16 +-
.../auth/members/values.jsp} | 16 +-
.../components/editor/fields/auth/membership.json | 6 +
.../auth/membership/options.jsp} | 16 +-
.../auth/membership/values.jsp} | 16 +-
.../auth/status/status.jsp} | 30 ++-
.../components/editor/scripts/localeOptions.jsp | 10 +-
.../libs/sling-cms/components/pages/base/nav.jsp | 29 ++-
.../libs/sling-cms/content/auth/group/create.json | 35 +++
.../libs/sling-cms/content/auth/group/members.json | 32 +++
.../jcr_root/libs/sling-cms/content/auth/list.json | 229 +++++++++++++++++
.../libs/sling-cms/content/auth/membership.json | 32 +++
.../libs/sling-cms/content/auth/newfolder.json | 36 +++
.../libs/sling-cms/content/auth/user/create.json | 43 ++++
.../libs/sling-cms/content/auth/user/password.json | 35 +++
.../libs/sling-cms/content/auth/user/profile.json | 41 ++++
.../libs/sling-cms/content/auth/user/status.json | 31 +++
.../jcr_root/libs/sling-cms/content/start.json | 5 +-
....internal.ResourceEditorAssociation-auth.config | 20 ++
49 files changed, 2753 insertions(+), 242 deletions(-)
diff --git a/api/src/main/java/org/apache/sling/cms/AuthorizableWrapper.java b/api/src/main/java/org/apache/sling/cms/AuthorizableWrapper.java
new file mode 100644
index 0000000..417f420
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/AuthorizableWrapper.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms;
+
+import java.util.Iterator;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+
+/**
+ * A wrapper for working with JackRabbit Authorizables in JSPs and Sling Models
+ * from a Resource
+ */
+public interface AuthorizableWrapper {
+
+ /**
+ * Gets the JackRabbit Authorizable
+ *
+ * @return a JackRabbit Authorizable
+ */
+ Authorizable getAuthorizable();
+
+ /**
+ * Gets the declared members of this authorizable. For Users this will return an
+ * empty iterator.
+ *
+ * @return the declared members of this authorizable
+ */
+ Iterator<Authorizable> getDeclaredMembers();
+
+ /**
+ * Get the groups this authorizable is a member of
+ *
+ * @return the direct membership
+ */
+ Iterator<Group> getDeclaredMembership();
+
+ /**
+ * Gets a collection of all of the groups this user belongs to including
+ * containing groups.
+ *
+ * @return the groups the user belongs to
+ */
+ Iterator<String> getGroupNames();
+
+ /**
+ * Get the id of the current user.
+ *
+ * @return the current user's ID
+ */
+ public String getId();
+
+ /**
+ * Gets the transitive members of this authorizable. For Users this will return
+ * an empty iterator.
+ *
+ * @return the transitive members of this authorizable
+ */
+ Iterator<Authorizable> getMembers();
+
+ /**
+ * Gets the transitive membership of this authorizable
+ *
+ * @return the transitive membership
+ */
+ Iterator<Group> getMembership();
+
+ /**
+ * Returns true if the authorizable is a user and is the admin user or is a
+ * member of the administrators group.
+ *
+ * @return true if the user is a super user
+ */
+ public boolean isAdministrator();
+
+ /**
+ * Returns true if the authorizable is a member of the group
+ *
+ * @param groupName the name of the group to check
+ * @return true if the authorizable is a member of the group
+ */
+ public boolean isMember(String groupName);
+
+}
diff --git a/api/src/main/java/org/apache/sling/cms/CurrentUser.java b/api/src/main/java/org/apache/sling/cms/CurrentUser.java
deleted file mode 100644
index 292e61b..0000000
--- a/api/src/main/java/org/apache/sling/cms/CurrentUser.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.cms;
-
-import java.util.Collection;
-
-/**
- * Represents the current user, adaptable from a ResourceResolver.
- */
-public interface CurrentUser {
-
- /**
- * Gets a collection of all of the groups this user belongs to including
- * containing groups.
- *
- * @return the groups the user belongs to
- */
- public Collection<String> getGroups();
-
- /**
- * Get the id of the current user.
- *
- * @return the current user's ID
- */
- public String getId();
-
- /**
- * Returns true if the user is a member of the group, is the admin user or is a
- * member of the administrators group.
- *
- * @param groupName the name of the group to check
- * @return true if the use is a member of the group or is a super user
- */
- public boolean isMember(String groupName);
-}
diff --git a/builder/src/main/provisioning/composum.txt b/builder/src/main/provisioning/composum.txt
index 56e6a43..1d68a9a 100644
--- a/builder/src/main/provisioning/composum.txt
+++ b/builder/src/main/provisioning/composum.txt
@@ -24,7 +24,6 @@
com.composum.sling.core/composum-sling-core-commons/${composum.nodes.version}
com.composum.sling.core/composum-sling-core-console/${composum.nodes.version}
com.composum.sling.core/composum-sling-core-jslibs/${composum.nodes.version}
- com.composum.sling.core/composum-sling-user-management/${composum.nodes.version}
com.composum.sling.core/composum-sling-package-manager/${composum.nodes.version}
com.composum.sling.core.osgi/composum-sling-osgi-package-installer/${composum.nodes.version}
org.apache.jackrabbit.vault/org.apache.jackrabbit.vault/3.2.8
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/CommonUtils.java b/core/src/main/java/org/apache/sling/cms/core/internal/CommonUtils.java
new file mode 100644
index 0000000..1f411d8
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/CommonUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal;
+
+import java.util.Optional;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CommonUtils {
+
+ private static final Logger log = LoggerFactory.getLogger(CommonUtils.class);
+
+ public static final UserManager getUserManager(ResourceResolver resolver) throws RepositoryException {
+ return Optional.ofNullable(resolver.adaptTo(Session.class)).map(session -> {
+ UserManager userManager = null;
+ if (session instanceof JackrabbitSession) {
+ try {
+ userManager = ((JackrabbitSession) session).getUserManager();
+ } catch (RepositoryException e) {
+ log.error("Failed to get user manager", e);
+ }
+
+ }
+ return userManager;
+ }).orElseThrow(() -> new RepositoryException("Failed to get user manager"));
+ }
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/SimplePrincipal.java b/core/src/main/java/org/apache/sling/cms/core/internal/SimplePrincipal.java
new file mode 100644
index 0000000..076e5dc
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/SimplePrincipal.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal;
+
+import java.security.Principal;
+
+/**
+ * Simple String-based principal
+ */
+public class SimplePrincipal implements Principal {
+
+ private final String name;
+
+ public SimplePrincipal(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImpl.java
new file mode 100644
index 0000000..a170163
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImpl.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.models;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.StreamSupport;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.models.annotations.Model;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the AuthorizableWrapper Sling Model.
+ */
+@Model(adaptables = { Resource.class, ResourceResolver.class }, adapters = AuthorizableWrapper.class)
+public class AuthorizableWrapperImpl implements AuthorizableWrapper {
+
+ private static final Logger log = LoggerFactory.getLogger(AuthorizableWrapperImpl.class);
+ private final Authorizable authorizable;
+
+ public AuthorizableWrapperImpl(ResourceResolver resolver) throws RepositoryException {
+ authorizable = CommonUtils.getUserManager(resolver).getAuthorizable(resolver.getUserID());
+ }
+
+ public AuthorizableWrapperImpl(Resource resource)
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ this.authorizable = CommonUtils.getUserManager(resource.getResourceResolver())
+ .getAuthorizableByPath(resource.getPath());
+ if (authorizable == null) {
+ throw new RepositoryException("Failed to get authorizable from " + resource);
+ }
+ }
+
+ @Override
+ public Authorizable getAuthorizable() {
+ return authorizable;
+ }
+
+ @Override
+ public Iterator<Authorizable> getDeclaredMembers() {
+ try {
+ if (authorizable.isGroup()) {
+ return ((Group) authorizable).getDeclaredMembers();
+ } else {
+ List<Authorizable> empty = Collections.emptyList();
+ return empty.iterator();
+ }
+ } catch (RepositoryException e) {
+ log.error("Failed to get membership of authorizable: {}", authorizable, e);
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public Iterator<Group> getDeclaredMembership() {
+ try {
+ return authorizable.declaredMemberOf();
+ } catch (RepositoryException e) {
+ log.error("Failed to get membership of authorizable: {}", authorizable, e);
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public Iterator<String> getGroupNames() {
+ Iterable<Group> iterable = () -> getMembership();
+ return StreamSupport.stream(iterable.spliterator(), false).map(g -> {
+ try {
+ return g.getPrincipal().getName();
+ } catch (RepositoryException e) {
+ log.error("Failed to get name from group: {}", g, e);
+ return null;
+ }
+ }).iterator();
+ }
+
+ @Override
+ public String getId() {
+ try {
+ return authorizable.getID();
+ } catch (RepositoryException e) {
+ log.error("Failed to get ID from authorizable: {}", e);
+ return null;
+ }
+ }
+
+ @Override
+ public Iterator<Authorizable> getMembers() {
+ try {
+ if (authorizable.isGroup()) {
+ return ((Group) authorizable).getMembers();
+ } else {
+ List<Authorizable> empty = Collections.emptyList();
+ return empty.iterator();
+ }
+ } catch (RepositoryException e) {
+ log.error("Failed to get membership of authorizable: {}", authorizable, e);
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public Iterator<Group> getMembership() {
+ try {
+ return authorizable.memberOf();
+ } catch (RepositoryException e) {
+ log.error("Failed to get membership of authorizable: {}", authorizable, e);
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public boolean isAdministrator() {
+ try {
+ return !authorizable.isGroup() && ("admin".equals(authorizable.getID()) || isMember("administrators"));
+ } catch (RepositoryException e) {
+ log.error("Failed to check if authorizable is an administrator", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isMember(String groupName) {
+ Iterable<Group> iterable = () -> getMembership();
+ return StreamSupport.stream(iterable.spliterator(), false).anyMatch(g -> {
+ try {
+ return groupName.equals(g.getID());
+ } catch (RepositoryException e) {
+ log.error("Failed to get ID from authorizable: {}", g, e);
+ return false;
+ }
+ });
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/CMSJobManagerImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/CMSJobManagerImpl.java
index d7a46f0..9abcfef 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/models/CMSJobManagerImpl.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/CMSJobManagerImpl.java
@@ -27,10 +27,10 @@ import java.util.stream.Stream;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.cms.AuthorizableWrapper;
import org.apache.sling.cms.CMSConstants;
import org.apache.sling.cms.CMSJobManager;
import org.apache.sling.cms.ConfigurableJobExecutor;
-import org.apache.sling.cms.CurrentUser;
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.JobManager;
import org.apache.sling.event.jobs.JobManager.QueryType;
@@ -58,8 +58,9 @@ public class CMSJobManagerImpl implements CMSJobManager {
private SlingHttpServletRequest request;
public CMSJobManagerImpl(SlingHttpServletRequest request) {
- CurrentUser currentUser = request.getResourceResolver().adaptTo(CurrentUser.class);
- if (currentUser == null || !currentUser.isMember(CMSConstants.GROUP_JOB_USERS)) {
+ AuthorizableWrapper currentUser = request.getResourceResolver().adaptTo(AuthorizableWrapper.class);
+ if (currentUser == null
+ || (!currentUser.isAdministrator() && !currentUser.isMember(CMSConstants.GROUP_JOB_USERS))) {
throw new SlingException(
"User " + request.getResourceResolver().getUserID() + " is not a member of job-users", null);
}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/CurrentUserImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/CurrentUserImpl.java
deleted file mode 100644
index 4568822..0000000
--- a/core/src/main/java/org/apache/sling/cms/core/internal/models/CurrentUserImpl.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.cms.core.internal.models;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.cms.CMSConstants;
-import org.apache.sling.cms.CurrentUser;
-import org.apache.sling.models.annotations.Model;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Model(adaptables = { ResourceResolver.class }, adapters = CurrentUser.class)
-public class CurrentUserImpl implements CurrentUser {
-
- private ResourceResolver resolver;
- private UserManager userManager;
-
- private Collection<String> groupNames;
-
- private static final Logger log = LoggerFactory.getLogger(CurrentUserImpl.class);
-
- public CurrentUserImpl(ResourceResolver resolver) {
- this.resolver = resolver;
-
- try {
- Session session = resolver.adaptTo(Session.class);
- JackrabbitSession js = (JackrabbitSession) session;
- if (js != null) {
- userManager = js.getUserManager();
- }
- } catch (RepositoryException e) {
- log.warn("Failed to get user manager", e);
- }
- }
-
- @Override
- public String getId() {
- return resolver.getUserID();
- }
-
- @Override
- public Collection<String> getGroups() {
- if (groupNames == null) {
- groupNames = new HashSet<>();
- User user = null;
- try {
- user = (User) userManager.getAuthorizable(getId());
- user.memberOf().forEachRemaining(g -> {
- try {
- groupNames.add(g.getID());
- } catch (RepositoryException e) {
- log.warn("Failed to get group name", e);
- }
- });
- } catch (RepositoryException re) {
- log.warn("Failed to get user", re);
- }
- }
- return groupNames;
- }
-
- @Override
- public boolean isMember(String groupName) {
- return CMSConstants.USER_ADMIN.equals(getId()) || getGroups().contains(CMSConstants.GROUP_ADMINISTRATORS)
- || getGroups().contains(groupName);
- }
-
-}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperation.java
new file mode 100644
index 0000000..533ebb5
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperation.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>ChangePasswordOperation</code> will change a user's password
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=changepassword")
+public class ChangePasswordOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(ChangePasswordOperation.class);
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ String password = request.getParameter(CreateUserOperation.PN_PASSWORD);
+
+ AuthorizableWrapper authWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+
+ if (authWrapper.getAuthorizable().isGroup()) {
+ throw new RepositoryException("Authorizable is a group!");
+ }
+ User user = (User) authWrapper.getAuthorizable();
+
+ user.changePassword(password);
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.setPath(user.getPath());
+ response.onModified(user.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to change user password", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperation.java
new file mode 100644
index 0000000..42183d7
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperation.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.cms.core.internal.SimplePrincipal;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>CreateGroupOperation</code> will create a new group
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=creategroup")
+public class CreateGroupOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(CreateGroupOperation.class);
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ String name = request.getParameter(SlingPostConstants.RP_NODE_NAME);
+
+ ResourceResolver resolver = request.getResourceResolver();
+ UserManager userManager = CommonUtils.getUserManager(resolver);
+
+ if (userManager.getAuthorizable(new SimplePrincipal(name)) != null) {
+ throw new RepositoryException("Authorizable with id " + name + " already exists");
+ }
+ String intermediatePath = StringUtils.substringBeforeLast(request.getResource().getPath(), "/")
+ .replaceAll("\\/home\\/groups\\/?", "");
+ Group group = userManager.createGroup(name, new SimplePrincipal(name), intermediatePath);
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.setPath(group.getPath());
+ response.onCreated(group.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to create group", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateUserOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateUserOperation.java
new file mode 100644
index 0000000..f03db3d
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/CreateUserOperation.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.cms.core.internal.SimplePrincipal;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>CreateUserOperation</code> will create a new user
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=createuser")
+public class CreateUserOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(CreateUserOperation.class);
+
+ public static final String PN_PASSWORD = ":password";
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ String name = request.getParameter(SlingPostConstants.RP_NODE_NAME);
+ String password = request.getParameter(PN_PASSWORD);
+
+ ResourceResolver resolver = request.getResourceResolver();
+ UserManager userManager = CommonUtils.getUserManager(resolver);
+
+ if (userManager.getAuthorizable(new SimplePrincipal(name)) != null) {
+ throw new RepositoryException("Authorizable with id " + name + " already exists");
+ }
+ String intermediatePath = StringUtils.substringBeforeLast(request.getResource().getPath(), "/")
+ .replaceAll("\\/home\\/users\\/?", "");
+
+ User user = userManager.createUser(name, password, new SimplePrincipal(name), intermediatePath);
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.setPath(user.getPath());
+ response.onCreated(user.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to create user", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java
new file mode 100644
index 0000000..c21c661
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembersOperation.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>MembersOperation</code> will update the membership of a group.
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=members")
+public class MembersOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(MembersOperation.class);
+ public static final String PN_MEMBERS = ":members";
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ List<String> auths = new ArrayList<>();
+ Optional.ofNullable(request.getParameterValues(PN_MEMBERS)).ifPresent(p -> {
+ auths.addAll(Arrays.asList(p));
+ });
+
+ AuthorizableWrapper groupWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+ if (!groupWrapper.getAuthorizable().isGroup()) {
+ throw new RepositoryException("Provided authorizable is not a group");
+ }
+ Group group = (Group) groupWrapper.getAuthorizable();
+ response.setPath(group.getPath());
+ changes.add(Modification.onModified(group.getPath()));
+
+ group.getDeclaredMembers().forEachRemaining(member -> {
+ try {
+ if (!auths.contains(member.getPath())) {
+ log.debug("Removing member {} from {}", member, group);
+ group.removeMember(member);
+ changes.add(Modification.onModified(member.getPath()));
+ } else {
+ auths.remove(member.getPath());
+ }
+ } catch (RepositoryException e) {
+ log.warn("Failed to remove members", e);
+ }
+ });
+
+ for (String path : auths) {
+ Resource resource = request.getResourceResolver().getResource(path);
+ if (resource == null) {
+ throw new RepositoryException("Failed to resolve authorizable at " + path);
+ }
+ Authorizable authorizable = resource.adaptTo(AuthorizableWrapper.class).getAuthorizable();
+ group.addMember(authorizable);
+ changes.add(Modification.onModified(authorizable.getPath()));
+ log.debug("Adding member {} to {}", authorizable, group);
+ }
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.onModified(group.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to update members", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembershipOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembershipOperation.java
new file mode 100644
index 0000000..36e49c1
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/MembershipOperation.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>MembersOperation</code> will update the membership of a group.
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=membership")
+public class MembershipOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(MembershipOperation.class);
+ public static final String PN_MEMBERSHIP = ":membership";
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ List<String> groups = new ArrayList<>();
+ Optional.ofNullable(request.getParameterValues(PN_MEMBERSHIP)).ifPresent(p -> {
+ groups.addAll(Arrays.asList(p));
+ });
+
+ AuthorizableWrapper authWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+
+ Authorizable auth = authWrapper.getAuthorizable();
+ response.setPath(auth.getPath());
+ changes.add(Modification.onModified(auth.getPath()));
+
+ auth.declaredMemberOf().forEachRemaining(group -> {
+ try {
+ if (!groups.contains(group.getPath())) {
+ log.debug("Removing member {} from {}", auth, group);
+ group.removeMember(auth);
+ changes.add(Modification.onModified(group.getPath()));
+ } else {
+ groups.remove(group.getPath());
+ }
+ } catch (RepositoryException e) {
+ log.warn("Failed to remove members", e);
+ }
+ });
+
+ for (String path : groups) {
+ Resource resource = request.getResourceResolver().getResource(path);
+ if (resource == null) {
+ throw new RepositoryException("Failed to resolve authorizable at " + path);
+ }
+ Group group = (Group) resource.adaptTo(AuthorizableWrapper.class).getAuthorizable();
+ group.addMember(auth);
+ changes.add(Modification.onModified(group.getPath()));
+ log.debug("Adding member {} to {}", auth, group);
+ }
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.onModified(auth.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to update membership", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/TouchLastModifiedPostOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/TouchLastModifiedPostOperation.java
index caf940e..7aabdb4 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/operations/TouchLastModifiedPostOperation.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/TouchLastModifiedPostOperation.java
@@ -69,7 +69,7 @@ public class TouchLastModifiedPostOperation implements SlingPostProcessor {
Set<String> parentPaths = new HashSet<>();
List<Resource> resources = paths.stream().map(p -> request.getResourceResolver().getResource(p))
.map(CMSUtils::findPublishableParent).filter(p -> {
- if (parentPaths.contains(p.getPath())) {
+ if (p == null || parentPaths.contains(p.getPath())) {
return false;
} else {
parentPaths.add(p.getPath());
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java b/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java
new file mode 100644
index 0000000..8f91cf9
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperation.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.jsoup.helper.StringUtil;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>UpdateStatusOperation</code> will update the status of a user.
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+ + "=updatestatus")
+public class UpdateStatusOperation implements PostOperation {
+
+ private static final Logger log = LoggerFactory.getLogger(UpdateStatusOperation.class);
+
+ public static final String PN_REASON = ":reason";
+
+ @Override
+ public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+ final List<Modification> changes = new ArrayList<>();
+ try {
+
+ String reason = request.getParameter(PN_REASON);
+
+ AuthorizableWrapper authWrapper = request.getResource().adaptTo(AuthorizableWrapper.class);
+
+ if (authWrapper.getAuthorizable().isGroup()) {
+ throw new RepositoryException("Authorizable is not a user");
+ }
+
+ User user = (User) authWrapper.getAuthorizable();
+ user.disable(StringUtil.isBlank(reason) ? null : reason);
+
+ // invoke processors
+ if (processors != null) {
+ for (SlingPostProcessor processor : processors) {
+ processor.process(request, changes);
+ }
+ }
+
+ request.getResourceResolver().commit();
+
+ response.setPath(user.getPath());
+ response.onCreated(user.getPath());
+ } catch (Exception e) {
+ log.warn("Failed to update user status", e);
+ response.setError(e);
+ }
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSContextHelper.java b/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSContextHelper.java
deleted file mode 100644
index c77cdfa..0000000
--- a/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSContextHelper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.cms.core.helpers;
-
-import java.io.InputStream;
-
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.testing.mock.sling.junit.SlingContext;
-
-import com.google.common.base.Function;
-
-public class SlingCMSContextHelper {
-
- public static final void initContext(SlingContext context) {
- context.addModelsForPackage("org.apache.sling.cms.core.internal.models");
- context.addModelsForPackage("org.apache.sling.cms.core.models");
-
- context.load().json("/content.json", "/content");
- context.load().binaryResource("/apache.png", "/content/apache/sling-apache-org/index/apache.png/jcr:content");
-
- context.registerAdapter(Resource.class, InputStream.class, new Function<Resource, InputStream>() {
- public InputStream apply(Resource input) {
- return input.getValueMap().get("jcr:content/jcr:data", InputStream.class);
- }
- });
-
- }
-}
diff --git a/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSTestHelper.java b/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSTestHelper.java
new file mode 100644
index 0000000..80476bf
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/helpers/SlingCMSTestHelper.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.helpers;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.ResourceTree;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.mockito.Mockito;
+
+import com.google.common.base.Function;
+
+public class SlingCMSTestHelper {
+
+ public static final void initContext(SlingContext context) {
+ context.addModelsForPackage("org.apache.sling.cms.core.internal.models");
+ context.addModelsForPackage("org.apache.sling.cms.core.models");
+
+ context.load().json("/content.json", "/content");
+ context.load().binaryResource("/apache.png", "/content/apache/sling-apache-org/index/apache.png/jcr:content");
+
+ context.registerAdapter(Resource.class, InputStream.class, new Function<Resource, InputStream>() {
+ public InputStream apply(Resource input) {
+ return input.getValueMap().get("jcr:content/jcr:data", InputStream.class);
+ }
+ });
+ }
+
+ public static final Map<String, Authorizable> AUTH_REGISTRY = new HashMap<>();
+
+ public static final void initAuthContext(SlingContext context)
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ initContext(context);
+ context.load().json("/auth.json", "/home");
+
+ JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+
+ AUTH_REGISTRY.clear();
+
+ ResourceTree.stream(context.resourceResolver().getResource("/home/users"), "rep:User").forEach(u -> {
+
+ User user = Mockito.mock(User.class);
+ try {
+ Mockito.when(user.getID()).thenReturn(u.getResource().getValueMap().get("rep:principalName", ""));
+ Mockito.when(user.getPath()).thenReturn(u.getResource().getPath());
+ Mockito.when(user.declaredMemberOf()).thenAnswer((ans) -> {
+ final List<Group> groups = new ArrayList<>();
+ AUTH_REGISTRY.values().forEach(a -> {
+ if (a instanceof Group) {
+ try {
+ ((Group) a).getDeclaredMembers().forEachRemaining(m -> {
+ if (m == user) {
+ groups.add((Group) a);
+ }
+ });
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ return groups.iterator();
+ });
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ AUTH_REGISTRY.put(u.getResource().getPath(), user);
+ });
+
+ ResourceTree.stream(context.resourceResolver().getResource("/home/groups/sling-cms"), "rep:Group")
+ .forEach(g -> {
+
+ Group group = Mockito.mock(Group.class);
+ try {
+ Mockito.when(group.getID())
+ .thenReturn(g.getResource().getValueMap().get("rep:principalName", ""));
+ Mockito.when(group.getPath()).thenReturn(g.getResource().getPath());
+ Mockito.when(group.isGroup()).thenReturn(true);
+ Mockito.when(group.declaredMemberOf()).thenAnswer((ans) -> {
+ final List<Group> groups = new ArrayList<>();
+ AUTH_REGISTRY.values().forEach(a -> {
+ if (a instanceof Group) {
+ try {
+ ((Group) a).getDeclaredMembers().forEachRemaining(m -> {
+ if (m == group) {
+ groups.add((Group) a);
+ }
+ });
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ return groups.iterator();
+ });
+ Mockito.when(group.getDeclaredMembers()).thenAnswer((ans) -> {
+ final List<Authorizable> members = new ArrayList<>();
+ for (String member : g.getResource().getValueMap().get("members", new String[0])) {
+ if (AUTH_REGISTRY.containsKey(member)) {
+ members.add(AUTH_REGISTRY.get(member));
+ }
+ }
+
+ return members.iterator();
+ });
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+
+ AUTH_REGISTRY.put(g.getResource().getPath(), group);
+ });
+
+ UserManager userManager = Mockito.mock(UserManager.class);
+ Mockito.when(session.getUserManager()).thenReturn(userManager);
+ Mockito.when(userManager.getAuthorizableByPath(Mockito.anyString())).thenAnswer((ans) -> {
+ String path = ans.getArgument(0);
+ return AUTH_REGISTRY.get(path);
+ });
+ context.registerAdapter(ResourceResolver.class, Session.class, session);
+ }
+
+ public static final <I> Stream<I> toStream(Iterator<I> iterator) {
+ Iterable<I> iterable = () -> iterator;
+ return StreamSupport.stream(iterable.spliterator(), false);
+ }
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImplTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImplTest.java
new file mode 100644
index 0000000..0c9fe89
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/models/AuthorizableWrapperImplTest.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.models;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.stream.Collectors;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.AuthorizableWrapper;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class AuthorizableWrapperImplTest {
+
+ private static final String ADMIN_PATH = "/home/users/admin";
+ private static final String GROUP_PATH = "/home/groups/group1";
+ private static final String GROUP2_PATH = "/home/groups/group2";
+ private static final String USER_PATH = "/home/users/user1";
+ private static final String THROWY_PATH = "/home/users/throwy";
+ private Resource adminResource;
+ private Resource contentResource;
+ private Group group;
+ private Group group2;
+ private Resource groupResource;
+ private User user;
+ private Resource userResource;
+ private Authorizable throwy;
+ private Resource throwyResource;
+ private Resource group2Resource;
+
+ @Before
+ public void init() throws AccessDeniedException, AuthorizableExistsException,
+ UnsupportedRepositoryOperationException, RepositoryException {
+
+ userResource = Mockito.mock(Resource.class);
+ Mockito.when(userResource.getPath()).thenReturn(USER_PATH);
+ groupResource = Mockito.mock(Resource.class);
+ Mockito.when(groupResource.getPath()).thenReturn(GROUP_PATH);
+ contentResource = Mockito.mock(Resource.class);
+ Mockito.when(contentResource.getPath()).thenReturn("/content");
+ adminResource = Mockito.mock(Resource.class);
+ Mockito.when(adminResource.getPath()).thenReturn(ADMIN_PATH);
+ throwyResource = Mockito.mock(Resource.class);
+ Mockito.when(throwyResource.getPath()).thenReturn(THROWY_PATH);
+ group2Resource = Mockito.mock(Resource.class);
+ Mockito.when(group2Resource.getPath()).thenReturn(GROUP2_PATH);
+
+ ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+ Mockito.when(userResource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(groupResource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(contentResource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(adminResource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(throwyResource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(group2Resource.getResourceResolver()).thenReturn(resolver);
+ Mockito.when(resolver.getUserID()).thenReturn("123");
+
+ JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+ Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+ UserManager userManager = Mockito.mock(UserManager.class);
+ Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+ throwy = Mockito.mock(Authorizable.class, inv -> {
+ throw new RepositoryException("YAY JCR!");
+ });
+ Mockito.when(userManager.getAuthorizableByPath(THROWY_PATH)).thenReturn(throwy);
+
+ User adminUser = Mockito.mock(User.class);
+ Mockito.when(adminUser.getID()).thenReturn("admin");
+
+ Mockito.when(userManager.getAuthorizableByPath(ADMIN_PATH)).thenReturn(adminUser);
+
+ user = Mockito.mock(User.class);
+ Mockito.when(user.getID()).thenReturn("123");
+ Mockito.when(userManager.getAuthorizableByPath(USER_PATH)).thenReturn(user);
+ Mockito.when(userManager.getAuthorizable("123")).thenReturn(user);
+
+ group = Mockito.mock(Group.class);
+ Mockito.when(group.getID()).thenReturn("456");
+ Mockito.when(group.isGroup()).thenReturn(true);
+ Mockito.when(group.getMembers()).thenReturn(Collections.singletonList((Authorizable) user).iterator());
+ Mockito.when(group.getPrincipal()).thenReturn(new Principal() {
+ @Override
+ public String getName() {
+ return "456";
+ }
+ });
+ Mockito.when(user.memberOf()).thenReturn(Collections.singletonList(group).iterator());
+ Mockito.when(userManager.getAuthorizableByPath(GROUP_PATH)).thenReturn(group);
+
+ group2 = Mockito.mock(Group.class);
+ Mockito.when(group2.isGroup()).thenReturn(true);
+ Mockito.when(adminUser.declaredMemberOf()).thenReturn(Collections.singletonList(group2).iterator());
+ Mockito.when(group2.getDeclaredMembers())
+ .thenReturn(Collections.singletonList((Authorizable) adminUser).iterator());
+ Mockito.when(userManager.getAuthorizableByPath(GROUP2_PATH)).thenReturn(group2);
+
+ }
+
+ @Test
+ public void testResourceResolver()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource.getResourceResolver());
+ assertNotNull(authWrapper.getAuthorizable());
+ assertEquals(user, authWrapper.getAuthorizable());
+ }
+
+ @Test
+ public void testGetAuthorizable()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+ assertNotNull(authWrapper);
+ assertEquals(user, authWrapper.getAuthorizable());
+
+ authWrapper = new AuthorizableWrapperImpl(groupResource);
+ assertNotNull(authWrapper);
+ assertEquals(group, authWrapper.getAuthorizable());
+
+ try {
+ authWrapper = new AuthorizableWrapperImpl(contentResource);
+ fail();
+ } catch (RepositoryException re) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetDeclaredMembers()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(group2Resource);
+
+ Iterator<Authorizable> members = authWrapper.getDeclaredMembers();
+ assertTrue(members.hasNext());
+ assertEquals(1, SlingCMSTestHelper.toStream(members).count());
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.getDeclaredMembers().hasNext());
+
+ authWrapper = new AuthorizableWrapperImpl(adminResource);
+ assertFalse(authWrapper.getDeclaredMembers().hasNext());
+ }
+
+ @Test
+ public void testGetMembers()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(groupResource);
+
+ Iterator<Authorizable> members = authWrapper.getMembers();
+ assertTrue(members.hasNext());
+ assertEquals(1, SlingCMSTestHelper.toStream(members).count());
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.getMembers().hasNext());
+
+ authWrapper = new AuthorizableWrapperImpl(userResource);
+ assertFalse(authWrapper.getMembers().hasNext());
+ }
+
+ @Test
+ public void testGetGroupNames()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+
+ assertEquals(Collections.singletonList("456"),
+ SlingCMSTestHelper.toStream(authWrapper.getGroupNames()).collect(Collectors.toList()));
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.getGroupNames().hasNext());
+ }
+
+ @Test
+ public void testGetId() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+
+ assertEquals("123", authWrapper.getId());
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertNull(authWrapper.getId());
+ }
+
+ @Test
+ public void testIsAdmin()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+
+ assertFalse(authWrapper.isAdministrator());
+
+ authWrapper = new AuthorizableWrapperImpl(adminResource);
+ assertTrue(authWrapper.isAdministrator());
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.isAdministrator());
+ }
+
+ @Test
+ public void testIsMember()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+ assertTrue(authWrapper.isMember("456"));
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.isMember("456"));
+
+ }
+
+ @Test
+ public void testDeclaredMembership()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(adminResource);
+ assertTrue(SlingCMSTestHelper.toStream(authWrapper.getDeclaredMembership()).anyMatch(g -> g == group2));
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.getDeclaredMembership().hasNext());
+ }
+
+ @Test
+ public void testMembership()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+ assertTrue(SlingCMSTestHelper.toStream(authWrapper.getMembership()).anyMatch(g -> g == group));
+
+ authWrapper = new AuthorizableWrapperImpl(throwyResource);
+ assertFalse(authWrapper.getMembership().hasNext());
+ }
+
+ @Test
+ public void testNonMember()
+ throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ AuthorizableWrapper authWrapper = new AuthorizableWrapperImpl(userResource);
+ assertFalse(SlingCMSTestHelper.toStream(authWrapper.getMembership()).anyMatch(g -> g == group2));
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperationTest.java
new file mode 100644
index 0000000..312b2b8
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/ChangePasswordOperationTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class ChangePasswordOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+
+ private User user;
+
+ public static final String USER_PATH = "/home/users/test";
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ user = (User) SlingCMSTestHelper.AUTH_REGISTRY.get(USER_PATH);
+
+ }
+
+ @Test
+ public void testChangePassword() throws RepositoryException {
+ ChangePasswordOperation operation = new ChangePasswordOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource(USER_PATH);
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(CreateUserOperation.PN_PASSWORD, "test1").build());
+
+ operation.run(context.request(), response, new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ Mockito.verify(user).changePassword("test1", "test2");
+
+ }
+
+ @Test
+ public void testGroup() throws RepositoryException {
+ ChangePasswordOperation operation = new ChangePasswordOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/groups/sling-cms/authors");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(CreateUserOperation.PN_PASSWORD, "test1").build());
+
+ operation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperationTest.java
new file mode 100644
index 0000000..32c9f6f
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateGroupOperationTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.security.Principal;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class CreateGroupOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+
+ private Group group;
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ group = null;
+ UserManager userManager = CommonUtils.getUserManager(context.resourceResolver());
+ Mockito.when(userManager.createGroup(Mockito.anyString(), Mockito.any(), Mockito.anyString()))
+ .thenAnswer((ans) -> {
+ Group group = Mockito.mock(Group.class);
+ Mockito.when(group.getPath()).thenReturn("/home/groups/tests");
+ this.group = group;
+ return group;
+ });
+ }
+
+ @Test
+ public void testCreate() throws RepositoryException {
+ CreateGroupOperation createGroupOperation = new CreateGroupOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(SlingPostConstants.RP_NODE_NAME, "test5").build());
+
+ createGroupOperation.run(context.request(), response,
+ new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ assertNotNull(group);
+ assertEquals("/home/groups/tests", group.getPath());
+
+ }
+
+ @Test
+ public void testExisting() throws RepositoryException {
+ CreateGroupOperation createGroupOperation = new CreateGroupOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(SlingPostConstants.RP_NODE_NAME, "test5").build());
+
+ UserManager userManager = CommonUtils.getUserManager(context.resourceResolver());
+ Mockito.when(userManager.getAuthorizable(Mockito.any(Principal.class))).thenReturn(Mockito.mock(Group.class));
+
+ createGroupOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateUserOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateUserOperationTest.java
new file mode 100644
index 0000000..5af3fdd
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/CreateUserOperationTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.security.Principal;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class CreateUserOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+
+ private User user;
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ user = null;
+ UserManager userManager = CommonUtils.getUserManager(context.resourceResolver());
+ Mockito.when(
+ userManager.createUser(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.anyString()))
+ .thenAnswer((ans) -> {
+ User user = Mockito.mock(User.class);
+ Mockito.when(user.getPath()).thenReturn("/home/users/tests");
+ this.user = user;
+ return user;
+ });
+ }
+
+ @Test
+ public void testCreate() throws RepositoryException {
+ CreateUserOperation createUserOperation = new CreateUserOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(SlingPostConstants.RP_NODE_NAME, "tests").put(CreateUserOperation.PN_PASSWORD, "test5").build());
+
+ createUserOperation.run(context.request(), response,
+ new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ assertNotNull(user);
+ assertEquals("/home/users/tests", user.getPath());
+
+ }
+
+ @Test
+ public void testExisting() throws RepositoryException {
+ CreateUserOperation createUserOperation = new CreateUserOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(SlingPostConstants.RP_NODE_NAME, "test5").build());
+
+ UserManager userManager = CommonUtils.getUserManager(context.resourceResolver());
+ Mockito.when(userManager.getAuthorizable(Mockito.any(Principal.class))).thenReturn(Mockito.mock(Group.class));
+
+ createUserOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembersOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembersOperationTest.java
new file mode 100644
index 0000000..518d525
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembersOperationTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class MembersOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+ private ArrayList<String> added;
+ private ArrayList<String> removed;
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ Group group = (Group) SlingCMSTestHelper.AUTH_REGISTRY.get("/home/groups/sling-cms/authors");
+
+ added = new ArrayList<>();
+ removed = new ArrayList<>();
+
+ Mockito.when(group.addMember(Mockito.any())).then((ans) -> {
+ added.add(ans.getArgument(0, Authorizable.class).getPath());
+ return true;
+ });
+
+ Mockito.when(group.removeMember(Mockito.any())).then((ans) -> {
+ removed.add(ans.getArgument(0, Authorizable.class).getPath());
+ return true;
+ });
+ }
+
+ @Test
+ public void testModifyOperation() throws RepositoryException {
+ MembersOperation membersOperation = new MembersOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/groups/sling-cms/authors");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(":members", new String[] { "/home/users/test2", "/home/users/test3" }).build());
+
+ membersOperation.run(context.request(), response, null);
+
+ assertNull(response.getError());
+
+ assertEquals("/home/groups/sling-cms/authors", response.getPath());
+
+ assertTrue(added.size() == 1);
+ assertEquals("/home/users/test2", added.get(0));
+
+ assertTrue(removed.size() == 1);
+ assertEquals("/home/users/test", removed.get(0));
+ }
+
+
+ @Test
+ public void testNotGroup() throws RepositoryException {
+ MembersOperation membersOperation = new MembersOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users/test2");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(":members", new String[] { "/home/users/test2", "/home/users/test3" }).build());
+
+ membersOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+
+ @Test
+ public void testInvalidPath() throws RepositoryException {
+ MembersOperation membersOperation = new MembersOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/groups/sling-cms/authors");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(":members", new String[] { "/home/users/test2", "/home/users/test4" }).build());
+
+ membersOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembershipOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembershipOperationTest.java
new file mode 100644
index 0000000..a48188f
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/MembershipOperationTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class MembershipOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+ private ArrayList<String> added;
+ private ArrayList<String> removed;
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ Group group = (Group) SlingCMSTestHelper.AUTH_REGISTRY.get("/home/groups/sling-cms/authors");
+
+ added = new ArrayList<>();
+ removed = new ArrayList<>();
+
+ Mockito.when(group.addMember(Mockito.any())).then((ans) -> {
+ added.add(ans.getArgument(0, Authorizable.class).getPath());
+ return true;
+ });
+
+ Mockito.when(group.removeMember(Mockito.any())).then((ans) -> {
+ removed.add(ans.getArgument(0, Authorizable.class).getPath());
+ return true;
+ });
+ }
+
+ @Test
+ public void testModifyOperation() throws RepositoryException {
+ MembershipOperation membersOperation = new MembershipOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users/test2");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(MembershipOperation.PN_MEMBERSHIP, new String[] { "/home/groups/sling-cms/authors" }).build());
+
+ membersOperation.run(context.request(), response,
+ new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ assertEquals("/home/users/test2", response.getPath());
+
+ assertTrue(added.size() == 1);
+ assertEquals("/home/users/test2", added.get(0));
+
+ assertTrue(removed.size() == 0);
+
+ }
+
+ @Test
+ public void testInvalidPath() throws RepositoryException {
+ MembershipOperation membersOperation = new MembershipOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/content");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(":members", new String[] { "/home/groups/sling-cms/authors" }).build());
+
+ membersOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+ @Test
+ public void testInvalidGroup() throws RepositoryException {
+ MembershipOperation membersOperation = new MembershipOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users/test");
+ context.request().setParameterMap(ImmutableMap.<String, Object>builder()
+ .put(MembershipOperation.PN_MEMBERSHIP, new String[] { "/home/groups/sling-cms/authors32" }).build());
+
+ membersOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperationTest.java
new file mode 100644
index 0000000..0489b74
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/operations/UpdateStatusOperationTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.operations;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.security.Principal;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.cms.core.internal.CommonUtils;
+import org.apache.sling.servlets.post.JSONResponse;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+public class UpdateStatusOperationTest {
+
+ @Rule
+ public SlingContext context = new SlingContext();
+
+ private User user;
+
+ @Before
+ public void init() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+ SlingCMSTestHelper.initAuthContext(context);
+
+ user = (User) SlingCMSTestHelper.AUTH_REGISTRY.get("/home/users/test");
+ }
+
+ @Test
+ public void testEnable() throws RepositoryException {
+ UpdateStatusOperation operation = new UpdateStatusOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/users/test");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(UpdateStatusOperation.PN_REASON, "").build());
+
+ operation.run(context.request(), response, new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ Mockito.verify(user).disable(null);
+
+ }
+
+ @Test
+ public void testDisable() throws RepositoryException {
+ UpdateStatusOperation operation = new UpdateStatusOperation();
+ PostResponse response = new JSONResponse();
+
+ String reason = "A valid reason";
+ context.currentResource("/home/users/test");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(UpdateStatusOperation.PN_REASON, reason).build());
+
+ operation.run(context.request(), response, new SlingPostProcessor[] { Mockito.mock(SlingPostProcessor.class) });
+
+ assertNull(response.getError());
+
+ Mockito.verify(user).disable(reason);
+
+ }
+
+ @Test
+ public void testGroup() throws RepositoryException {
+ CreateUserOperation createUserOperation = new CreateUserOperation();
+ PostResponse response = new JSONResponse();
+
+ context.currentResource("/home/groups/sling-cms/authors");
+ context.request().setParameterMap(
+ ImmutableMap.<String, Object>builder().put(UpdateStatusOperation.PN_REASON, "").build());
+
+ UserManager userManager = CommonUtils.getUserManager(context.resourceResolver());
+ Mockito.when(userManager.getAuthorizable(Mockito.any(Principal.class))).thenReturn(Mockito.mock(Group.class));
+
+ createUserOperation.run(context.request(), response, null);
+
+ assertNotNull(response.getError());
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/servlets/DownloadFileServletTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/servlets/DownloadFileServletTest.java
index 839f227..01279dd 100644
--- a/core/src/test/java/org/apache/sling/cms/core/internal/servlets/DownloadFileServletTest.java
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/servlets/DownloadFileServletTest.java
@@ -23,7 +23,7 @@ import java.io.IOException;
import javax.servlet.ServletException;
-import org.apache.sling.cms.core.helpers.SlingCMSContextHelper;
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
import org.apache.sling.testing.mock.sling.junit.SlingContext;
import org.junit.Before;
import org.junit.Rule;
@@ -36,7 +36,7 @@ public class DownloadFileServletTest {
@Before
public void init() {
- SlingCMSContextHelper.initContext(context);
+ SlingCMSTestHelper.initContext(context);
}
diff --git a/core/src/test/resources/auth.json b/core/src/test/resources/auth.json
new file mode 100644
index 0000000..47d8eab
--- /dev/null
+++ b/core/src/test/resources/auth.json
@@ -0,0 +1,142 @@
+{
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "users": {
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "user1": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "user1",
+ "rep:authorizableId": "user1"
+ },
+ "F": {
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "FnzSMsZTdPghKjb4jwNML": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "admin",
+ "rep:authorizableId": "admin"
+ }
+ },
+ "w": {
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "wUbBUsoC475uXtLGfEwpG": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "anonymous",
+ "rep:authorizableId": "anonymous"
+ }
+ },
+ "test": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "test",
+ "rep:authorizableId": "test"
+ },
+ "test2": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "test2",
+ "rep:authorizableId": "test2"
+ },
+ "test3": {
+ "jcr:primaryType": "rep:User",
+ "rep:principalName": "test3",
+ "rep:authorizableId": "test3"
+ }
+ },
+ "groups": {
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "sling-cms": {
+ "jcr:primaryType": "rep:AuthorizableFolder",
+ "authors": {
+ "jcr:primaryType": "rep:Group",
+ "jcr:mixinTypes": [
+ "rep:AccessControllable"
+ ],
+ "rep:principalName": "authors",
+ "rep:authorizableId": "authors",
+ "rep:policy": {
+ "jcr:primaryType": "rep:ACL",
+ "allow": {
+ "jcr:primaryType": "rep:GrantACE",
+ "rep:principalName": "authors",
+ "rep:privileges": [
+ "jcr:read"
+ ]
+ }
+ },
+ "members": [
+ "/home/users/test",
+ "/home/users/test3"
+ ]
+ },
+ "job-users": {
+ "jcr:primaryType": "rep:Group",
+ "jcr:mixinTypes": [
+ "rep:AccessControllable"
+ ],
+ "rep:principalName": "job-users",
+ "rep:authorizableId": "job-users",
+ "rep:policy": {
+ "jcr:primaryType": "rep:ACL",
+ "allow": {
+ "jcr:primaryType": "rep:GrantACE",
+ "rep:principalName": "job-users",
+ "rep:privileges": [
+ "jcr:read"
+ ]
+ }
+ }
+ },
+ "taxonomy-users": {
+ "jcr:primaryType": "rep:Group",
+ "jcr:mixinTypes": [
+ "rep:AccessControllable"
+ ],
+ "rep:principalName": "taxonomy-users",
+ "rep:authorizableId": "taxonomy-users",
+ "rep:policy": {
+ "jcr:primaryType": "rep:ACL",
+ "allow": {
+ "jcr:primaryType": "rep:GrantACE",
+ "rep:principalName": "taxonomy-users",
+ "rep:privileges": [
+ "jcr:read"
+ ]
+ }
+ }
+ },
+ "administrators": {
+ "jcr:primaryType": "rep:Group",
+ "jcr:mixinTypes": [
+ "rep:AccessControllable"
+ ],
+ "rep:principalName": "administrators",
+ "rep:authorizableId": "administrators",
+ "rep:policy": {
+ "jcr:primaryType": "rep:ACL",
+ "allow": {
+ "jcr:primaryType": "rep:GrantACE",
+ "rep:principalName": "administrators",
+ "rep:privileges": [
+ "jcr:read"
+ ]
+ }
+ }
+ },
+ "ugc-users": {
+ "jcr:primaryType": "rep:Group",
+ "jcr:mixinTypes": [
+ "rep:AccessControllable"
+ ],
+ "rep:principalName": "ugc-users",
+ "rep:authorizableId": "ugc-users",
+ "rep:policy": {
+ "jcr:primaryType": "rep:ACL",
+ "allow": {
+ "jcr:primaryType": "rep:GrantACE",
+ "rep:principalName": "ugc-users",
+ "rep:privileges": [
+ "jcr:read"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/admin-tools.md b/docs/admin-tools.md
index 24ffa8d..46661fa 100644
--- a/docs/admin-tools.md
+++ b/docs/admin-tools.md
@@ -60,4 +60,4 @@ This console is accessible from *Tools > System Console* or at [http://localhost
![users and Groups](img/users-groups.png)
-This tool is accessible from *Tools > Users & Groups* or at [http://localhost:8080/bin/users.html](http://localhost:8080/bin/users.html). It allows administrators to create and manage users and groups within Sling CMS. Permissions are managed in the Node Browser
\ No newline at end of file
+This tool is accessible from *Tools > Users & Groups* or at [http://localhost:8080/cms/auth/list.html/home](http://localhost:8080/cms/auth/list.html/home). It allows administrators to create and manage users and groups within Sling CMS. Permissions are managed in the Node Browser
\ No newline at end of file
diff --git a/docs/img/users-groups.png b/docs/img/users-groups.png
index b47296f..72c66ae 100644
Binary files a/docs/img/users-groups.png and b/docs/img/users-groups.png differ
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/staticnav/staticnav.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/staticnav/staticnav.jsp
index 0a19bcf..80c1bfd 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/staticnav/staticnav.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/staticnav/staticnav.jsp
@@ -32,7 +32,7 @@
</c:if>
</c:forEach>
</c:forEach>
- <sling:adaptTo var="currentUser" adaptable="${slingRequest.resourceResolver}" adaptTo="org.apache.sling.cms.CurrentUser" />
+ <sling:adaptTo var="currentUser" adaptable="${slingRequest.resourceResolver}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" />
<ul id="${fn:replace(properties.title,' ','-')}-nav" class="menu-list ${hidden}">
<c:forEach var="item" items="${sling:listChildren(sling:getRelativeResource(resource,'links'))}">
<c:set var="selected" value="" />
@@ -44,11 +44,11 @@
<c:set var="selected" value="is-selected" />
</c:if>
</c:forEach>
- <c:set var="enabled" value="${true}" />
- <c:if test="${not empty item.valueMap.enabledGroups && currentUser.id != 'admin'}">
+ <c:set var="enabled" value="${currentUser.administrator || empty item.valueMap.enabledGroups}" />
+ <c:if test="${not empty item.valueMap.enabledGroups && !currentUser.administrator}">
<c:set var="enabled" value="${false}" />
<c:forEach var="group" items="${item.valueMap.enabledGroups}">
- <c:forEach var="userGroup" items="${currentUser.groups}">
+ <c:forEach var="userGroup" items="${currentUser.groupNames}">
<c:if test="${group == userGroup}">
<c:set var="enabled" value="${true}" />
</c:if>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members.json
new file mode 100644
index 0000000..8fb7b25
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members.json
@@ -0,0 +1,6 @@
+{
+ "jcr:primaryType": "sling:Component",
+ "sling:resourceSuperType" : "sling-cms/components/editor/fields/labelfield",
+ "componentType": "SlingCMS-FieldConfig",
+ "jcr:title": "Sling CMS - Members"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/options.jsp
similarity index 62%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/options.jsp
index 6ade9a5..2541bef 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/options.jsp
@@ -16,12 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/ --%>
- <%@include file="/libs/sling-cms/global.jsp"%>
-<option value="">Select Locale</option>
-<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
-</c:forEach>
\ No newline at end of file
+<%@include file="/libs/sling-cms/global.jsp"%>
+<datalist id="labelfield-${fn:replace(resource.name,':','-')}">
+ <c:set var="query" value="SELECT * FROM [rep:Authorizable] WHERE ISDESCENDANTNODE([/home]) ORDER BY [rep:principalName]" />
+ <c:forEach var="auth" items="${sling:findResources(resourceResolver,query,'JCR-SQL2')}">
+ <option value="${sling:encode(auth.path,'HTML_ATTR')}">${sling:encode(auth.valueMap['rep:principalName'],'HTML')}</option>
+ </c:forEach>
+</datalist>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/values.jsp
similarity index 64%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/values.jsp
index 6ade9a5..ddce705 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/members/values.jsp
@@ -17,11 +17,13 @@
* under the License.
*/ --%>
<%@include file="/libs/sling-cms/global.jsp"%>
-<option value="">Select Locale</option>
-<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
+<sling:adaptTo var="auth" adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" />
+<c:forEach var="member" items="${auth.declaredMembers}">
+ <a class="button labelfield__item">
+ <input type="hidden" name="${properties.name}" value="${member.path}" />
+ <span class="labelfield__title">
+ ${sling:encode(member,'HTML')}
+ </span>
+ <span class="jam jam-close"></span>
+ </a>
</c:forEach>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership.json
new file mode 100644
index 0000000..f9a1868
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership.json
@@ -0,0 +1,6 @@
+{
+ "jcr:primaryType": "sling:Component",
+ "sling:resourceSuperType" : "sling-cms/components/editor/fields/labelfield",
+ "componentType": "SlingCMS-FieldConfig",
+ "jcr:title": "Sling CMS - Membership"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/options.jsp
similarity index 62%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/options.jsp
index 6ade9a5..3a709b3 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/options.jsp
@@ -16,12 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/ --%>
- <%@include file="/libs/sling-cms/global.jsp"%>
-<option value="">Select Locale</option>
-<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
-</c:forEach>
\ No newline at end of file
+<%@include file="/libs/sling-cms/global.jsp"%>
+<datalist id="labelfield-${fn:replace(resource.name,':','-')}">
+ <c:set var="query" value="SELECT * FROM [rep:Group] WHERE ISDESCENDANTNODE([/home/groups]) ORDER BY [rep:principalName]" />
+ <c:forEach var="group" items="${sling:findResources(resourceResolver,query,'JCR-SQL2')}">
+ <option value="${sling:encode(group.path,'HTML_ATTR')}">${sling:encode(group.valueMap['rep:principalName'],'HTML')}</option>
+ </c:forEach>
+</datalist>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/values.jsp
similarity index 64%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/values.jsp
index 6ade9a5..7ccba05 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/membership/values.jsp
@@ -17,11 +17,13 @@
* under the License.
*/ --%>
<%@include file="/libs/sling-cms/global.jsp"%>
-<option value="">Select Locale</option>
-<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
+<sling:adaptTo var="auth" adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" />
+<c:forEach var="group" items="${auth.declaredMembership}">
+ <a class="button labelfield__item">
+ <input type="hidden" name="${properties.name}" value="${group.path}" />
+ <span class="labelfield__title">
+ ${sling:encode(group,'HTML')}
+ </span>
+ <span class="jam jam-close"></span>
+ </a>
</c:forEach>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/status/status.jsp
similarity index 51%
copy from ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
copy to ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/status/status.jsp
index 6ade9a5..1d68d6b 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/auth/status/status.jsp
@@ -16,12 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/ --%>
- <%@include file="/libs/sling-cms/global.jsp"%>
-<option value="">Select Locale</option>
-<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
-</c:forEach>
\ No newline at end of file
+<%@include file="/libs/sling-cms/global.jsp"%>
+<c:set var="user" value="${sling:adaptTo(slingRequest.requestPathInfo.suffixResource,'org.apache.sling.cms.AuthorizableWrapper').authorizable }" />
+<c:choose>
+ <c:when test="${user.disabled}">
+ <dl>
+ <dt>Status</dt>
+ <dd>Disabled</dd>
+ <dt>Reason</dt>
+ <dd><sling:encode value="${user.disabledReason}" mode="HTML" /></dd>
+ </dl>
+ <input type="hidden" name=":reason" value="" />
+ </c:when>
+ <c:otherwise>
+ <dl>
+ <dt>Status</dt>
+ <dd>Enabled</dd>
+ <dt><label for=":reason">Disable Reason</label></dt>
+ <dd><input type="text" class="input" name=":reason" value="" /></dd>
+ </dl>
+ </c:otherwise>
+</c:choose>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
index 6ade9a5..8e813cc 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/scripts/localeOptions.jsp
@@ -19,9 +19,9 @@
<%@include file="/libs/sling-cms/global.jsp"%>
<option value="">Select Locale</option>
<c:forEach var="locale" items="${sling:adaptTo(slingRequest,'org.apache.sling.cms.core.models.LocaleList').locales}">
- <c:if test="${not empty locale.language}">
- <option value="${locale}" ${locale == editProperties['jcr:language'] ? 'selected' : ''}>
- ${locale.displayLanguage} ${locale.displayCountry} (${locale})
- </option>
- </c:if>
+ <c:if test="${not empty locale.language}">
+ <option value="${locale}" ${locale == editProperties[properties.name] ? 'selected' : ''}>
+ ${locale.displayLanguage} ${locale.displayCountry} (${locale})
+ </option>
+ </c:if>
</c:forEach>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
index eb42fc1..e9b3f8a 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/pages/base/nav.jsp
@@ -37,7 +37,34 @@
</div>
<div class="navbar-menu" id="top-navbar-menu">
<div class="navbar-end">
- <a class="navbar-item " href="/system/sling/logout" title="Logout of Apache Sling CMS"><span>${resourceResolver.userID} </span><i class="jam jam-log-out"></i></a>
+ <div class="navbar-item has-dropdown is-hoverable">
+ <sling:adaptTo adaptable="${resourceResolver}" adaptTo="org.apache.sling.cms.AuthorizableWrapper" var="auth" />
+ <sling:getResource path="${auth.authorizable.path}/profile" var="profile" />
+ <a class="navbar-link">
+ <sling:encode value="${profile.valueMap.name}" default="${resourceResolver.userID}" mode="HTML" />
+ </a>
+ <div class="navbar-dropdown">
+ <a class="navbar-item Fetch-Modal" data-title="User Profile" data-path=".Main-Content form" href="/cms/auth/user/profile.html${auth.authorizable.path}">
+ <i class="jam jam-id-card">
+ <span class="is-vhidden">Profile</span>
+ </i>
+ Profile
+ </a>
+ <a class="navbar-item" href="https://github.com/apache/sling-org-apache-sling-app-cms" target="_blank">
+ <i class="jam jam-help">
+ <span class="is-vhidden">Help</span>
+ </i>
+ Help
+ </a>
+ <hr class="navbar-divider">
+ <a class="navbar-item" href="/system/sling/logout">
+ <i class="jam jam-log-out">
+ <span class="is-vhidden">Logout</span>
+ </i>
+ Logout
+ </a>
+ </div>
+ </div>
</div>
</div>
</nav>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/create.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/create.json
new file mode 100644
index 0000000..9685745
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/create.json
@@ -0,0 +1,35 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Create Group",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "actionSuffix": "/*",
+ "button": "Create Group",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Name",
+ "name": ":name",
+ "required": true
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "creategroup"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/members.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/members.json
new file mode 100644
index 0000000..19a5d22
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/group/members.json
@@ -0,0 +1,32 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Members",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "button": "Update Members",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "membership": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/auth/members",
+ "name": ":members"
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "members"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/list.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/list.json
new file mode 100644
index 0000000..6c79a66
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/list.json
@@ -0,0 +1,229 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/base",
+ "jcr:title": "Users / Groups",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "contentactions": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/contentactions",
+ "actions": {
+ "user": {
+ "jcr:primaryType": "nt:unstructured",
+ "label": "User",
+ "prefix": "/cms/auth/user/create.html"
+ },
+ "group": {
+ "jcr:primaryType": "nt:unstructured",
+ "label": "Group",
+ "prefix": "/cms/auth/group/create.html"
+ },
+ "repFolder": {
+ "jcr:primaryType": "nt:unstructured",
+ "label": "Folder",
+ "prefix": "/cms/auth/newfolder.html"
+ }
+ }
+ },
+ "contentbreadcrumb": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/contentbreadcrumb",
+ "depth": 0
+ },
+ "contenttable": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/contenttable",
+ "columns": {
+ "jcr:primaryType": "nt:unstructured",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "title": "Name"
+ },
+ "actions": {
+ "jcr:primaryType": "nt:unstructured",
+ "title": "Actions"
+ }
+ },
+ "types": {
+ "jcr:primaryType": "nt:unstructured",
+ "rep:AuthorizableFolder": {
+ "jcr:primaryType": "nt:unstructured",
+ "columns": {
+ "jcr:primaryType": "nt:unstructured",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/name",
+ "link": true,
+ "prefix": "/cms/auth/list.html"
+ },
+ "actions": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/actions",
+ "movecopy": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Move / Copy Folder",
+ "icon": "move-alt",
+ "prefix": "/cms/shared/movecopy.html"
+ },
+ "delete": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Delete Folder",
+ "icon": "trash",
+ "prefix": "/cms/shared/delete.html"
+ }
+ }
+ }
+ },
+ "rep:Group": {
+ "jcr:primaryType": "nt:unstructured",
+ "columns": {
+ "jcr:primaryType": "nt:unstructured",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/text",
+ "property": "rep:principalName"
+ },
+ "actions": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/actions",
+ "membership": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Group Membership",
+ "icon": "user-circle",
+ "prefix": "/cms/auth/membership.html"
+ },
+ "members": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Group Members",
+ "icon": "users",
+ "prefix": "/cms/auth/group/members.html"
+ },
+ "movecopy": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Move / Copy Group",
+ "icon": "move-alt",
+ "prefix": "/cms/shared/movecopy.html"
+ },
+ "delete": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Delete Group",
+ "icon": "trash",
+ "prefix": "/cms/shared/delete.html"
+ }
+ }
+ }
+ },
+ "rep:SystemUser": {
+ "jcr:primaryType": "nt:unstructured",
+ "columns": {
+ "jcr:primaryType": "nt:unstructured",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/text",
+ "property": "rep:principalName"
+ },
+ "actions": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/actions",
+ "edit": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Edit User",
+ "icon": "pencil-f",
+ "prefix": "/cms/auth/user/edit.html"
+ },
+ "membership": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Group Membership",
+ "icon": "user-circle",
+ "prefix": "/cms/auth/membership.html"
+ },
+ "movecopy": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Move / Copy User",
+ "icon": "move-alt",
+ "prefix": "/cms/shared/movecopy.html"
+ },
+ "delete": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Delete User",
+ "icon": "trash",
+ "prefix": "/cms/shared/delete.html"
+ }
+ }
+ }
+ },
+ "rep:User": {
+ "jcr:primaryType": "nt:unstructured",
+ "columns": {
+ "jcr:primaryType": "nt:unstructured",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/text",
+ "property": "rep:principalName"
+ },
+ "actions": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/cms/columns/actions",
+ "profile": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "User Profile",
+ "icon": "id-card",
+ "prefix": "/cms/auth/user/profile.html"
+ },
+ "password": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Change Password",
+ "icon": "key",
+ "prefix": "/cms/auth/user/password.html"
+ },
+ "status": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Status",
+ "icon": "stop-sign",
+ "prefix": "/cms/auth/user/status.html"
+ },
+ "membership": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Group Membership",
+ "icon": "user-circle",
+ "prefix": "/cms/auth/membership.html"
+ },
+ "movecopy": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Move / Copy User",
+ "icon": "move-alt",
+ "prefix": "/cms/shared/movecopy.html"
+ },
+ "delete": {
+ "jcr:primaryType": "nt:unstructured",
+ "modal": true,
+ "title": "Delete User",
+ "icon": "trash",
+ "prefix": "/cms/shared/delete.html"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/membership.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/membership.json
new file mode 100644
index 0000000..138aa87
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/membership.json
@@ -0,0 +1,32 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Membership",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "button": "Update Membership",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "membership": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/auth/membership",
+ "name": ":membership"
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "membership"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/newfolder.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/newfolder.json
new file mode 100644
index 0000000..617a1f8
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/newfolder.json
@@ -0,0 +1,36 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Create Authorizable Folder",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "actionSuffix": "/*",
+ "button": "Create Folder",
+ "successPrepend": "/cms/auth/list.html",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Name",
+ "name": ":name",
+ "required": true
+ },
+ "primaryType": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": "jcr:primaryType",
+ "value": "rep:AuthorizableFolder"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/create.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/create.json
new file mode 100644
index 0000000..3fb2e6f
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/create.json
@@ -0,0 +1,43 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Create User",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "actionSuffix": "/*",
+ "button": "Create User",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Name",
+ "name": ":name",
+ "required": true
+ },
+ "password": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Password",
+ "name": ":password",
+ "required": true,
+ "type": "password"
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "createuser"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/password.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/password.json
new file mode 100644
index 0000000..54c3f4c
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/password.json
@@ -0,0 +1,35 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "Change Password",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "button": "Change Password",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "password": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "New Password",
+ "name": ":password",
+ "type": "password",
+ "requred": true
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "changepassword"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json
new file mode 100644
index 0000000..2680887
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/profile.json
@@ -0,0 +1,41 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "User Profile",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "button": "Update Profile",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "name": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Name",
+ "name": "profile/name"
+ },
+ "email": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/text",
+ "label": "Email",
+ "type": "email",
+ "name": "profile/email"
+ },
+ "locale": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/select",
+ "label": "Locale",
+ "name": "profile/locale",
+ "optionsScript": "/libs/sling-cms/components/editor/scripts/localeOptions.jsp"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/status.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/status.json
new file mode 100644
index 0000000..e16d044
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/auth/user/status.json
@@ -0,0 +1,31 @@
+{
+ "jcr:primaryType": "sling:Page",
+ "jcr:content": {
+ "sling:resourceType": "sling-cms/components/pages/modal",
+ "jcr:title": "User Status",
+ "jcr:primaryType": "nt:unstructured",
+ "container": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "slingform": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/slingform",
+ "button": "Update Status",
+ "fields": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/general/container",
+ "status": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/auth/status"
+ },
+ "operation": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+ "name": ":operation",
+ "value": "updatestatus"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
index b6fea7f..43fe601 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
@@ -141,7 +141,10 @@
},
"usersgroups": {
"jcr:primaryType": "nt:unstructured",
- "link": "/bin/users.html",
+ "enabledGroups": [
+ "administrators"
+ ],
+ "link": "/cms/auth/list.html/home",
"text": "Users & Groups"
}
}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.cms.core.internal.ResourceEditorAssociation-auth.config b/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.cms.core.internal.ResourceEditorAssociation-auth.config
new file mode 100644
index 0000000..9188e1b
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.cms.core.internal.ResourceEditorAssociation-auth.config
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+pathPattern="\\/home.*"
+editor="/cms/auth/list.html"
\ No newline at end of file