fix: usermemberships in authz (#1288)

* fix: usermemberships in authz

* fix: tests

* fix: migration

* fix: handler
This commit is contained in:
Fabi
2021-02-15 16:26:58 +01:00
committed by GitHub
parent c3bec78db4
commit 33534ab006
16 changed files with 693 additions and 127 deletions

View File

@@ -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) }()

View File

@@ -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, ""
}

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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"})

View File

@@ -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
}