Skip to content

Commit

Permalink
implement domain group members api (#2641)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Avetisyan <[email protected]>
  • Loading branch information
havetisyan authored Jun 25, 2024
1 parent b68e846 commit 48efa78
Show file tree
Hide file tree
Showing 19 changed files with 560 additions and 4 deletions.
32 changes: 32 additions & 0 deletions clients/go/zms/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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), ""))
Expand Down
11 changes: 11 additions & 0 deletions clients/go/zms/zms_schema.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<GroupMember> memberGroups = new ArrayList<>();
memberGroups.add(memberGroup);

DomainGroupMember member = new DomainGroupMember();
member.setMemberName("athenz.api");
member.setMemberGroups(memberGroups);

List<DomainGroupMember> 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);
Expand Down
16 changes: 16 additions & 0 deletions core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
13 changes: 13 additions & 0 deletions core/zms/src/main/rdl/Group.rdli
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
13 changes: 13 additions & 0 deletions libs/go/zmscli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -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")
Expand Down
17 changes: 17 additions & 0 deletions libs/go/zmscli/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 6 additions & 0 deletions servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
34 changes: 34 additions & 0 deletions servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PrincipalGroup> listGroupsWithUserAuthorityRestrictions();

Expand Down
Loading

0 comments on commit 48efa78

Please sign in to comment.