Skip to content

Commit

Permalink
Add Group Memberships (#323)
Browse files Browse the repository at this point in the history
* add and list group members
* remove members
* replace members
* update group memebers actions
* list user groups
* return only group IDs on list-user-groups
* add user-info-groups endpoint
* add tests
* Apply suggestions from code review

---------

Signed-off-by: Bailin He <[email protected]>
Signed-off-by: Bailin He <[email protected]>
Co-authored-by: Mike Mason <[email protected]>
  • Loading branch information
bailinhe and mikemrm authored Sep 30, 2024
1 parent 484ba82 commit f7ab15a
Show file tree
Hide file tree
Showing 13 changed files with 1,806 additions and 81 deletions.
2 changes: 1 addition & 1 deletion internal/api/httpsrv/handler_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (h *apiHandler) ListGroups(ctx context.Context, req ListGroupsRequestObject
return nil, permissionsError(err)
}

groups, err := h.engine.ListGroups(ctx, ownerID, req.Params)
groups, err := h.engine.ListGroupsByOwner(ctx, ownerID, req.Params)
if err != nil {
return nil, err
}
Expand Down
234 changes: 234 additions & 0 deletions internal/api/httpsrv/handler_group_members.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package httpsrv

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/labstack/echo/v4"
"go.infratographer.com/identity-api/internal/types"
v1 "go.infratographer.com/identity-api/pkg/api/v1"
"go.infratographer.com/permissions-api/pkg/permissions"
"go.infratographer.com/x/gidx"
)

const (
actionGroupMembersList = "iam_group_members_list"
actionGroupMembersAdd = "iam_group_members_add"
actionGroupMembersPut = "iam_group_members_put"
actionGroupMembersRemove = "iam_group_members_remove"
)

// AddGroupMembers creates a group
func (h *apiHandler) AddGroupMembers(ctx context.Context, req AddGroupMembersRequestObject) (AddGroupMembersResponseObject, error) {
reqbody := req.Body
gid := req.GroupID

if _, err := gidx.Parse(string(gid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid owner id: %s", err.Error()),
)

return nil, err
}

for _, mid := range reqbody.MemberIDs {
if _, err := gidx.Parse(string(mid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid member id %s: %s", mid, err.Error()),
)

return nil, err
}
}

if err := permissions.CheckAccess(ctx, gid, actionGroupMembersAdd); err != nil {
return nil, permissionsError(err)
}

if err := h.engine.AddMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

return AddGroupMembers200JSONResponse{Success: true}, nil
}

// ListGroupMembers lists the members of a group
func (h *apiHandler) ListGroupMembers(ctx context.Context, req ListGroupMembersRequestObject) (ListGroupMembersResponseObject, error) {
gid := req.GroupID

if _, err := gidx.Parse(string(gid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid group id: %s", err.Error()),
)

return nil, err
}

if err := permissions.CheckAccess(ctx, gid, actionGroupMembersList); err != nil {
return nil, permissionsError(err)
}

members, err := h.engine.ListMembers(ctx, gid, req.Params)
if err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

collection := v1.GroupMemberCollection{
MemberIDs: members,
GroupID: gid,
Pagination: v1.Pagination{},
}

if err := req.Params.SetPagination(&collection); err != nil {
return nil, err
}

return ListGroupMembers200JSONResponse{GroupMemberCollectionJSONResponse(collection)}, nil
}

// RemoveGroupMember removes a member from a group
func (h *apiHandler) RemoveGroupMember(ctx context.Context, req RemoveGroupMemberRequestObject) (RemoveGroupMemberResponseObject, error) {
gid := req.GroupID
sid := req.SubjectID

if _, err := gidx.Parse(string(gid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid group id: %s", err.Error()),
)

return nil, err
}

if _, err := gidx.Parse(string(sid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid member id: %s", err.Error()),
)
}

if _, err := gidx.Parse(string(sid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid member id: %s", err.Error()),
)

return nil, err
}

if err := permissions.CheckAccess(ctx, gid, actionGroupMembersRemove); err != nil {
return nil, permissionsError(err)
}

if err := h.engine.RemoveMember(ctx, gid, sid); err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

return RemoveGroupMember200JSONResponse{true}, nil
}

// ReplaceGroupMembers replaces the members of a group
func (h *apiHandler) ReplaceGroupMembers(ctx context.Context, req ReplaceGroupMembersRequestObject) (ReplaceGroupMembersResponseObject, error) {
gid := req.GroupID
reqbody := req.Body

if _, err := gidx.Parse(string(gid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid group id: %s", err.Error()),
)

return nil, err
}

for _, mid := range reqbody.MemberIDs {
if _, err := gidx.Parse(string(mid)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid member id %s: %s", mid, err.Error()),
)

return nil, err
}
}

if err := permissions.CheckAccess(ctx, gid, actionGroupMembersPut); err != nil {
return nil, permissionsError(err)
}

if err := h.engine.ReplaceMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

return ReplaceGroupMembers200JSONResponse{true}, nil
}

func (h *apiHandler) ListUserGroups(ctx context.Context, req ListUserGroupsRequestObject) (ListUserGroupsResponseObject, error) {
subject := req.UserID

if _, err := gidx.Parse(string(subject)); err != nil {
err = echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("invalid subject id: %s", err.Error()),
)

return nil, err
}

// Find the owner the user's issuer is on to check permissions.
ownerID, err := h.engine.LookupUserOwnerID(ctx, subject)
switch err {
case nil:
case types.ErrUserInfoNotFound:
return nil, echo.NewHTTPError(http.StatusNotFound, err.Error())
default:
return nil, err
}

if err := permissions.CheckAccess(ctx, ownerID, actionUserGet); err != nil {
return nil, permissionsError(err)
}

groups, err := h.engine.ListGroupsBySubject(ctx, subject, req.Params)
if err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

resp := groups.ToPrefixedIDs()

collection := v1.GroupIDCollection{
GroupIDs: resp,
Pagination: v1.Pagination{},
}

if err := req.Params.SetPagination(&collection); err != nil {
return nil, err
}

return ListUserGroups200JSONResponse{GroupIDCollectionJSONResponse(collection)}, nil
}
Loading

0 comments on commit f7ab15a

Please sign in to comment.