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

View File

@@ -0,0 +1,327 @@
//go:build integration
package management_test
import (
"context"
"testing"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
"github.com/zitadel/zitadel/pkg/grpc/member"
"github.com/zitadel/zitadel/pkg/grpc/object"
)
var iamRoles = []string{
"SELF_MANAGEMENT_GLOBAL",
"ORG_OWNER",
"ORG_USER_MANAGER",
"ORG_OWNER_VIEWER",
"ORG_SETTINGS_MANAGER",
"ORG_USER_PERMISSION_EDITOR",
"ORG_PROJECT_PERMISSION_EDITOR",
"ORG_PROJECT_CREATOR",
"ORG_USER_SELF_MANAGER",
"ORG_ADMIN_IMPERSONATOR",
"ORG_END_USER_IMPERSONATOR",
}
func TestServer_ListOrgMemberRoles(t *testing.T) {
got, err := Client.ListOrgMemberRoles(OrgCTX, &mgmt_pb.ListOrgMemberRolesRequest{})
require.NoError(t, err)
assert.ElementsMatch(t, iamRoles, got.GetResult())
}
func TestServer_ListOrgMembers(t *testing.T) {
user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles[1:],
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *mgmt_pb.ListOrgMembersRequest
}
tests := []struct {
name string
args args
want *mgmt_pb.ListOrgMembersResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: CTX,
req: &mgmt_pb.ListOrgMembersRequest{
Query: &object.ListQuery{},
Queries: []*member.SearchQuery{{
Query: &member.SearchQuery_UserIdQuery{
UserIdQuery: &member.UserIDQuery{
UserId: user.GetUserId(),
},
},
}},
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.ListOrgMembersRequest{
Query: &object.ListQuery{},
Queries: []*member.SearchQuery{{
Query: &member.SearchQuery_UserIdQuery{
UserIdQuery: &member.UserIDQuery{
UserId: user.GetUserId(),
},
},
}},
},
},
want: &mgmt_pb.ListOrgMembersResponse{
Result: []*member.Member{{
UserId: user.GetUserId(),
Roles: iamRoles[1:],
}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.ListOrgMembers(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_AddOrgMember(t *testing.T) {
user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email())
type args struct {
ctx context.Context
req *mgmt_pb.AddOrgMemberRequest
}
tests := []struct {
name string
args args
want *mgmt_pb.AddOrgMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: CTX,
req: &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles[1:],
},
},
want: &mgmt_pb.AddOrgMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "unknown roles error",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"FOO", "BAR"},
},
},
wantErr: true,
},
{
name: "iam role error",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"IAM_OWNER"},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.AddOrgMember(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_UpdateOrgMember(t *testing.T) {
user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"ORG_OWNER"},
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *mgmt_pb.UpdateOrgMemberRequest
}
tests := []struct {
name string
args args
want *mgmt_pb.UpdateOrgMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: CTX,
req: &mgmt_pb.UpdateOrgMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles,
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.UpdateOrgMemberRequest{
UserId: user.GetUserId(),
Roles: iamRoles[1:],
},
},
want: &mgmt_pb.UpdateOrgMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Organisation.ID,
ChangeDate: timestamppb.Now(),
},
},
},
{
name: "unknown roles error",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.UpdateOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"FOO", "BAR"},
},
},
wantErr: true,
},
{
name: "iam role error",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.UpdateOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"IAM_OWNER"},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.UpdateOrgMember(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(OrgCTX, Tester.Organisation.ID, gofakeit.Email())
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
UserId: user.GetUserId(),
Roles: []string{"ORG_OWNER"},
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *mgmt_pb.RemoveOrgMemberRequest
}
tests := []struct {
name string
args args
want *mgmt_pb.RemoveOrgMemberResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: CTX,
req: &mgmt_pb.RemoveOrgMemberRequest{
UserId: user.GetUserId(),
},
},
wantErr: true,
},
{
name: "success",
args: args{
ctx: OrgCTX,
req: &mgmt_pb.RemoveOrgMemberRequest{
UserId: user.GetUserId(),
},
},
want: &mgmt_pb.RemoveOrgMemberResponse{
Details: &object.ObjectDetails{
ResourceOwner: Tester.Organisation.ID,
ChangeDate: timestamppb.Now(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.RemoveOrgMember(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

@@ -0,0 +1,34 @@
//go:build integration
package management_test
import (
"context"
"os"
"testing"
"time"
"github.com/zitadel/zitadel/internal/integration"
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
var (
CTX, OrgCTX context.Context
Tester *integration.Tester
Client mgmt_pb.ManagementServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, _, cancel := integration.Contexts(3 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
CTX = ctx
OrgCTX = Tester.WithAuthorization(ctx, integration.OrgOwner)
Client = Tester.Client.Mgmt
return m.Run()
}())
}

View File

@@ -3,8 +3,6 @@
package management_test
import (
"context"
"os"
"strconv"
"strings"
"testing"
@@ -20,26 +18,6 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/user"
)
var (
CTX context.Context
Tester *integration.Tester
Client management.ManagementServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, errCtx, cancel := integration.Contexts(3 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
Client = Tester.Client.Mgmt
return m.Run()
}())
}
// TestImport_and_Get reproduces https://github.com/zitadel/zitadel/issues/5808
// which led to consistency issues due the call timestamp not being
// updated after a bulk Trigger.
@@ -57,7 +35,7 @@ func TestImport_and_Get(t *testing.T) {
userName := strings.Join([]string{firstName, lastName}, "_")
email := strings.Join([]string{userName, "example.com"}, "@")
res, err := Client.ImportHumanUser(CTX, &management.ImportHumanUserRequest{
res, err := Client.ImportHumanUser(OrgCTX, &management.ImportHumanUserRequest{
UserName: userName,
Profile: &management.ImportHumanUserRequest_Profile{
FirstName: firstName,
@@ -72,7 +50,7 @@ func TestImport_and_Get(t *testing.T) {
})
require.NoError(t, err)
_, err = Client.GetUserByID(CTX, &management.GetUserByIDRequest{Id: res.GetUserId()})
_, err = Client.GetUserByID(OrgCTX, &management.GetUserByIDRequest{Id: res.GetUserId()})
s, ok := status.FromError(err)
if ok && s != nil && s.Code() == codes.NotFound {
@@ -85,7 +63,7 @@ func TestImport_and_Get(t *testing.T) {
func TestImport_UnparsablePreferredLanguage(t *testing.T) {
random := integration.RandString(5)
_, err := Client.ImportHumanUser(CTX, &management.ImportHumanUserRequest{
_, err := Client.ImportHumanUser(OrgCTX, &management.ImportHumanUserRequest{
UserName: random,
Profile: &management.ImportHumanUserRequest_Profile{
FirstName: random,

View File

@@ -13,6 +13,7 @@ import (
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -183,6 +184,7 @@ func TestServer_CreateCallback(t *testing.T) {
want: &oidc_pb.CreateCallbackResponse{
CallbackUrl: regexp.QuoteMeta(`oidcintegrationtest://callback?error=access_denied&error_description=nope&error_uri=https%3A%2F%2Fexample.com%2Fdocs&state=state`),
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
@@ -206,6 +208,7 @@ func TestServer_CreateCallback(t *testing.T) {
want: &oidc_pb.CreateCallbackResponse{
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
@@ -231,6 +234,7 @@ func TestServer_CreateCallback(t *testing.T) {
want: &oidc_pb.CreateCallbackResponse{
CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`,
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},

View File

@@ -210,6 +210,10 @@ func (m *mockInstance) SecurityPolicyAllowedOrigins() []string {
return nil
}
func (m *mockInstance) EnableImpersonation() bool {
return false
}
func (m *mockInstance) Features() feature.Features {
return feature.Features{}
}

View File

@@ -16,6 +16,7 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -164,6 +165,7 @@ func TestServer_CreateSession(t *testing.T) {
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
@@ -183,6 +185,7 @@ func TestServer_CreateSession(t *testing.T) {
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
@@ -211,6 +214,7 @@ func TestServer_CreateSession(t *testing.T) {
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
@@ -230,6 +234,7 @@ func TestServer_CreateSession(t *testing.T) {
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},

View File

@@ -0,0 +1,34 @@
//go:build integration
package settings_test
import (
"context"
"os"
"testing"
"time"
"github.com/zitadel/zitadel/internal/integration"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
)
var (
CTX, AdminCTX context.Context
Tester *integration.Tester
Client settings.SettingsServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, _, cancel := integration.Contexts(3 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
CTX = ctx
AdminCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
Client = Tester.Client.SettingsV2
return m.Run()
}())
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/query"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
)
func (s *Server) GetLoginSettings(ctx context.Context, req *settings.GetLoginSettingsRequest) (*settings.GetLoginSettingsResponse, error) {
@@ -124,3 +124,23 @@ func (s *Server) GetGeneralSettings(ctx context.Context, _ *settings.GetGeneralS
DefaultLanguage: instance.DefaultLanguage().String(),
}, nil
}
func (s *Server) GetSecuritySettings(ctx context.Context, req *settings.GetSecuritySettingsRequest) (*settings.GetSecuritySettingsResponse, error) {
policy, err := s.query.SecurityPolicy(ctx)
if err != nil {
return nil, err
}
return &settings.GetSecuritySettingsResponse{
Settings: securityPolicyToSettingsPb(policy),
}, nil
}
func (s *Server) SetSecuritySettings(ctx context.Context, req *settings.SetSecuritySettingsRequest) (*settings.SetSecuritySettingsResponse, error) {
details, err := s.command.SetSecurityPolicy(ctx, securitySettingsToCommand(req))
if err != nil {
return nil, err
}
return &settings.SetSecuritySettingsResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}

View File

@@ -3,6 +3,7 @@ package settings
import (
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
@@ -205,3 +206,21 @@ func idpTypeToPb(idpType domain.IDPType) settings.IdentityProviderType {
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED
}
}
func securityPolicyToSettingsPb(policy *query.SecurityPolicy) *settings.SecuritySettings {
return &settings.SecuritySettings{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: policy.EnableIframeEmbedding,
AllowedOrigins: policy.AllowedOrigins,
},
EnableImpersonation: policy.EnableImpersonation,
}
}
func securitySettingsToCommand(req *settings.SetSecuritySettingsRequest) *command.SecurityPolicy {
return &command.SecurityPolicy{
EnableIframeEmbedding: req.GetEmbeddedIframe().GetEnabled(),
AllowedOrigins: req.GetEmbeddedIframe().GetAllowedOrigins(),
EnableImpersonation: req.GetEnableImpersonation(),
}
}

View File

@@ -12,6 +12,7 @@ import (
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/api/grpc"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
@@ -450,3 +451,35 @@ func Test_idpTypeToPb(t *testing.T) {
})
}
}
func Test_securityPolicyToSettingsPb(t *testing.T) {
want := &settings.SecuritySettings{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo", "bar"},
},
EnableImpersonation: true,
}
got := securityPolicyToSettingsPb(&query.SecurityPolicy{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo", "bar"},
EnableImpersonation: true,
})
assert.Equal(t, want, got)
}
func Test_securitySettingsToCommand(t *testing.T) {
want := &command.SecurityPolicy{
EnableIframeEmbedding: true,
AllowedOrigins: []string{"foo", "bar"},
EnableImpersonation: true,
}
got := securitySettingsToCommand(&settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo", "bar"},
},
EnableImpersonation: true,
})
assert.Equal(t, want, got)
}

View File

@@ -0,0 +1,174 @@
//go:build integration
package settings_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"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
)
func TestServer_GetSecuritySettings(t *testing.T) {
_, err := Client.SetSecuritySettings(AdminCTX, &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo", "bar"},
},
EnableImpersonation: true,
})
require.NoError(t, err)
tests := []struct {
name string
ctx context.Context
want *settings.GetSecuritySettingsResponse
wantErr bool
}{
{
name: "permission error",
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
wantErr: true,
},
{
name: "success",
ctx: AdminCTX,
want: &settings.GetSecuritySettingsResponse{
Settings: &settings.SecuritySettings{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo", "bar"},
},
EnableImpersonation: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := Client.GetSecuritySettings(tt.ctx, &settings.GetSecuritySettingsRequest{})
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
got, want := resp.GetSettings(), tt.want.GetSettings()
assert.Equal(t, want.GetEmbeddedIframe().GetEnabled(), got.GetEmbeddedIframe().GetEnabled(), "enable iframe embedding")
assert.Equal(t, want.GetEmbeddedIframe().GetAllowedOrigins(), got.GetEmbeddedIframe().GetAllowedOrigins(), "allowed origins")
assert.Equal(t, want.GetEnableImpersonation(), got.GetEnableImpersonation(), "enable impersonation")
})
}
}
func TestServer_SetSecuritySettings(t *testing.T) {
type args struct {
ctx context.Context
req *settings.SetSecuritySettingsRequest
}
tests := []struct {
name string
args args
want *settings.SetSecuritySettingsResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Tester.WithAuthorization(CTX, integration.OrgOwner),
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
},
EnableImpersonation: true,
},
},
wantErr: true,
},
{
name: "success allowed origins",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
AllowedOrigins: []string{"foo.com", "bar.com"},
},
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success enable iframe embedding",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
},
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success impersonation",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EnableImpersonation: true,
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "success all",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
},
EnableImpersonation: true,
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.SetSecuritySettings(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

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -216,6 +217,7 @@ func TestServer_AddOTPEmail(t *testing.T) {
},
want: &user.AddOTPEmailResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -282,6 +284,7 @@ func TestServer_RemoveOTPEmail(t *testing.T) {
},
want: &user.RemoveOTPEmailResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ResourceOwner,
},
},

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -58,6 +59,7 @@ func TestServer_RegisterPasskey(t *testing.T) {
},
want: &user.RegisterPasskeyResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -109,6 +111,7 @@ func TestServer_RegisterPasskey(t *testing.T) {
},
want: &user.RegisterPasskeyResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -187,6 +190,7 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) {
},
want: &user.VerifyPasskeyRegistrationResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -253,6 +257,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) {
},
want: &user.CreatePasskeyRegistrationLinkResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -272,6 +277,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) {
},
want: &user.CreatePasskeyRegistrationLinkResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -287,6 +293,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) {
},
want: &user.CreatePasskeyRegistrationLinkResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},

View File

@@ -143,6 +143,7 @@ func TestServer_SetPassword(t *testing.T) {
},
want: &user.SetPasswordResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -173,6 +174,7 @@ func TestServer_SetPassword(t *testing.T) {
},
want: &user.SetPasswordResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -206,6 +208,7 @@ func TestServer_SetPassword(t *testing.T) {
},
want: &user.SetPasswordResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},

View File

@@ -10,6 +10,7 @@ import (
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -60,6 +61,7 @@ func TestServer_RegisterTOTP(t *testing.T) {
},
want: &user.RegisterTOTPResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -136,6 +138,7 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) {
},
want: &user.VerifyTOTPRegistrationResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ResourceOwner,
},
},

View File

@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -60,6 +61,7 @@ func TestServer_RegisterU2F(t *testing.T) {
},
want: &user.RegisterU2FResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
@@ -134,6 +136,7 @@ func TestServer_VerifyU2FRegistration(t *testing.T) {
},
want: &user.VerifyU2FRegistrationResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},

View File

@@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -22,8 +21,6 @@ import (
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
)
var ignoreTypes = []protoreflect.FullName{"google.protobuf.Duration", "google.protobuf.Struct"}
func Test_idpIntentToIDPIntentPb(t *testing.T) {
decryption := func(err error) crypto.EncryptionAlgorithm {
mCrypto := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t))