mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-18 09:03:23 +00:00
fix: usermemberships in authz (#1288)
* fix: usermemberships in authz * fix: tests * fix: migration * fix: handler
This commit is contained in:
@@ -36,6 +36,27 @@ type Grant struct {
|
||||
Roles []string
|
||||
}
|
||||
|
||||
type Memberships []*Membership
|
||||
|
||||
type Membership struct {
|
||||
MemberType MemberType
|
||||
AggregateID string
|
||||
//ObjectID differs from aggregate id if obejct is sub of an aggregate
|
||||
ObjectID string
|
||||
|
||||
Roles []string
|
||||
}
|
||||
|
||||
type MemberType int32
|
||||
|
||||
const (
|
||||
MemberTypeUnspecified MemberType = iota
|
||||
MemberTypeOrganisation
|
||||
MemberTypeProject
|
||||
MemberTypeProjectGrant
|
||||
MemberTypeIam
|
||||
)
|
||||
|
||||
func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID string, t *TokenVerifier, method string) (_ CtxData, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
@@ -2,7 +2,6 @@ package authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
@@ -16,41 +15,43 @@ func getUserMethodPermissions(ctx context.Context, t *TokenVerifier, requiredPer
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, dataKey, ctxData)
|
||||
grant, err := t.ResolveGrant(ctx)
|
||||
memberships, err := t.SearchMyMemberships(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if grant == nil {
|
||||
if len(memberships) == 0 {
|
||||
return requestedPermissions, nil, nil
|
||||
}
|
||||
requestedPermissions, allPermissions = mapGrantToPermissions(requiredPerm, grant, authConfig)
|
||||
requestedPermissions, allPermissions = mapMembershipsToPermissions(requiredPerm, memberships, authConfig)
|
||||
return requestedPermissions, allPermissions, nil
|
||||
}
|
||||
|
||||
func mapGrantToPermissions(requiredPerm string, grant *Grant, authConfig Config) (requestPermissions, allPermissions []string) {
|
||||
func mapMembershipsToPermissions(requiredPerm string, memberships []*Membership, authConfig Config) (requestPermissions, allPermissions []string) {
|
||||
requestPermissions = make([]string, 0)
|
||||
allPermissions = make([]string, 0)
|
||||
for _, role := range grant.Roles {
|
||||
requestPermissions, allPermissions = mapRoleToPerm(requiredPerm, role, authConfig, requestPermissions, allPermissions)
|
||||
for _, membership := range memberships {
|
||||
requestPermissions, allPermissions = mapMembershipToPerm(requiredPerm, membership, authConfig, requestPermissions, allPermissions)
|
||||
}
|
||||
|
||||
return requestPermissions, allPermissions
|
||||
}
|
||||
|
||||
func mapRoleToPerm(requiredPerm, actualRole string, authConfig Config, requestPermissions, allPermissions []string) ([]string, []string) {
|
||||
roleName, roleContextID := SplitPermission(actualRole)
|
||||
perms := authConfig.getPermissionsFromRole(roleName)
|
||||
func mapMembershipToPerm(requiredPerm string, membership *Membership, authConfig Config, requestPermissions, allPermissions []string) ([]string, []string) {
|
||||
roleNames, roleContextID := roleWithContext(membership)
|
||||
for _, roleName := range roleNames {
|
||||
perms := authConfig.getPermissionsFromRole(roleName)
|
||||
|
||||
for _, p := range perms {
|
||||
permWithCtx := addRoleContextIDToPerm(p, roleContextID)
|
||||
if !ExistsPerm(allPermissions, permWithCtx) {
|
||||
allPermissions = append(allPermissions, permWithCtx)
|
||||
}
|
||||
for _, p := range perms {
|
||||
permWithCtx := addRoleContextIDToPerm(p, roleContextID)
|
||||
if !ExistsPerm(allPermissions, permWithCtx) {
|
||||
allPermissions = append(allPermissions, permWithCtx)
|
||||
}
|
||||
|
||||
p, _ = SplitPermission(p)
|
||||
if p == requiredPerm {
|
||||
if !ExistsPerm(requestPermissions, permWithCtx) {
|
||||
requestPermissions = append(requestPermissions, permWithCtx)
|
||||
p, _ = SplitPermission(p)
|
||||
if p == requiredPerm {
|
||||
if !ExistsPerm(requestPermissions, permWithCtx) {
|
||||
requestPermissions = append(requestPermissions, permWithCtx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,3 +73,10 @@ func ExistsPerm(existingPermissions []string, perm string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func roleWithContext(membership *Membership) (roles []string, ctxID string) {
|
||||
if membership.MemberType == MemberTypeProject || membership.MemberType == MemberTypeProjectGrant {
|
||||
return membership.Roles, membership.ObjectID
|
||||
}
|
||||
return membership.Roles, ""
|
||||
}
|
||||
|
@@ -12,15 +12,14 @@ func getTestCtx(userID, orgID string) context.Context {
|
||||
}
|
||||
|
||||
type testVerifier struct {
|
||||
grant *Grant
|
||||
memberships []*Membership
|
||||
}
|
||||
|
||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, error) {
|
||||
return "userID", "agentID", "de", nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) ResolveGrants(ctx context.Context) (*Grant, error) {
|
||||
return v.grant, nil
|
||||
func (v *testVerifier) SearchMyMemberships(ctx context.Context) ([]*Membership, error) {
|
||||
return v.memberships, nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
|
||||
@@ -65,8 +64,10 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
name: "Empty Context",
|
||||
args: args{
|
||||
ctxData: CtxData{},
|
||||
verifier: Start(&testVerifier{grant: &Grant{
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
verifier: Start(&testVerifier{memberships: []*Membership{
|
||||
{
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
}}),
|
||||
requiredPerm: "project.read",
|
||||
authConfig: Config{
|
||||
@@ -90,7 +91,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
name: "No Grants",
|
||||
args: args{
|
||||
ctxData: CtxData{},
|
||||
verifier: Start(&testVerifier{grant: &Grant{}}),
|
||||
verifier: Start(&testVerifier{memberships: []*Membership{}}),
|
||||
requiredPerm: "project.read",
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
@@ -111,8 +112,13 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
name: "Get Permissions",
|
||||
args: args{
|
||||
ctxData: CtxData{UserID: "userID", OrgID: "orgID"},
|
||||
verifier: Start(&testVerifier{grant: &Grant{
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
verifier: Start(&testVerifier{memberships: []*Membership{
|
||||
{
|
||||
AggregateID: "IAM",
|
||||
ObjectID: "IAM",
|
||||
MemberType: MemberTypeIam,
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
}}),
|
||||
requiredPerm: "project.read",
|
||||
authConfig: Config{
|
||||
@@ -150,10 +156,10 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
func Test_MapMembershipToPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
grant *Grant
|
||||
membership []*Membership
|
||||
authConfig Config
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -166,7 +172,14 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
name: "One Role existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grant: &Grant{Roles: []string{"ORG_OWNER"}},
|
||||
membership: []*Membership{
|
||||
{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -187,7 +200,14 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
name: "One Role not existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.write",
|
||||
grant: &Grant{Roles: []string{"ORG_OWNER"}},
|
||||
membership: []*Membership{
|
||||
{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -208,7 +228,20 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
name: "Multiple Roles one existing",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grant: &Grant{Roles: []string{"ORG_OWNER", "IAM_OWNER"}},
|
||||
membership: []*Membership{
|
||||
{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
{
|
||||
AggregateID: "IAM",
|
||||
ObjectID: "IAM",
|
||||
MemberType: MemberTypeIam,
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -229,7 +262,20 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
name: "Multiple Roles, global and specific",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grant: &Grant{Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}},
|
||||
membership: []*Membership{
|
||||
{
|
||||
AggregateID: "2",
|
||||
ObjectID: "2",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeProject,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -249,7 +295,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
requestPerms, allPerms := mapGrantToPermissions(tt.args.requiredPerm, tt.args.grant, tt.args.authConfig)
|
||||
requestPerms, allPerms := mapMembershipsToPermissions(tt.args.requiredPerm, tt.args.membership, tt.args.authConfig)
|
||||
if !equalStringArray(requestPerms, tt.requestPerms) {
|
||||
t.Errorf("got wrong requestPerms, expecting: %v, actual: %v ", tt.requestPerms, requestPerms)
|
||||
}
|
||||
@@ -260,10 +306,10 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapRoleToPerm(t *testing.T) {
|
||||
func Test_MapMembershipToPerm(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
actualRole string
|
||||
membership *Membership
|
||||
authConfig Config
|
||||
requestPerms []string
|
||||
allPerms []string
|
||||
@@ -278,7 +324,12 @@ func Test_MapRoleToPerm(t *testing.T) {
|
||||
name: "first perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
membership: &Membership{
|
||||
AggregateID: "Org",
|
||||
ObjectID: "Org",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -301,7 +352,12 @@ func Test_MapRoleToPerm(t *testing.T) {
|
||||
name: "existing perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
membership: &Membership{
|
||||
AggregateID: "Org",
|
||||
ObjectID: "Org",
|
||||
MemberType: MemberTypeOrganisation,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -324,7 +380,12 @@ func Test_MapRoleToPerm(t *testing.T) {
|
||||
name: "first perm with context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
membership: &Membership{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeProject,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -347,7 +408,12 @@ func Test_MapRoleToPerm(t *testing.T) {
|
||||
name: "perm with context id, existing global",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
membership: &Membership{
|
||||
AggregateID: "1",
|
||||
ObjectID: "1",
|
||||
MemberType: MemberTypeProject,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
authConfig: Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
{
|
||||
@@ -369,7 +435,7 @@ func Test_MapRoleToPerm(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
requestPerms, allPerms := mapRoleToPerm(tt.args.requiredPerm, tt.args.actualRole, tt.args.authConfig, tt.args.requestPerms, tt.args.allPerms)
|
||||
requestPerms, allPerms := mapMembershipToPerm(tt.args.requiredPerm, tt.args.membership, tt.args.authConfig, tt.args.requestPerms, tt.args.allPerms)
|
||||
if !equalStringArray(requestPerms, tt.requestPerms) {
|
||||
t.Errorf("got wrong requestPerms, expecting: %v, actual: %v ", tt.requestPerms, requestPerms)
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ type TokenVerifier struct {
|
||||
type authZRepo interface {
|
||||
VerifyAccessToken(ctx context.Context, token, clientID string) (userID, agentID, prefLang string, err error)
|
||||
VerifierClientID(ctx context.Context, name string) (clientID string, err error)
|
||||
ResolveGrants(ctx context.Context) (grant *Grant, err error)
|
||||
SearchMyMemberships(ctx context.Context) ([]*Membership, error)
|
||||
ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error)
|
||||
ExistsOrg(ctx context.Context, orgID string) error
|
||||
}
|
||||
@@ -86,11 +86,10 @@ func (v *TokenVerifier) clientIDFromMethod(ctx context.Context, method string) (
|
||||
v.clients.Store(prefix, c)
|
||||
return c.id, nil
|
||||
}
|
||||
|
||||
func (v *TokenVerifier) ResolveGrant(ctx context.Context) (_ *Grant, err error) {
|
||||
func (v *TokenVerifier) SearchMyMemberships(ctx context.Context) (_ []*Membership, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
return v.authZRepo.ResolveGrants(ctx)
|
||||
return v.authZRepo.SearchMyMemberships(ctx)
|
||||
}
|
||||
|
||||
func (v *TokenVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (_ string, _ []string, err error) {
|
||||
|
@@ -43,7 +43,7 @@ func Test_VerifyAccessToken(t *testing.T) {
|
||||
ctx: context.Background(),
|
||||
token: "Bearer AUTH",
|
||||
verifier: &TokenVerifier{
|
||||
authZRepo: &testVerifier{grant: &Grant{}},
|
||||
authZRepo: &testVerifier{memberships: []*Membership{}},
|
||||
clients: func() sync.Map {
|
||||
m := sync.Map{}
|
||||
m.Store("service", &client{name: "name"})
|
||||
|
@@ -24,9 +24,10 @@ type verifierMock struct{}
|
||||
func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, error) {
|
||||
return "", "", "", nil
|
||||
}
|
||||
func (v *verifierMock) ResolveGrants(ctx context.Context) (*authz.Grant, error) {
|
||||
func (v *verifierMock) SearchMyMemberships(ctx context.Context) ([]*authz.Membership, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (v *verifierMock) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user