From 02a2812852ac26ee9a2727f9bc49fb17becb59c9 Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Mon, 24 Jun 2024 07:13:45 -0700 Subject: [PATCH] implement domain group members api Signed-off-by: Henry Avetisyan --- clients/go/zms/client.go | 32 +++++ clients/go/zms/zms_schema.go | 11 ++ .../java/com/yahoo/athenz/zms/ZMSClient.java | 19 +++ .../athenz/zms/ZMSRDLGeneratedClient.java | 28 +++++ .../com/yahoo/athenz/zms/ZMSClientTest.java | 50 ++++++++ .../java/com/yahoo/athenz/zms/ZMSSchema.java | 16 +++ core/zms/src/main/rdl/Group.rdli | 13 ++ libs/go/zmscli/cli.go | 13 ++ libs/go/zmscli/group.go | 17 +++ .../java/com/yahoo/athenz/zms/DBService.java | 6 + .../java/com/yahoo/athenz/zms/ZMSHandler.java | 1 + .../java/com/yahoo/athenz/zms/ZMSImpl.java | 19 +++ .../com/yahoo/athenz/zms/ZMSResources.java | 34 ++++++ .../zms/store/ObjectStoreConnection.java | 1 + .../zms/store/impl/jdbc/JDBCConnection.java | 54 ++++++++ .../yahoo/athenz/zms/PrincipalGroupTest.java | 67 ++++++++++ .../com/yahoo/athenz/zms/ZMSImplTest.java | 14 +++ .../com/yahoo/athenz/zms/ZMSTestUtils.java | 54 ++++++++ .../store/impl/jdbc/JDBCConnectionTest.java | 115 +++++++++++++++++- 19 files changed, 560 insertions(+), 4 deletions(-) diff --git a/clients/go/zms/client.go b/clients/go/zms/client.go index adfc7a4b0ce..0ea1e3ba53d 100644 --- a/clients/go/zms/client.go +++ b/clients/go/zms/client.go @@ -2201,6 +2201,38 @@ func (client ZMSClient) PutResourceGroupOwnership(domainName DomainName, groupNa } } +func (client ZMSClient) GetDomainGroupMembers(domainName DomainName) (*DomainGroupMembers, error) { + var data *DomainGroupMembers + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/group/member" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + contentBytes, err := io.ReadAll(resp.Body) + if err != nil { + return data, err + } + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + func (client ZMSClient) GetPolicyList(domainName DomainName, limit *int32, skip string) (*PolicyList, error) { var data *PolicyList url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy" + encodeParams(encodeOptionalInt32Param("limit", limit), encodeStringParam("skip", string(skip), "")) diff --git a/clients/go/zms/zms_schema.go b/clients/go/zms/zms_schema.go index 4c7fa81070c..e106b77834e 100644 --- a/clients/go/zms/zms_schema.go +++ b/clients/go/zms/zms_schema.go @@ -1960,6 +1960,17 @@ func init() { mPutResourceGroupOwnership.Exception("UNAUTHORIZED", "ResourceError", "") sb.AddResource(mPutResourceGroupOwnership.Build()) + mGetDomainGroupMembers := rdl.NewResourceBuilder("DomainGroupMembers", "GET", "/domain/{domainName}/group/member") + mGetDomainGroupMembers.Comment("Get list of principals defined in groups in the given domain") + mGetDomainGroupMembers.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + mGetDomainGroupMembers.Auth("", "", true, "") + mGetDomainGroupMembers.Exception("BAD_REQUEST", "ResourceError", "") + mGetDomainGroupMembers.Exception("FORBIDDEN", "ResourceError", "") + mGetDomainGroupMembers.Exception("NOT_FOUND", "ResourceError", "") + mGetDomainGroupMembers.Exception("TOO_MANY_REQUESTS", "ResourceError", "") + mGetDomainGroupMembers.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(mGetDomainGroupMembers.Build()) + mGetPolicyList := rdl.NewResourceBuilder("PolicyList", "GET", "/domain/{domainName}/policy") mGetPolicyList.Comment("List policies provisioned in this namespace.") mGetPolicyList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java index 998e81b7c7b..fe0aee5e978 100644 --- a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java @@ -3759,6 +3759,25 @@ public GroupMembership getGroupMembership(String domainName, String groupName, S } } + /** + * Retrieve the list of all members provisioned for a domain + * in groups + * + * @param domainName name of the domain + * @return DomainGroupMembers object that includes the list of members with their groups + * @throws ZMSClientException in case of failure + */ + public DomainGroupMembers getDomainGroupMembers(String domainName) { + updatePrincipal(); + try { + return client.getDomainGroupMembers(domainName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ResourceException.BAD_REQUEST, ex.getMessage()); + } + } + /** * Fetch all the groups across domains by either calling or specified principal * @param principal - Requested principal. If null will return groups for the user making the call diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java index 1a325f32cac..4ec3be9c724 100644 --- a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java @@ -2105,6 +2105,34 @@ public ResourceGroupOwnership putResourceGroupOwnership(String domainName, Strin } } + public DomainGroupMembers getDomainGroupMembers(String domainName) throws URISyntaxException, IOException { + UriTemplateBuilder uriTemplateBuilder = new UriTemplateBuilder(baseUrl, "/domain/{domainName}/group/member") + .resolveTemplate("domainName", domainName); + URIBuilder uriBuilder = new URIBuilder(uriTemplateBuilder.getUri()); + HttpUriRequest httpUriRequest = RequestBuilder.get() + .setUri(uriBuilder.build()) + .build(); + if (credsHeader != null) { + httpUriRequest.addHeader(credsHeader, credsToken); + } + HttpEntity httpResponseEntity = null; + try (CloseableHttpResponse httpResponse = client.execute(httpUriRequest, httpContext)) { + int code = httpResponse.getStatusLine().getStatusCode(); + httpResponseEntity = httpResponse.getEntity(); + switch (code) { + case 200: + return jsonMapper.readValue(httpResponseEntity.getContent(), DomainGroupMembers.class); + default: + final String errorData = (httpResponseEntity == null) ? null : EntityUtils.toString(httpResponseEntity); + throw (errorData != null && !errorData.isEmpty()) + ? new ResourceException(code, jsonMapper.readValue(errorData, ResourceError.class)) + : new ResourceException(code); + } + } finally { + EntityUtils.consumeQuietly(httpResponseEntity); + } + } + public PolicyList getPolicyList(String domainName, Integer limit, String skip) throws URISyntaxException, IOException { UriTemplateBuilder uriTemplateBuilder = new UriTemplateBuilder(baseUrl, "/domain/{domainName}/policy") .resolveTemplate("domainName", domainName); diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java index bb5abe9ac75..14e42c4ee4c 100644 --- a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java @@ -3536,6 +3536,56 @@ public void testGetDomainRoleMembers() throws URISyntaxException, IOException { } } + @Test + public void testGetDomainGroupMembers() throws URISyntaxException, IOException { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + + GroupMember memberGroup = new GroupMember(); + memberGroup.setGroupName("readers"); + + List memberGroups = new ArrayList<>(); + memberGroups.add(memberGroup); + + DomainGroupMember member = new DomainGroupMember(); + member.setMemberName("athenz.api"); + member.setMemberGroups(memberGroups); + + List members = new ArrayList<>(); + members.add(member); + + DomainGroupMembers domainGroupMembers = new DomainGroupMembers(); + domainGroupMembers.setMembers(members); + + Mockito.when(c.getDomainGroupMembers("athenz")) + .thenReturn(domainGroupMembers) + .thenThrow(new ZMSClientException(401, "fail")) + .thenThrow(new IllegalArgumentException("other-error")); + + DomainGroupMembers retMembers = client.getDomainGroupMembers("athenz"); + assertNotNull(retMembers); + assertEquals(retMembers.getMembers().get(0).getMemberName(), "athenz.api"); + + // second time it fails with zms client exception + + try { + client.getDomainGroupMembers("athenz"); + fail(); + } catch (ZMSClientException ex) { + assertEquals(401, ex.getCode()); + } + + // last time with std exception - resulting in 400 + + try { + client.getDomainGroupMembers("athenz"); + fail(); + } catch (ZMSClientException ex) { + assertEquals(400, ex.getCode()); + } + } + @Test public void testGetPrincipalRoles() throws URISyntaxException, IOException { ZMSClient client = createClient(systemAdminUser); diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java index 792988e0aeb..4cdf030b98d 100644 --- a/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java @@ -2069,6 +2069,22 @@ private static Schema build() { .exception("UNAUTHORIZED", "ResourceError", "") ; + sb.resource("DomainGroupMembers", "GET", "/domain/{domainName}/group/member") + .comment("Get list of principals defined in groups in the given domain") + .pathParam("domainName", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("TOO_MANY_REQUESTS", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + sb.resource("PolicyList", "GET", "/domain/{domainName}/policy") .comment("List policies provisioned in this namespace.") .pathParam("domainName", "DomainName", "name of the domain") diff --git a/core/zms/src/main/rdl/Group.rdli b/core/zms/src/main/rdl/Group.rdli index 6199e1edda6..1ee546c3f8b 100644 --- a/core/zms/src/main/rdl/Group.rdli +++ b/core/zms/src/main/rdl/Group.rdli @@ -295,3 +295,16 @@ resource ResourceGroupOwnership PUT "/domain/{domainName}/group/{groupName}/owne ResourceError TOO_MANY_REQUESTS; } } + +//Get list of principals defined in groups in the given domain +resource DomainGroupMembers GET "/domain/{domainName}/group/member" { + DomainName domainName; //name of the domain + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError TOO_MANY_REQUESTS; + } +} diff --git a/libs/go/zmscli/cli.go b/libs/go/zmscli/cli.go index f954872a8e3..330ddf16eb3 100644 --- a/libs/go/zmscli/cli.go +++ b/libs/go/zmscli/cli.go @@ -644,6 +644,10 @@ func (cli Zms) EvalCommand(params []string) (*string, error) { if argc == 0 { return cli.ListDomainRoleMembers(dn) } + case "list-domain-group-members": + if argc == 0 { + return cli.ListDomainGroupMembers(dn) + } case "list-group", "list-groups": return cli.ListGroups(dn) case "show-group": @@ -2253,6 +2257,15 @@ func (cli Zms) HelpSpecificCommand(interactive bool, cmd string) string { buf.WriteString(" role : name of the role to be deleted\n") buf.WriteString(" examples:\n") buf.WriteString(" " + domainExample + " delete-role readers\n") + case "list-domain-group-members": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domainParam + " list-domain-group-members\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain\n") + } + buf.WriteString(" examples:\n") + buf.WriteString(" " + domainExample + " list-domain-group-members\n") case "list-group": buf.WriteString(" syntax:\n") buf.WriteString(" " + domainParam + " list-group\n") diff --git a/libs/go/zmscli/group.go b/libs/go/zmscli/group.go index 42f64589ea0..8ca29b4eb17 100644 --- a/libs/go/zmscli/group.go +++ b/libs/go/zmscli/group.go @@ -699,3 +699,20 @@ func (cli Zms) SetGroupPrincipalDomainFilter(dn, gn, domainFilter string) (*stri return cli.dumpByFormat(message, cli.buildYAMLOutput) } + +func (cli Zms) ListDomainGroupMembers(dn string) (*string, error) { + groupMembers, err := cli.Zms.GetDomainGroupMembers(zms.DomainName(dn)) + if err != nil { + return nil, err + } + + oldYamlConverter := func(res interface{}) (*string, error) { + var buf bytes.Buffer + buf.WriteString("group members:\n") + cli.dumpDomainGroupMembers(&buf, groupMembers, false) + s := buf.String() + return &s, nil + } + + return cli.dumpByFormat(groupMembers, oldYamlConverter) +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java index 1c0f364fdf4..f208a87450e 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java @@ -3503,6 +3503,12 @@ DomainRoleMembers listDomainRoleMembers(String domainName) { } } + DomainGroupMembers listDomainGroupMembers(String domainName) { + try (ObjectStoreConnection con = store.getConnection(true, false)) { + return con.listDomainGroupMembers(domainName); + } + } + DomainRoleMember getPrincipalRoles(String principal, String domainName, Boolean expand) { DomainRoleMember principalRoles; diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java index aba338da640..426bce93637 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java @@ -69,6 +69,7 @@ public interface ZMSHandler { Response putGroupReview(ResourceContext context, String domainName, String groupName, String auditRef, Boolean returnObj, String resourceOwner, Group group); DomainGroupMembership getPendingDomainGroupMembersList(ResourceContext context, String principal, String domainName); void putResourceGroupOwnership(ResourceContext context, String domainName, String groupName, String auditRef, ResourceGroupOwnership resourceOwnership); + DomainGroupMembers getDomainGroupMembers(ResourceContext context, String domainName); PolicyList getPolicyList(ResourceContext context, String domainName, Integer limit, String skip); Policies getPolicies(ResourceContext context, String domainName, Boolean assertions, Boolean includeNonActive, String tagKey, String tagValue); Policy getPolicy(ResourceContext context, String domainName, String policyName); diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java index 02619596dc8..3dde9dd436a 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java @@ -3901,6 +3901,25 @@ public DomainRoleMembers getDomainRoleMembers(ResourceContext ctx, String domain return dbService.listDomainRoleMembers(domainName); } + @Override + public DomainGroupMembers getDomainGroupMembers(ResourceContext ctx, String domainName) { + + final String caller = ctx.getApiName(); + logPrincipal(ctx); + + validateRequest(ctx.request(), caller); + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + setRequestDomain(ctx, domainName); + + return dbService.listDomainGroupMembers(domainName); + } + @Override public DomainRoleMember getPrincipalRoles(ResourceContext context, String principal, String domainName, Boolean expand) { final String caller = context.getApiName(); diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java index 85cd787668e..9b0a28856a4 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java @@ -2166,6 +2166,40 @@ public void putResourceGroupOwnership( } } + @GET + @Path("/domain/{domainName}/group/member") + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Get list of principals defined in groups in the given domain") + public DomainGroupMembers getDomainGroupMembers( + @Parameter(description = "name of the domain", required = true) @PathParam("domainName") String domainName) { + int code = ResourceException.OK; + ResourceContext context = null; + try { + context = this.delegate.newResourceContext(this.servletContext, this.request, this.response, "getDomainGroupMembers"); + context.authenticate(); + return this.delegate.getDomainGroupMembers(context, domainName); + } catch (ResourceException e) { + code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.TOO_MANY_REQUESTS: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomainGroupMembers"); + throw typedException(code, e, ResourceError.class); + } + } finally { + this.delegate.recordMetrics(context, code); + } + } + @GET @Path("/domain/{domainName}/policy") @Produces(MediaType.APPLICATION_JSON) diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java index dcf8649a8b2..57f2dd11bfd 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java @@ -127,6 +127,7 @@ public interface ObjectStoreConnection extends Closeable { boolean deletePendingGroupMember(String domainName, String groupName, String member, String principal, String auditRef); boolean confirmGroupMember(String domainName, String groupName, GroupMember groupMember, String principal, String auditRef); + DomainGroupMembers listDomainGroupMembers(String domainName); DomainGroupMember getPrincipalGroups(String principal, String domainName); List listGroupsWithUserAuthorityRestrictions(); diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnection.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnection.java index ae7beb4794c..5ae8b19ccbf 100644 --- a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnection.java +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnection.java @@ -6868,6 +6868,60 @@ private DomainGroupMember getGroupsForPrincipal(String caller, DomainGroupMember } } + @Override + public DomainGroupMembers listDomainGroupMembers(String domainName) { + + final String caller = "listDomainGroupMembers"; + + int domainId = getDomainId(domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + + DomainGroupMembers domainGroupMembers = new DomainGroupMembers(); + domainGroupMembers.setDomainName(domainName); + + Map memberMap = new HashMap<>(); + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_GROUP_MEMBERS)) { + ps.setInt(1, domainId); + try (ResultSet rs = executeQuery(ps, caller)) { + while (rs.next()) { + final String groupName = rs.getString(1); + final String memberName = rs.getString(2); + + DomainGroupMember domainGroupMember = memberMap.get(memberName); + if (domainGroupMember == null) { + domainGroupMember = new DomainGroupMember(); + domainGroupMember.setMemberName(memberName); + memberMap.put(memberName, domainGroupMember); + } + + List members = domainGroupMember.getMemberGroups(); + if (members == null) { + members = new ArrayList<>(); + domainGroupMember.setMemberGroups(members); + } + GroupMember groupMember = new GroupMember(); + groupMember.setGroupName(groupName); + java.sql.Timestamp expiration = rs.getTimestamp(3); + if (expiration != null) { + groupMember.setExpiration(Timestamp.fromMillis(expiration.getTime())); + } + groupMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(4), 0)); + members.add(groupMember); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + if (!memberMap.isEmpty()) { + domainGroupMembers.setMembers(new ArrayList<>(memberMap.values())); + } + return domainGroupMembers; + } + @Override public DomainGroupMember getPrincipalGroups(String principal, String domainName) { diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/PrincipalGroupTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/PrincipalGroupTest.java index a900406d891..90d4594f497 100644 --- a/servers/zms/src/test/java/com/yahoo/athenz/zms/PrincipalGroupTest.java +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/PrincipalGroupTest.java @@ -15,12 +15,37 @@ */ package com.yahoo.athenz.zms; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.List; + import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; public class PrincipalGroupTest { + private final ZMSTestInitializer zmsTestInitializer = new ZMSTestInitializer(); + + @BeforeClass + public void startMemoryMySQL() { + zmsTestInitializer.startMemoryMySQL(); + } + + @AfterClass + public void stopMemoryMySQL() { + zmsTestInitializer.stopMemoryMySQL(); + } + + @BeforeMethod + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + zmsTestInitializer.setUp(); + } + @Test public void testPrincipalGroup() { PrincipalGroup prGroup = new PrincipalGroup(); @@ -32,4 +57,46 @@ public void testPrincipalGroup() { assertEquals("domain", prGroup.getDomainName()); assertEquals("authority", prGroup.getDomainUserAuthorityFilter()); } + + @Test + public void testListDomainGroupMembers() { + + String domainName = "listdomaingroupmembers"; + + ZMSImpl zmsImpl = zmsTestInitializer.getZms(); + RsrcCtxWrapper ctx = zmsTestInitializer.getMockDomRsrcCtx(); + final String auditRef = zmsTestInitializer.getAuditRef(); + + TopLevelDomain dom1 = zmsTestInitializer.createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", zmsTestInitializer.getAdminUser()); + zmsImpl.postTopLevelDomain(ctx, auditRef, null, dom1); + + Group group1 = zmsTestInitializer.createGroupObject(domainName, "group1", "user.jack", "user.janie"); + zmsImpl.putGroup(ctx, domainName, "group1", auditRef, false, null, group1); + + Group group2 = zmsTestInitializer.createGroupObject(domainName, "group2", "user.janie", "user.jane"); + zmsImpl.putGroup(ctx, domainName, "group2", auditRef, false, null, group2); + + Group group3 = zmsTestInitializer.createGroupObject(domainName, "group3", "user.jack", "user.jane"); + zmsImpl.putGroup(ctx, domainName, "group3", auditRef, false, null, group3); + + Group group4 = zmsTestInitializer.createGroupObject(domainName, "group4", "user.jack", null); + zmsImpl.putGroup(ctx, domainName, "group4", auditRef, false, null, group4); + + Group group5 = zmsTestInitializer.createGroupObject(domainName, "group5", "user.jack-service", "user.jane"); + zmsImpl.putGroup(ctx, domainName, "group5", auditRef, false, null, group5); + + DomainGroupMembers domainGroupMembers = zmsImpl.getDomainGroupMembers(ctx, domainName); + assertEquals(domainName, domainGroupMembers.getDomainName()); + + List members = domainGroupMembers.getMembers(); + assertNotNull(members); + assertEquals(4, members.size()); + ZMSTestUtils.verifyDomainGroupMember(members, "user.jack", "role1", "role3", "role4"); + ZMSTestUtils.verifyDomainGroupMember(members, "user.janie", "role1", "role2"); + ZMSTestUtils.verifyDomainGroupMember(members, "user.jane", "role2", "role3", "role5"); + ZMSTestUtils.verifyDomainGroupMember(members, "user.jack-service", "role5"); + + zmsImpl.deleteTopLevelDomain(ctx, domainName, auditRef, null); + } } diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java index 4ff2c4d39f0..71053f43f20 100644 --- a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java @@ -19574,6 +19574,20 @@ public void testGetDomainRoleMembersInvalidDomain() { } } + @Test + public void testGetDomainGroupMembersInvalidDomain() { + + ZMSImpl zmsImpl = zmsTestInitializer.getZms(); + RsrcCtxWrapper ctx = zmsTestInitializer.getMockDomRsrcCtx(); + + try { + zmsImpl.getDomainGroupMembers(ctx, "invalid-domain"); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + } + @Test public void testPutQuota() { diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTestUtils.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTestUtils.java index bfbba8c47f8..009374bae0a 100644 --- a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTestUtils.java +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTestUtils.java @@ -235,4 +235,58 @@ public static GroupMember getGroupMember(Group group, final String memberName) { } return null; } + + public static boolean verifyDomainGroupMember(DomainGroupMember domainGroupMember, GroupMember groupMember) { + for (GroupMember grpMember : domainGroupMember.getMemberGroups()) { + if (grpMember.equals(groupMember)) { + return true; + } + } + return false; + } + + public static boolean verifyDomainGroupMember(List members, String memberName, + String... groups) { + + for (DomainGroupMember member : members) { + if (member.getMemberName().equals(memberName)) { + List memberGroups = member.getMemberGroups(); + if (memberGroups.size() != groups.length) { + return false; + } + for (String group : groups) { + boolean bMatchFound = false; + for (GroupMember memberGroup : memberGroups) { + if (memberGroup.getGroupName().equals(group)) { + bMatchFound = true; + break; + } + } + if (!bMatchFound) { + return false; + } + } + + return true; + } + } + + return false; + } + + public static boolean verifyDomainGroupMemberTimestamp(List members, + final String memberName, final String groupName, Timestamp timestamp) { + + for (DomainGroupMember member : members) { + if (member.getMemberName().equals(memberName)) { + for (GroupMember groupMember : member.getMemberGroups()) { + if (groupMember.getGroupName().equals(groupName)) { + return groupMember.getExpiration().equals(timestamp); + } + } + } + } + + return false; + } } diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnectionTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnectionTest.java index a4ab37d67a2..d00c23d52d2 100644 --- a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnectionTest.java +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/impl/jdbc/JDBCConnectionTest.java @@ -8797,10 +8797,8 @@ public void testListDomainRoleMembersNoEntries() throws Exception { @Test public void testListDomainRoleMembers() throws Exception { - JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); - domainRoleMembers(jdbcConn::listDomainRoleMembers, - MemberRole::getExpiration); + domainRoleMembers(jdbcConn::listDomainRoleMembers, MemberRole::getExpiration); jdbcConn.close(); } @@ -9034,7 +9032,6 @@ public void testGetPrincipalRolesException() throws SQLException { } catch (ResourceException exception) { assertEquals(exception.getCode(), 503); assertEquals(exception.getData().toString(), "{code: 503, message: \"Statement cancelled due to timeout\"}"); - } } @@ -15899,4 +15896,114 @@ public void testLastTrustRoleUpdatesTimestampException() throws Exception { assertEquals(jdbcConn.lastTrustRoleUpdatesTimestamp(), 0); jdbcConn.close(); } + + @Test + public void testListDomainGroupMembers() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(3); // domain id + + // domain group members + Mockito.when(mockResultSet.next()) + .thenReturn(true) // get domain id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + + Mockito.when(mockResultSet.getString(1)) + .thenReturn("admin") + .thenReturn("reader") + .thenReturn("writer"); + Mockito.when(mockResultSet.getString(2)) + .thenReturn("user.joe") + .thenReturn("user.jane") + .thenReturn("user.joe"); + Mockito.when(mockResultSet.getTimestamp(3)) + .thenReturn(new java.sql.Timestamp(1454358916000L)) + .thenReturn(null) + .thenReturn(null); + Mockito.when(mockResultSet.getTimestamp(4)) + .thenReturn(new java.sql.Timestamp(1454358916000L)) + .thenReturn(null) + .thenReturn(null); + + DomainGroupMembers domainGroupMembers = jdbcConn.listDomainGroupMembers("athenz"); + List members = domainGroupMembers.getMembers(); + assertEquals(2, members.size()); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "athenz"); + // get role list + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 3); + + assertTrue(ZMSTestUtils.verifyDomainGroupMember(members, "user.joe", "admin", "writer")); + assertTrue(ZMSTestUtils.verifyDomainGroupMember(members, "user.jane", "reader")); + assertTrue(ZMSTestUtils.verifyDomainGroupMemberTimestamp(members, "user.joe", "admin", + Timestamp.fromMillis(1454358916000L))); + } + + @Test + public void testListDomainGroupMembersException() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + Mockito.when(mockResultSet.next()) + .thenReturn(true); // get domain id + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + + try { + jdbcConn.listDomainGroupMembers("athenz"); + fail(); + } catch (Exception ignored) { + } + + // get principal id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "athenz"); + // get role list + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + } + + @Test + public void testListDomainGroupMembersInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listDomainGroupMembers("athenz"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.NOT_FOUND); + } + } + + @Test + public void testListDomainGroupMembersNoEntries() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(3); // domain id + + // domain role members + Mockito.when(mockResultSet.next()) + .thenReturn(true) // get domain id + .thenReturn(false); + + DomainGroupMembers domainGroupMembers = jdbcConn.listDomainGroupMembers("athenz"); + assertNull(domainGroupMembers.getMembers()); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "athenz"); + // get role list + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 3); + } }