mirror of
https://github.com/pgsty/minio.git
synced 2026-09-15 23:14:04 +03:00
7b4cacc392
Signed-off-by: Feng Ruohang <rh@vonng.com>
165 lines
4.5 KiB
Go
165 lines
4.5 KiB
Go
// Copyright (c) 2026 PGSTY
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"maps"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/set"
|
|
)
|
|
|
|
type (
|
|
iamGroupGrantsKey struct{}
|
|
iamGroupMutationKey struct{}
|
|
iamGroupMutation struct {
|
|
Members []string
|
|
Remove bool
|
|
StatusOnly bool
|
|
}
|
|
)
|
|
|
|
// Merge the intended mutation with the record read under the distributed
|
|
// revision lock, not the older cache used to prepare the request.
|
|
func mergeIAMGroupMutation(ctx context.Context, previous GroupInfo, next *GroupInfo) {
|
|
op, ok := ctx.Value(iamGroupMutationKey{}).(iamGroupMutation)
|
|
if !ok || previous.Deleted || previous.Version == 0 {
|
|
return
|
|
}
|
|
members := set.CreateStringSet(previous.Members...)
|
|
grants := maps.Clone(previous.MemberGrants)
|
|
if grants == nil {
|
|
grants = make(map[string]time.Time)
|
|
}
|
|
switch {
|
|
case op.StatusOnly:
|
|
// Only the status changes.
|
|
case op.Remove:
|
|
for _, member := range op.Members {
|
|
members.Remove(member)
|
|
delete(grants, member)
|
|
}
|
|
next.Status = previous.Status
|
|
default:
|
|
requested := set.CreateStringSet(next.Members...)
|
|
for _, member := range op.Members {
|
|
if !requested.Contains(member) {
|
|
continue
|
|
}
|
|
at := next.MemberGrants[member]
|
|
if at.Before(grants[member]) {
|
|
continue
|
|
}
|
|
members.Add(member)
|
|
grants[member] = at
|
|
}
|
|
next.Status = previous.Status
|
|
}
|
|
next.Members, next.MemberGrants = members.ToSlice(), grants
|
|
slices.Sort(next.Members)
|
|
}
|
|
|
|
// A non-nil map is supplied by the versioned peer envelope, including for
|
|
// snapshots. Missing times are unknown, never the snapshot's newer timestamp.
|
|
func withIAMGroupGrants(ctx context.Context, grants map[string]time.Time) context.Context {
|
|
return context.WithValue(ctx, iamGroupGrantsKey{}, grants)
|
|
}
|
|
|
|
func (c *iamCache) effectiveGroupMembers(gi GroupInfo) []string {
|
|
var members []string
|
|
for _, member := range gi.Members {
|
|
if c.groupMemberAllowed(member, gi.MemberGrants[member], gi.RevokedBefore) {
|
|
members = append(members, member)
|
|
}
|
|
}
|
|
return members
|
|
}
|
|
|
|
func (c *iamCache) effectiveUserGroups(user string) []string {
|
|
var groups []string
|
|
for group := range c.iamUserGroupMemberships[user] {
|
|
gi, ok := c.iamGroupsMap[group]
|
|
r := c.revisions.get(getGroupInfoPath(group))
|
|
if r.RevokedBefore.After(gi.RevokedBefore) {
|
|
gi.RevokedBefore = r.RevokedBefore
|
|
}
|
|
if ok && !r.Deleted && c.groupMemberAllowed(user, gi.MemberGrants[user], gi.RevokedBefore) {
|
|
groups = append(groups, group)
|
|
}
|
|
}
|
|
return groups
|
|
}
|
|
|
|
func (c *iamCache) addGroupMembers(ctx context.Context, gi GroupInfo, members []string) (GroupInfo, error) {
|
|
grants, versioned := ctx.Value(iamGroupGrantsKey{}).(map[string]time.Time)
|
|
if boundary, ok := ctx.Value(iamRecordBoundaryKey{}).(time.Time); ok && boundary.After(gi.RevokedBefore) {
|
|
gi.RevokedBefore = boundary
|
|
}
|
|
origin, replicated := iamReplicationTime(ctx)
|
|
gi.Members = slices.Clone(gi.Members)
|
|
gi.MemberGrants = maps.Clone(gi.MemberGrants)
|
|
if gi.MemberGrants == nil {
|
|
gi.MemberGrants = make(map[string]time.Time)
|
|
}
|
|
current := set.CreateStringSet(gi.Members...)
|
|
gi.UpdatedAt = UTCNow()
|
|
if replicated {
|
|
gi.UpdatedAt = origin
|
|
}
|
|
for _, member := range members {
|
|
at := gi.UpdatedAt
|
|
r := c.userRevocation(member)
|
|
if replicated {
|
|
switch {
|
|
case versioned:
|
|
at = grants[member]
|
|
if at.After(origin) {
|
|
return gi, errInvalidArgument
|
|
}
|
|
case !r.RevokedBefore.IsZero() || r.Deleted || !gi.RevokedBefore.IsZero():
|
|
// Legacy snapshots cannot prove a post-revocation grant.
|
|
continue
|
|
case current.Contains(member):
|
|
continue
|
|
}
|
|
if !c.groupMemberAllowed(member, at, gi.RevokedBefore) {
|
|
continue
|
|
}
|
|
} else {
|
|
if current.Contains(member) && c.groupMemberAllowed(member, gi.MemberGrants[member], gi.RevokedBefore) {
|
|
continue // Editing the group is not reissuing every grant.
|
|
}
|
|
if !at.After(gi.RevokedBefore) {
|
|
at = gi.RevokedBefore.Add(time.Nanosecond)
|
|
}
|
|
if !at.After(r.RevokedBefore) {
|
|
at = r.RevokedBefore.Add(time.Nanosecond)
|
|
}
|
|
if !at.After(gi.MemberGrants[member]) {
|
|
at = gi.MemberGrants[member].Add(time.Nanosecond)
|
|
}
|
|
if at.After(gi.UpdatedAt) {
|
|
gi.UpdatedAt = at
|
|
}
|
|
}
|
|
u, ok := c.iamUsersMap[member]
|
|
if !ok {
|
|
return gi, errNoSuchUser
|
|
}
|
|
if u.Credentials.IsTemp() || u.Credentials.IsServiceAccount() {
|
|
return gi, errIAMActionNotAllowed
|
|
}
|
|
if previous := gi.MemberGrants[member]; previous.After(at) {
|
|
continue
|
|
}
|
|
current.Add(member)
|
|
gi.MemberGrants[member] = at
|
|
}
|
|
gi.Members = current.ToSlice()
|
|
slices.Sort(gi.Members)
|
|
return gi, nil
|
|
}
|