Browse Source

iam: add group management handlers to standalone IAM API

Add group handlers (CreateGroup, DeleteGroup, GetGroup, ListGroups,
AddUserToGroup, RemoveUserFromGroup, AttachGroupPolicy, DetachGroupPolicy,
ListAttachedGroupPolicies, ListGroupsForUser) and wire into DoActions
dispatch. Also add helper functions for user/policy side effects.
pull/8560/head
Chris Lu 2 days ago
parent
commit
204512151b
  1. 282
      weed/iamapi/iamapi_group_handlers.go
  2. 70
      weed/iamapi/iamapi_management_handlers.go
  3. 11
      weed/iamapi/iamapi_response.go

282
weed/iamapi/iamapi_group_handlers.go

@ -0,0 +1,282 @@
package iamapi
import (
"fmt"
"net/url"
"strings"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)
func (iama *IamApiServer) CreateGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*CreateGroupResponse, *IamError) {
resp := &CreateGroupResponse{}
groupName := values.Get("GroupName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
return resp, &IamError{Code: iam.ErrCodeEntityAlreadyExistsException, Error: fmt.Errorf("group %s already exists", groupName)}
}
}
s3cfg.Groups = append(s3cfg.Groups, &iam_pb.Group{Name: groupName})
resp.CreateGroupResult.Group.GroupName = &groupName
return resp, nil
}
func (iama *IamApiServer) DeleteGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*DeleteGroupResponse, *IamError) {
resp := &DeleteGroupResponse{}
groupName := values.Get("GroupName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
for i, g := range s3cfg.Groups {
if g.Name == groupName {
if len(g.Members) > 0 {
return resp, &IamError{Code: iam.ErrCodeDeleteConflictException, Error: fmt.Errorf("cannot delete group %s: group has %d member(s)", groupName, len(g.Members))}
}
s3cfg.Groups = append(s3cfg.Groups[:i], s3cfg.Groups[i+1:]...)
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) GetGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*GetGroupResponse, *IamError) {
resp := &GetGroupResponse{}
groupName := values.Get("GroupName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
resp.GetGroupResult.Group.GroupName = &g.Name
for _, member := range g.Members {
m := member
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &iam.User{UserName: &m})
}
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) ListGroups(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) *ListGroupsResponse {
resp := &ListGroupsResponse{}
for _, g := range s3cfg.Groups {
name := g.Name
resp.ListGroupsResult.Groups = append(resp.ListGroupsResult.Groups, &iam.Group{GroupName: &name})
}
return resp
}
func (iama *IamApiServer) AddUserToGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*AddUserToGroupResponse, *IamError) {
resp := &AddUserToGroupResponse{}
groupName := values.Get("GroupName")
userName := values.Get("UserName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
if userName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
}
userFound := false
for _, ident := range s3cfg.Identities {
if ident.Name == userName {
userFound = true
break
}
}
if !userFound {
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
for _, m := range g.Members {
if m == userName {
return resp, nil
}
}
g.Members = append(g.Members, userName)
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) RemoveUserFromGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*RemoveUserFromGroupResponse, *IamError) {
resp := &RemoveUserFromGroupResponse{}
groupName := values.Get("GroupName")
userName := values.Get("UserName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
if userName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
for i, m := range g.Members {
if m == userName {
g.Members = append(g.Members[:i], g.Members[i+1:]...)
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s is not a member of group %s", userName, groupName)}
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) AttachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*AttachGroupPolicyResponse, *IamError) {
resp := &AttachGroupPolicyResponse{}
groupName := values.Get("GroupName")
policyArn := values.Get("PolicyArn")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
policyName, iamErr := parsePolicyArn(policyArn)
if iamErr != nil {
return resp, iamErr
}
// Verify policy exists
policyFound := false
for _, p := range s3cfg.Policies {
if p.Name == policyName {
policyFound = true
break
}
}
if !policyFound {
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s not found", policyName)}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
for _, p := range g.PolicyNames {
if p == policyName {
return resp, nil
}
}
g.PolicyNames = append(g.PolicyNames, policyName)
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) DetachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*DetachGroupPolicyResponse, *IamError) {
resp := &DetachGroupPolicyResponse{}
groupName := values.Get("GroupName")
policyArn := values.Get("PolicyArn")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
policyName, iamErr := parsePolicyArn(policyArn)
if iamErr != nil {
return resp, iamErr
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
for i, p := range g.PolicyNames {
if p == policyName {
g.PolicyNames = append(g.PolicyNames[:i], g.PolicyNames[i+1:]...)
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s is not attached to group %s", policyName, groupName)}
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) ListAttachedGroupPolicies(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*ListAttachedGroupPoliciesResponse, *IamError) {
resp := &ListAttachedGroupPoliciesResponse{}
groupName := values.Get("GroupName")
if groupName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
for _, policyName := range g.PolicyNames {
pn := policyName
policyArn := policyArnPrefix + pn
resp.ListAttachedGroupPoliciesResult.AttachedPolicies = append(resp.ListAttachedGroupPoliciesResult.AttachedPolicies, &iam.AttachedPolicy{
PolicyName: &pn,
PolicyArn: &policyArn,
})
}
return resp, nil
}
}
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (iama *IamApiServer) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*ListGroupsForUserResponse, *IamError) {
resp := &ListGroupsForUserResponse{}
userName := values.Get("UserName")
if userName == "" {
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
}
userFound := false
for _, ident := range s3cfg.Identities {
if ident.Name == userName {
userFound = true
break
}
}
if !userFound {
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
}
for _, g := range s3cfg.Groups {
for _, m := range g.Members {
if m == userName {
name := g.Name
resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
break
}
}
}
return resp, nil
}
// removeUserFromAllGroups removes a user from all groups they belong to.
func removeUserFromAllGroups(s3cfg *iam_pb.S3ApiConfiguration, userName string) {
for _, g := range s3cfg.Groups {
for i, m := range g.Members {
if m == userName {
g.Members = append(g.Members[:i], g.Members[i+1:]...)
break
}
}
}
}
// updateUserInGroups updates group membership references when a user is renamed.
func updateUserInGroups(s3cfg *iam_pb.S3ApiConfiguration, oldUserName, newUserName string) {
for _, g := range s3cfg.Groups {
for i, m := range g.Members {
if m == oldUserName {
g.Members[i] = newUserName
break
}
}
}
}
// isPolicyAttachedToAnyGroup checks if a policy is attached to any group.
func isPolicyAttachedToAnyGroup(s3cfg *iam_pb.S3ApiConfiguration, policyName string) (string, bool) {
for _, g := range s3cfg.Groups {
for _, p := range g.PolicyNames {
if p == policyName {
return g.Name, true
}
}
}
return "", false
}
// policyNameFromArn extracts policy name from ARN for standalone handlers.
func policyNameFromArn(policyArn string) string {
return strings.TrimPrefix(policyArn, policyArnPrefix)
}

70
weed/iamapi/iamapi_management_handlers.go

@ -1055,6 +1055,76 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
return
}
changed = false
// Group actions
case "CreateGroup":
var err *IamError
response, err = iama.CreateGroup(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "DeleteGroup":
var err *IamError
response, err = iama.DeleteGroup(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "GetGroup":
var err *IamError
response, err = iama.GetGroup(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
changed = false
case "ListGroups":
response = iama.ListGroups(s3cfg, values)
changed = false
case "AddUserToGroup":
var err *IamError
response, err = iama.AddUserToGroup(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "RemoveUserFromGroup":
var err *IamError
response, err = iama.RemoveUserFromGroup(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "AttachGroupPolicy":
var err *IamError
response, err = iama.AttachGroupPolicy(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "DetachGroupPolicy":
var err *IamError
response, err = iama.DetachGroupPolicy(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
case "ListAttachedGroupPolicies":
var err *IamError
response, err = iama.ListAttachedGroupPolicies(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
changed = false
case "ListGroupsForUser":
var err *IamError
response, err = iama.ListGroupsForUser(s3cfg, values)
if err != nil {
writeIamErrorResponse(w, r, reqID, err)
return
}
changed = false
default:
errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
errorResponse := newErrorResponse(errNotImplemented.Code, errNotImplemented.Description, reqID)

11
weed/iamapi/iamapi_response.go

@ -36,4 +36,15 @@ type (
ListServiceAccountsResponse = iamlib.ListServiceAccountsResponse
GetServiceAccountResponse = iamlib.GetServiceAccountResponse
UpdateServiceAccountResponse = iamlib.UpdateServiceAccountResponse
// Group response types
CreateGroupResponse = iamlib.CreateGroupResponse
DeleteGroupResponse = iamlib.DeleteGroupResponse
GetGroupResponse = iamlib.GetGroupResponse
ListGroupsResponse = iamlib.ListGroupsResponse
AddUserToGroupResponse = iamlib.AddUserToGroupResponse
RemoveUserFromGroupResponse = iamlib.RemoveUserFromGroupResponse
AttachGroupPolicyResponse = iamlib.AttachGroupPolicyResponse
DetachGroupPolicyResponse = iamlib.DetachGroupPolicyResponse
ListAttachedGroupPoliciesResponse = iamlib.ListAttachedGroupPoliciesResponse
ListGroupsForUserResponse = iamlib.ListGroupsForUserResponse
)
Loading…
Cancel
Save