feat: impersonation roles (#7442)

* partial work done

* test IAM membership roles

* org membership tests

* console :(, translations and docs

* fix integration test

* fix tests

* add EnableImpersonation to security policy API

* fix integration test timestamp checking

* add security policy tests and fix projections

* add impersonation setting in console

* add security settings to the settings v2 API

* fix typo

* move impersonation to instance

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann
2024-02-28 12:21:11 +02:00
committed by GitHub
parent 68af4f59c9
commit 062d153cfe
60 changed files with 1624 additions and 144 deletions

View File

@@ -0,0 +1,322 @@
//go:build integration
package admin_test
import (
"context"
"testing"
"github.com/brianvoe/gofakeit/v6"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/integration"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
"github.com/zitadel/zitadel/pkg/grpc/member"
"github.com/zitadel/zitadel/pkg/grpc/object"
)
var iamRoles = []string{
"IAM_OWNER",
"IAM_OWNER_VIEWER",
"IAM_ORG_MANAGER",
"IAM_USER_MANAGER",
"IAM_ADMIN_IMPERSONATOR",
"IAM_END_USER_IMPERSONATOR",
}
func TestServer_ListIAMMemberRoles(t *testing.T) {
got, err := Client.ListIAMMemberRoles(AdminCTX, &admin_pb.ListIAMMemberRolesRequest{})
require.NoError(t, err)
assert.ElementsMatch(t, iamRoles, got.GetRoles())
}
func TestServer_ListIAMMembers(t *testing.T) {
user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *admin_pb.ListIAMMembersRequest
}
tests := []struct {
name string
args args
want *admin_pb.ListIAMMembersResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &admin_pb.ListIAMMembersRequest{
Query: &object.ListQuery{},
Queries: []*member.SearchQuery{{
Query: &member.SearchQuery_UserIdQuery{
UserIdQuery: &member.UserIDQuery{
UserId: user.GetUserId(),
},
},
}},
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: AdminCTX,
req: &admin_pb.ListIAMMembersRequest{
Query: &object.ListQuery{},
Queries: []*member.SearchQuery{{
Query: &member.SearchQuery_UserIdQuery{
UserIdQuery: &member.UserIDQuery{
UserId: user.GetUserId(),
},
},
}},
},
},
want: &admin_pb.ListIAMMembersResponse{
Result: []*member.Member{{
UserId: user.GetUserId(),
Roles: iamRoles,
}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.ListIAMMembers(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
wantResult := tt.want.GetResult()
gotResult := got.GetResult()
require.Len(t, gotResult, len(wantResult))
for i, want := range wantResult {
assert.Equal(t, want.GetUserId(), gotResult[i].GetUserId())
assert.ElementsMatch(t, want.GetRoles(), gotResult[i].GetRoles())
}
})
}
}
func TestServer_AddIAMMember(t *testing.T) {
user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email())
type args struct {
ctx context.Context
req *admin_pb.AddIAMMemberRequest
}
tests := []struct {
name string
args args
want *admin_pb.AddIAMMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: AdminCTX,
req: &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
want: &admin_pb.AddIAMMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "unknown roles error",
args: args{
ctx: AdminCTX,
req: &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"FOO", "BAR"},
},
},
wantErr: true,
},
{
name: "org role error",
args: args{
ctx: AdminCTX,
req: &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"ORG_OWNER"},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.AddIAMMember(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_UpdateIAMMember(t *testing.T) {
user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"IAM_OWNER"},
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *admin_pb.UpdateIAMMemberRequest
}
tests := []struct {
name string
args args
want *admin_pb.UpdateIAMMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &admin_pb.UpdateIAMMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: AdminCTX,
req: &admin_pb.UpdateIAMMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
want: &admin_pb.UpdateIAMMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Instance.InstanceID(),
ChangeDate: timestamppb.Now(),
},
},
},
{
name: "unknown roles error",
args: args{
ctx: AdminCTX,
req: &admin_pb.UpdateIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"FOO", "BAR"},
},
},
wantErr: true,
},
{
name: "org role error",
args: args{
ctx: AdminCTX,
req: &admin_pb.UpdateIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"ORG_OWNER"},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.UpdateIAMMember(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_RemoveIAMMember(t *testing.T) {
user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"IAM_OWNER"},
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *admin_pb.RemoveIAMMemberRequest
}
tests := []struct {
name string
args args
want *admin_pb.RemoveIAMMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &admin_pb.RemoveIAMMemberRequest{
UserId: user.GetUserId(),
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: AdminCTX,
req: &admin_pb.RemoveIAMMemberRequest{
UserId: user.GetUserId(),
},
},
want: &admin_pb.RemoveIAMMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Instance.InstanceID(),
ChangeDate: timestamppb.Now(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.RemoveIAMMember(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}

View File

@@ -119,7 +119,7 @@ func (s *Server) GetSecurityPolicy(ctx context.Context, req *admin_pb.GetSecurit
}
func (s *Server) SetSecurityPolicy(ctx context.Context, req *admin_pb.SetSecurityPolicyRequest) (*admin_pb.SetSecurityPolicyResponse, error) {
details, err := s.command.SetSecurityPolicy(ctx, req.EnableIframeEmbedding, req.AllowedOrigins)
details, err := s.command.SetSecurityPolicy(ctx, securityPolicyToCommand(req))
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/zitadel/zitadel/internal/api/grpc/object"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
@@ -173,7 +174,16 @@ func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy {
return &settings_pb.SecurityPolicy{
Details: obj_grpc.ToViewDetailsPb(policy.Sequence, policy.CreationDate, policy.ChangeDate, policy.AggregateID),
EnableIframeEmbedding: policy.Enabled,
EnableIframeEmbedding: policy.EnableIframeEmbedding,
AllowedOrigins: policy.AllowedOrigins,
EnableImpersonation: policy.EnableImpersonation,
}
}
func securityPolicyToCommand(req *admin_pb.SetSecurityPolicyRequest) *command.SecurityPolicy {
return &command.SecurityPolicy{
EnableIframeEmbedding: req.GetEnableIframeEmbedding(),
AllowedOrigins: req.GetAllowedOrigins(),
EnableImpersonation: req.GetEnableImpersonation(),
}
}

View File

@@ -0,0 +1,163 @@
//go:build integration
package admin_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
"github.com/zitadel/zitadel/pkg/grpc/object"
"github.com/zitadel/zitadel/pkg/grpc/settings"
)
func TestServer_GetSecurityPolicy(t *testing.T) {
_, err := Client.SetSecurityPolicy(AdminCTX, &admin_pb.SetSecurityPolicyRequest{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
EnableImpersonation: true,
})
require.NoError(t, err)
tests := []struct {
name string
ctx context.Context
want *admin_pb.GetSecurityPolicyResponse
wantErr bool
}{
{
name: "permission error",
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
wantErr: true,
},
{
name: "success",
ctx: AdminCTX,
want: &admin_pb.GetSecurityPolicyResponse{
Policy: &settings.SecurityPolicy{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
EnableImpersonation: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := Client.GetSecurityPolicy(tt.ctx, &admin_pb.GetSecurityPolicyRequest{})
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
got, want := resp.GetPolicy(), tt.want.GetPolicy()
assert.Equal(t, want.GetEnableIframeEmbedding(), got.GetEnableIframeEmbedding(), "enable iframe embedding")
assert.Equal(t, want.GetAllowedOrigins(), got.GetAllowedOrigins(), "allowed origins")
assert.Equal(t, want.GetEnableImpersonation(), got.GetEnableImpersonation(), "enable impersonation")
})
}
}
func TestServer_SetSecurityPolicy(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.SetSecurityPolicyRequest
}
tests := []struct {
name string
args args
want *admin_pb.SetSecurityPolicyResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &admin_pb.SetSecurityPolicyRequest{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
EnableImpersonation: true,
},
},
wantErr: true,
},
{
name: "success allowed origins",
args: args{
ctx: AdminCTX,
req: &admin_pb.SetSecurityPolicyRequest{
AllowedOrigins: []string{"foo.com", "bar.com"},
},
},
want: &admin_pb.SetSecurityPolicyResponse{
Details: &object.ObjectDetails{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success iframe embedding",
args: args{
ctx: AdminCTX,
req: &admin_pb.SetSecurityPolicyRequest{
EnableIframeEmbedding: true,
},
},
want: &admin_pb.SetSecurityPolicyResponse{
Details: &object.ObjectDetails{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success impersonation",
args: args{
ctx: AdminCTX,
req: &admin_pb.SetSecurityPolicyRequest{
EnableImpersonation: true,
},
},
want: &admin_pb.SetSecurityPolicyResponse{
Details: &object.ObjectDetails{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success all",
args: args{
ctx: AdminCTX,
req: &admin_pb.SetSecurityPolicyRequest{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
EnableImpersonation: true,
},
},
want: &admin_pb.SetSecurityPolicyResponse{
Details: &object.ObjectDetails{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.SetSecurityPolicy(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}

View File

@@ -12,11 +12,13 @@ import (
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/integration"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
)
var (
AdminCTX, SystemCTX context.Context
Tester *integration.Tester
CTX, AdminCTX, SystemCTX context.Context
Tester *integration.Tester
Client admin_pb.AdminServiceClient
)
func TestMain(m *testing.M) {
@@ -27,9 +29,10 @@ func TestMain(m *testing.M) {
Tester = integration.NewTester(ctx)
defer Tester.Done()
CTX = ctx
AdminCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
Client = Tester.Client.Admin
return m.Run()
}())
}