diff --git a/source/java/org/alfresco/rest/api/Groups.java b/source/java/org/alfresco/rest/api/Groups.java index 097f821725..0d78ac5238 100644 --- a/source/java/org/alfresco/rest/api/Groups.java +++ b/source/java/org/alfresco/rest/api/Groups.java @@ -131,4 +131,12 @@ public interface Groups * @return a paged list of {@code org.alfresco.rest.api.model.GroupMember} objects */ CollectionWithPagingInfo getGroupMembers(String groupId, Parameters parameters); + + /** + * Create a group member. + * + * @param groupId the identifier of a group. + * @return a {@code org.alfresco.rest.api.model.GroupMember} object + */ + GroupMember createGroupMember(String groupId, GroupMember groupMember); } diff --git a/source/java/org/alfresco/rest/api/groups/GroupMembersRelation.java b/source/java/org/alfresco/rest/api/groups/GroupMembersRelation.java index d989628e59..354f06c214 100644 --- a/source/java/org/alfresco/rest/api/groups/GroupMembersRelation.java +++ b/source/java/org/alfresco/rest/api/groups/GroupMembersRelation.java @@ -25,9 +25,14 @@ */ package org.alfresco.rest.api.groups; +import java.util.ArrayList; +import java.util.List; + import org.alfresco.rest.api.Groups; import org.alfresco.rest.api.model.GroupMember; import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.ResourceParameter; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -40,7 +45,7 @@ import org.springframework.beans.factory.InitializingBean; * @author cturlica */ @RelationshipResource(name = "members", entityResource = GroupsEntityResource.class, title = "Group Members") -public class GroupMembersRelation implements RelationshipResourceAction.Read, InitializingBean +public class GroupMembersRelation implements RelationshipResourceAction.Read, RelationshipResourceAction.Create, InitializingBean { private Groups groups; @@ -56,9 +61,19 @@ public class GroupMembersRelation implements RelationshipResourceAction.Read readAll(String groupId, Parameters params) { return groups.getGroupMembers(groupId, params); } + + @Override + @WebApiDescription(title = "Create group membership.") + @WebApiParam(name = "entity", title = "A single group member", description = "A single group member, multiple groups members are not supported.", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT, allowMultiple = false) + public List create(String groupId, List entity, Parameters params) + { + List result = new ArrayList<>(1); + result.add(groups.createGroupMember(groupId, entity.get(0))); + return result; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/impl/GroupsImpl.java b/source/java/org/alfresco/rest/api/impl/GroupsImpl.java index bec89c6ae5..d3c3ae06c3 100644 --- a/source/java/org/alfresco/rest/api/impl/GroupsImpl.java +++ b/source/java/org/alfresco/rest/api/impl/GroupsImpl.java @@ -25,11 +25,27 @@ */ package org.alfresco.rest.api.impl; +import static org.alfresco.repo.security.authentication.AuthenticationUtil.runAsSystem; + +import java.text.Collator; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import org.alfresco.query.CannedQueryPageDetails; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; -import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.security.authority.AuthorityException; import org.alfresco.repo.security.authority.AuthorityInfo; import org.alfresco.repo.security.authority.UnknownAuthorityException; @@ -51,17 +67,11 @@ import org.alfresco.rest.framework.resource.parameters.where.QueryHelper; import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalkerOrSupported; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.util.AlfrescoCollator; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.AlfrescoCollator; import org.alfresco.util.Pair; import org.springframework.extensions.surf.util.I18NUtil; -import java.text.Collator; -import java.util.*; -import java.util.stream.Collectors; - -import static org.alfresco.repo.security.authentication.AuthenticationUtil.runAsSystem; - /** * Centralises access to groups services and maps between representations. * @@ -573,21 +583,7 @@ public class GroupsImpl implements Groups QueryHelper.walk(q, propertyWalker); String memberTypeStr = propertyWalker.getProperty(PARAM_MEMBER_TYPE, WhereClauseParser.EQUALS, String.class); - - if (memberTypeStr != null && !memberTypeStr.isEmpty()) - { - switch (memberTypeStr) - { - case PARAM_MEMBER_TYPE_GROUP: - authorityType = AuthorityType.GROUP; - break; - case PARAM_MEMBER_TYPE_PERSON: - authorityType = AuthorityType.USER; - break; - default: - throw new InvalidArgumentException("MemberType is invalid (expected eg. GROUP, PERSON)"); - } - } + authorityType = getAuthorityType(memberTypeStr); } PagingResults pagingResult = getAuthoritiesInfo(authorityType, groupId, sortProp, paging); @@ -614,6 +610,45 @@ public class GroupsImpl implements Groups return CollectionWithPagingInfo.asPaged(paging, groupMembers, pagingResult.hasMoreItems(), totalItems); } + @Override + public GroupMember createGroupMember(String groupId, GroupMember groupMember) + { + validateGroupId(groupId, false); + validateGroupMember(groupMember); + + AuthorityType authorityType = getAuthorityType(groupMember.getMemberType()); + + if (!authorityService.authorityExists(groupMember.getId())) + { + throw new EntityNotFoundException("Group member with id " + groupMember.getId() + " does not exists"); + } + + authorityService.addAuthority(groupId, groupMember.getId()); + String authority = authorityService.getName(authorityType, groupMember.getId()); + + return getGroupMember(authority); + } + + private AuthorityType getAuthorityType(String memberType) + { + AuthorityType authorityType = null; + if (memberType != null && !memberType.isEmpty()) + { + switch (memberType) + { + case PARAM_MEMBER_TYPE_GROUP: + authorityType = AuthorityType.GROUP; + break; + case PARAM_MEMBER_TYPE_PERSON: + authorityType = AuthorityType.USER; + break; + default: + throw new InvalidArgumentException("MemberType is invalid (expected eg. GROUP, PERSON)"); + } + } + return authorityType; + } + private PagingResults getAuthoritiesInfo(AuthorityType authorityType, String groupId, Pair sortProp, Paging paging) { Set authorities; @@ -674,6 +709,13 @@ public class GroupsImpl implements Groups return groupMember; } + private GroupMember getGroupMember(String authorityId) + { + AuthorityInfo authorityInfo = getAuthorityInfo(authorityId); + + return getGroupMember(authorityInfo); + } + private void validateGroupId(String groupId, boolean inferPrefix) { if (groupId == null || groupId.isEmpty()) @@ -735,6 +777,24 @@ public class GroupsImpl implements Groups } } + private void validateGroupMember(GroupMember groupMember) + { + if (groupMember == null) + { + throw new InvalidArgumentException("group member is null"); + } + + if (groupMember.getId() == null || groupMember.getId().isEmpty()) + { + throw new InvalidArgumentException("group member Id is null or empty"); + } + + if (groupMember.getMemberType() == null || groupMember.getMemberType().isEmpty()) + { + throw new InvalidArgumentException("group member type is null or empty"); + } + } + private boolean groupAuthorityExists(String authorityName) { return groupAuthorityExists(authorityName, true); diff --git a/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java b/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java index 612f1645ce..ff59b4d620 100644 --- a/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java @@ -25,6 +25,22 @@ */ package org.alfresco.rest.api.tests; +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 java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.servlet.http.HttpServletResponse; + import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.rest.AbstractSingleNetworkSiteTest; import org.alfresco.rest.api.tests.client.PublicApiClient; @@ -44,12 +60,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import javax.servlet.http.HttpServletResponse; -import java.text.ParseException; -import java.util.*; - -import static org.junit.Assert.*; - /** * V1 REST API tests for managing Groups * @@ -57,6 +67,9 @@ import static org.junit.Assert.*; */ public class GroupsTest extends AbstractSingleNetworkSiteTest { + private static final String MEMBER_TYPE_GROUP = "GROUP"; + private static final String MEMBER_TYPE_PERSON = "PERSON"; + protected AuthorityService authorityService; private String rootGroupName = null; @@ -371,9 +384,11 @@ public class GroupsTest extends AbstractSingleNetworkSiteTest groupMemberA = new GroupMember(); groupMemberA.setId(groupAAuthorityName); + groupMemberA.setMemberType(AuthorityType.GROUP.toString()); groupMemberB = new GroupMember(); groupMemberB.setId(groupBAuthorityName); + groupMemberB.setMemberType(AuthorityType.GROUP.toString()); } } @@ -917,6 +932,101 @@ public class GroupsTest extends AbstractSingleNetworkSiteTest } } + @Test + public void testCreateGroupMembers() throws PublicApiException + { + final Groups groupsProxy = publicApiClient.groups(); + + try + { + createAuthorityContext(user1); + + Person personAlice; + { + publicApiClient.setRequestContext(new RequestContext(networkOne.getId(), networkAdmin, "admin")); + personAlice = new Person(); + String aliceId = "alice-" + UUID.randomUUID() + "@" + networkOne.getId(); + personAlice.setUserName(aliceId); + personAlice.setId(aliceId); + personAlice.setFirstName("Alice"); + personAlice.setEmail("alison.smith@example.com"); + personAlice.setPassword("password"); + personAlice.setEnabled(true); + PublicApiClient.People people = publicApiClient.people(); + people.create(personAlice); + } + + // +ve tests + // Create a group membership (for a existing person and a sub-group) + // within a group groupId + { + GroupMember personMember = new GroupMember(); + personMember.setId(personAlice.getId()); + personMember.setMemberType(MEMBER_TYPE_PERSON); + + // Add person as groupB member + groupsProxy.createGroupMember(groupB.getId(), personMember, HttpServletResponse.SC_CREATED); + // Add group as groupB sub-group + groupsProxy.createGroupMember(groupB.getId(), groupMemberA, HttpServletResponse.SC_CREATED); + } + + // If the added sub-group was previously a root group then it + // becomes a non-root group since it now has a parent. + { + // create a group without parent + Group groupRoot = generateGroup(); + Group groupRootCreated = groupsProxy.createGroup(groupRoot, null, HttpServletResponse.SC_CREATED); + assertTrue("Group was expected to be root.", groupRootCreated.getIsRoot()); + GroupMember groupMember = new GroupMember(); + groupMember.setId(groupRootCreated.getId()); + groupMember.setMemberType(MEMBER_TYPE_GROUP); + + groupsProxy.createGroupMember(groupB.getId(), groupMember, HttpServletResponse.SC_CREATED); + Group subGroup = groupsProxy.getGroup(groupMember.getId()); + assertFalse("Group was expected to be sub-group.", subGroup.getIsRoot()); + } + + // Person or group with given id does not exists + { + GroupMember invalidIdGroupMember = new GroupMember(); + invalidIdGroupMember.setId("invalidPersonId-" + GUID.generate()); + invalidIdGroupMember.setMemberType(MEMBER_TYPE_PERSON); + groupsProxy.createGroupMember(groupA.getId(), invalidIdGroupMember, HttpServletResponse.SC_NOT_FOUND); + invalidIdGroupMember.setMemberType(MEMBER_TYPE_GROUP); + groupsProxy.createGroupMember(groupA.getId(), invalidIdGroupMember, HttpServletResponse.SC_NOT_FOUND); + } + + // Invalid group Id + { + groupsProxy.createGroupMember("invalidGroupId", groupMemberA, HttpServletResponse.SC_NOT_FOUND); + } + + // Invalid group member + { + GroupMember invalidGroupMember = new GroupMember(); + groupsProxy.createGroupMember(groupA.getId(), invalidGroupMember, HttpServletResponse.SC_BAD_REQUEST); + + // Member type still missing + invalidGroupMember.setId("Test_" + GUID.generate()); + groupsProxy.createGroupMember(groupA.getId(), invalidGroupMember, HttpServletResponse.SC_BAD_REQUEST); + // invalid member type + invalidGroupMember.setMemberType("invalidMemberType"); + groupsProxy.createGroupMember(groupA.getId(), invalidGroupMember, HttpServletResponse.SC_BAD_REQUEST); + } + + // -ve tests + // Add group with non-admin user + { + setRequestContext(user1); + groupsProxy.createGroupMember(groupA.getId(), groupMemberA, HttpServletResponse.SC_FORBIDDEN); + } + } + finally + { + clearAuthorityContext(); + } + } + @Test public void testUpdateGroup() throws Exception { diff --git a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java index 475adc0d83..316fbecee1 100644 --- a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java +++ b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java @@ -70,8 +70,6 @@ import org.alfresco.rest.api.tests.client.data.SiteImpl; import org.alfresco.rest.api.tests.client.data.SiteMember; import org.alfresco.rest.api.tests.client.data.SiteMembershipRequest; import org.alfresco.rest.api.tests.client.data.Tag; -import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; -import org.alfresco.rest.framework.resource.parameters.Parameters; import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.Document; import org.apache.chemistry.opencmis.client.api.FileableCmisObject; @@ -2289,6 +2287,25 @@ public class PublicApiClient return parseGroupEntity(response); } + public GroupMember createGroupMember(String groupId, GroupMember groupMember) throws PublicApiException + { + return createGroupMember(groupId, groupMember, HttpServletResponse.SC_OK); + } + + public GroupMember createGroupMember(String groupId, GroupMember groupMember, int expectedStatus) throws PublicApiException + { + HttpResponse response = create("groups", groupId, "members", null, groupMember.toJSON().toString(), "Failed to create group membership", expectedStatus); + if (response != null && response.getJsonResponse() != null) + { + JSONObject jsonEntity = (JSONObject) response.getJsonResponse().get("entry"); + if (jsonEntity != null) + { + return GroupMember.parseGroupMember(response.getJsonResponse()); + } + } + return null; + } + public Group updateGroup(String groupId, Group group, Map params, int expectedStatus) throws PublicApiException { HttpResponse response = update("groups", groupId, null, null, group.toJSON().toString(), params, "Failed to update group " + group.getId(), expectedStatus);