mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-26 05:16:30 +00:00
feat(api): move organization settings endpoints to v2 (#10910)
# Which Problems Are Solved As part of our efforts to simplify the structure and versions of our APIs, were moving all existing v2beta endpoints to v2 and deprecate them. They will be removed in Zitadel V5. # How the Problems Are Solved - This PR adds the organization settings endpoints to the settings service v2. This was split from #10909, since that PR targets v4.x and the corresponding feature is not yet available in v4.x, but only v5. - The comments and have been improved and, where not already done, moved from swagger annotations to proto. # Additional Changes None # Additional Context - relates to #10909 - relates to #10772
This commit is contained in:
@@ -514,7 +514,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, session_v2.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, settings_v2.CreateServer(commands, queries)); err != nil {
|
||||
if err := apis.RegisterService(ctx, settings_v2.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, org_v2.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
@@ -427,3 +428,267 @@ func TestServer_GetHostedLoginTranslation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListOrganizationSettings(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(*settings.ListOrganizationSettingsRequest, *settings.ListOrganizationSettingsResponse)
|
||||
req *settings.ListOrganizationSettingsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *settings.ListOrganizationSettingsResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "list by id, unauthenticated",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list by id, no permission",
|
||||
args: args{
|
||||
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list by id, missing permission",
|
||||
args: args{
|
||||
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, not found",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{
|
||||
Filter: &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{"notexisting"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
settingsResp := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
response.OrganizationSettings[0] = &settings.OrganizationSettings{
|
||||
OrganizationId: orgResp.GetOrganizationId(),
|
||||
CreationDate: settingsResp.GetSetDate(),
|
||||
ChangeDate: settingsResp.GetSetDate(),
|
||||
OrganizationScopedUsernames: true,
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp1 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
settingsResp1 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp1.GetOrganizationId(), true)
|
||||
orgResp2 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
settingsResp2 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp2.GetOrganizationId(), true)
|
||||
orgResp3 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
settingsResp3 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp3.GetOrganizationId(), true)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp1.GetOrganizationId(), orgResp2.GetOrganizationId(), orgResp3.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
response.OrganizationSettings[2] = &settings.OrganizationSettings{
|
||||
OrganizationId: orgResp1.GetOrganizationId(),
|
||||
CreationDate: settingsResp1.GetSetDate(),
|
||||
ChangeDate: settingsResp1.GetSetDate(),
|
||||
OrganizationScopedUsernames: true,
|
||||
}
|
||||
response.OrganizationSettings[1] = &settings.OrganizationSettings{
|
||||
OrganizationId: orgResp2.GetOrganizationId(),
|
||||
CreationDate: settingsResp2.GetSetDate(),
|
||||
ChangeDate: settingsResp2.GetSetDate(),
|
||||
OrganizationScopedUsernames: true,
|
||||
}
|
||||
response.OrganizationSettings[0] = &settings.OrganizationSettings{
|
||||
OrganizationId: orgResp3.GetOrganizationId(),
|
||||
CreationDate: settingsResp3.GetSetDate(),
|
||||
ChangeDate: settingsResp3.GetSetDate(),
|
||||
OrganizationScopedUsernames: true,
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{{}, {}, {}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple id, only org scoped usernames",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
|
||||
orgResp1 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp1.GetOrganizationId(), false)
|
||||
orgResp2 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
settingsResp2 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp2.GetOrganizationId(), true)
|
||||
orgResp3 := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
||||
instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp3.GetOrganizationId(), false)
|
||||
|
||||
request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
|
||||
InOrganizationIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{orgResp1.GetOrganizationId(), orgResp2.GetOrganizationId(), orgResp3.GetOrganizationId()},
|
||||
},
|
||||
}
|
||||
request.Filters[1].Filter = &settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter{
|
||||
OrganizationScopedUsernamesFilter: &settings.OrganizationScopedUsernamesFilter{
|
||||
OrganizationScopedUsernames: true,
|
||||
},
|
||||
}
|
||||
response.OrganizationSettings[0] = &settings.OrganizationSettings{
|
||||
OrganizationId: orgResp2.GetOrganizationId(),
|
||||
CreationDate: settingsResp2.GetSetDate(),
|
||||
ChangeDate: settingsResp2.GetSetDate(),
|
||||
OrganizationScopedUsernames: true,
|
||||
}
|
||||
},
|
||||
req: &settings.ListOrganizationSettingsRequest{
|
||||
Filters: []*settings.OrganizationSettingsSearchFilter{{}, {}},
|
||||
},
|
||||
},
|
||||
want: &settings.ListOrganizationSettingsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
OrganizationSettings: []*settings.OrganizationSettings{{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
tt.args.dep(tt.args.req, tt.want)
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamOwnerCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := instance.Client.SettingsV2.ListOrganizationSettings(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, listErr)
|
||||
return
|
||||
}
|
||||
require.NoError(ttt, listErr)
|
||||
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.OrganizationSettings, len(tt.want.OrganizationSettings)) {
|
||||
for i := range tt.want.OrganizationSettings {
|
||||
assert.EqualExportedValues(ttt, tt.want.OrganizationSettings[i], got.OrganizationSettings[i])
|
||||
}
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationResponse, actual *filter.PaginationResponse) {
|
||||
assert.Equal(t, expected.AppliedLimit, actual.AppliedLimit)
|
||||
assert.Equal(t, expected.TotalResult, actual.TotalResult)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -189,3 +191,279 @@ func TestSetHostedLoginTranslation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetOrganizationSettings(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *settings.SetOrganizationSettingsRequest
|
||||
}
|
||||
type want struct {
|
||||
set bool
|
||||
setDate bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(req *settings.SetOrganizationSettingsRequest)
|
||||
args args
|
||||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "permission error",
|
||||
args: args{
|
||||
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
|
||||
req: &settings.SetOrganizationSettingsRequest{
|
||||
OrganizationId: Instance.DefaultOrg.GetId(),
|
||||
OrganizationScopedUsernames: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "org not provided",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.SetOrganizationSettingsRequest{
|
||||
OrganizationId: "",
|
||||
OrganizationScopedUsernames: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "org not existing",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.SetOrganizationSettingsRequest{
|
||||
OrganizationId: "notexisting",
|
||||
OrganizationScopedUsernames: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success no changes",
|
||||
prepare: func(req *settings.SetOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.SetOrganizationSettingsRequest{},
|
||||
},
|
||||
want: want{
|
||||
set: false,
|
||||
setDate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success user uniqueness",
|
||||
prepare: func(req *settings.SetOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.SetOrganizationSettingsRequest{
|
||||
OrganizationScopedUsernames: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
set: true,
|
||||
setDate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success no change",
|
||||
prepare: func(req *settings.SetOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.SetOrganizationSettingsRequest{
|
||||
OrganizationScopedUsernames: gu.Ptr(false),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
set: false,
|
||||
setDate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creationDate := time.Now().UTC()
|
||||
if tt.prepare != nil {
|
||||
tt.prepare(tt.args.req)
|
||||
}
|
||||
|
||||
got, err := instance.Client.SettingsV2.SetOrganizationSettings(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
setDate := time.Time{}
|
||||
if tt.want.set {
|
||||
setDate = time.Now().UTC()
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertOrganizationSettingsResponse(t, creationDate, setDate, tt.want.setDate, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertOrganizationSettingsResponse(t *testing.T, creationDate, setDate time.Time, expectedSetDate bool, actualResp *settings.SetOrganizationSettingsResponse) {
|
||||
if expectedSetDate {
|
||||
if !setDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, setDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.SetDate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteOrganizationSettings(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *settings.DeleteOrganizationSettingsRequest
|
||||
}
|
||||
type want struct {
|
||||
deletion bool
|
||||
deletionDate bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest)
|
||||
args args
|
||||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "permission error",
|
||||
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
|
||||
},
|
||||
args: args{
|
||||
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
|
||||
req: &settings.DeleteOrganizationSettingsRequest{
|
||||
OrganizationId: Instance.DefaultOrg.GetId(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "org not provided",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.DeleteOrganizationSettingsRequest{
|
||||
OrganizationId: "",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "org not existing",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.DeleteOrganizationSettingsRequest{
|
||||
OrganizationId: "notexisting",
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
deletion: false,
|
||||
deletionDate: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success user uniqueness",
|
||||
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.DeleteOrganizationSettingsRequest{},
|
||||
},
|
||||
want: want{
|
||||
deletion: true,
|
||||
deletionDate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success no existing",
|
||||
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.DeleteOrganizationSettingsRequest{},
|
||||
},
|
||||
want: want{
|
||||
deletion: false,
|
||||
deletionDate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success already deleted",
|
||||
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
|
||||
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
|
||||
req.OrganizationId = orgResp.GetOrganizationId()
|
||||
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
|
||||
instance.DeleteOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId())
|
||||
},
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
req: &settings.DeleteOrganizationSettingsRequest{},
|
||||
},
|
||||
want: want{
|
||||
deletion: false,
|
||||
deletionDate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creationDate := time.Now().UTC()
|
||||
if tt.prepare != nil {
|
||||
tt.prepare(t, tt.args.req)
|
||||
}
|
||||
|
||||
got, err := instance.Client.SettingsV2.DeleteOrganizationSettings(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
deletionDate := time.Time{}
|
||||
if tt.want.deletion {
|
||||
deletionDate = time.Now().UTC()
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertDeleteOrganizationSettingsResponse(t, creationDate, deletionDate, tt.want.deletionDate, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertDeleteOrganizationSettingsResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *settings.DeleteOrganizationSettingsResponse) {
|
||||
if expectedDeletionDate {
|
||||
if !deletionDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.DeletionDate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/i18n"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
)
|
||||
@@ -208,3 +211,103 @@ func (s *Server) GetHostedLoginTranslation(ctx context.Context, req *connect.Req
|
||||
|
||||
return connect.NewResponse(translation), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListOrganizationSettings(ctx context.Context, req *connect.Request[settings.ListOrganizationSettingsRequest]) (*connect.Response[settings.ListOrganizationSettingsResponse], error) {
|
||||
queries, err := s.listOrganizationSettingsRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.query.SearchOrganizationSettings(ctx, queries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&settings.ListOrganizationSettingsResponse{
|
||||
OrganizationSettings: organizationSettingsListToPb(resp.OrganizationSettingsList),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) listOrganizationSettingsRequestToModel(req *settings.ListOrganizationSettingsRequest) (*query.OrganizationSettingsSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := organizationSettingsFiltersToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.OrganizationSettingsSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: organizationSettingsFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func organizationSettingsFieldNameToSortingColumn(field *settings.OrganizationSettingsFieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.OrganizationSettingsColumnCreationDate
|
||||
}
|
||||
switch *field {
|
||||
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE:
|
||||
return query.OrganizationSettingsColumnCreationDate
|
||||
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID:
|
||||
return query.OrganizationSettingsColumnID
|
||||
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE:
|
||||
return query.OrganizationSettingsColumnChangeDate
|
||||
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED:
|
||||
return query.OrganizationSettingsColumnCreationDate
|
||||
default:
|
||||
return query.OrganizationSettingsColumnCreationDate
|
||||
}
|
||||
}
|
||||
|
||||
func organizationSettingsFiltersToQuery(queries []*settings.OrganizationSettingsSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, qry := range queries {
|
||||
q[i], err = organizationSettingsToModel(qry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func organizationSettingsToModel(filter *settings.OrganizationSettingsSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter:
|
||||
return organizationInIDsFilterToQuery(q.InOrganizationIdsFilter)
|
||||
case *settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter:
|
||||
return organizationScopedUsernamesFilterToQuery(q.OrganizationScopedUsernamesFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SETTINGS-uvTDqZHlvS", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func organizationInIDsFilterToQuery(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
|
||||
return query.NewOrganizationSettingsOrganizationIDSearchQuery(q.Ids)
|
||||
}
|
||||
|
||||
func organizationScopedUsernamesFilterToQuery(q *settings.OrganizationScopedUsernamesFilter) (query.SearchQuery, error) {
|
||||
return query.NewOrganizationSettingsOrganizationScopedUsernamesSearchQuery(q.OrganizationScopedUsernames)
|
||||
}
|
||||
|
||||
func organizationSettingsListToPb(settingsList []*query.OrganizationSettings) []*settings.OrganizationSettings {
|
||||
o := make([]*settings.OrganizationSettings, len(settingsList))
|
||||
for i, organizationSettings := range settingsList {
|
||||
o[i] = organizationSettingsToPb(organizationSettings)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func organizationSettingsToPb(organizationSettings *query.OrganizationSettings) *settings.OrganizationSettings {
|
||||
return &settings.OrganizationSettings{
|
||||
OrganizationId: organizationSettings.ID,
|
||||
CreationDate: timestamppb.New(organizationSettings.CreationDate),
|
||||
ChangeDate: timestamppb.New(organizationSettings.ChangeDate),
|
||||
OrganizationScopedUsernames: organizationSettings.OrganizationScopedUsernames,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2/settingsconnect"
|
||||
@@ -19,19 +21,25 @@ import (
|
||||
var _ settingsconnect.SettingsServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
|
||||
checkPermission domain.PermissionCheck
|
||||
assetsAPIDomain func(context.Context) string
|
||||
}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
checkPermission: checkPermission,
|
||||
assetsAPIDomain: assets.AssetAPI(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
@@ -27,3 +28,31 @@ func (s *Server) SetHostedLoginTranslation(ctx context.Context, req *connect.Req
|
||||
|
||||
return connect.NewResponse(res), nil
|
||||
}
|
||||
|
||||
func (s *Server) SetOrganizationSettings(ctx context.Context, req *connect.Request[settings.SetOrganizationSettingsRequest]) (*connect.Response[settings.SetOrganizationSettingsResponse], error) {
|
||||
details, err := s.command.SetOrganizationSettings(ctx, organizationSettingsToCommand(req.Msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var setDate *timestamppb.Timestamp
|
||||
if !details.EventDate.IsZero() {
|
||||
setDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
return connect.NewResponse(&settings.SetOrganizationSettingsResponse{
|
||||
SetDate: setDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteOrganizationSettings(ctx context.Context, req *connect.Request[settings.DeleteOrganizationSettingsRequest]) (*connect.Response[settings.DeleteOrganizationSettingsResponse], error) {
|
||||
details, err := s.command.DeleteOrganizationSettings(ctx, req.Msg.GetOrganizationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if !details.EventDate.IsZero() {
|
||||
deletionDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
return connect.NewResponse(&settings.DeleteOrganizationSettingsResponse{
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -252,3 +252,10 @@ func securitySettingsToCommand(req *settings.SetSecuritySettingsRequest) *comman
|
||||
EnableImpersonation: req.GetEnableImpersonation(),
|
||||
}
|
||||
}
|
||||
|
||||
func organizationSettingsToCommand(req *settings.SetOrganizationSettingsRequest) *command.SetOrganizationSettings {
|
||||
return &command.SetOrganizationSettings{
|
||||
OrganizationID: req.OrganizationId,
|
||||
OrganizationScopedUsernames: req.OrganizationScopedUsernames,
|
||||
}
|
||||
}
|
||||
|
||||
58
proto/zitadel/settings/v2/organization_settings.proto
Normal file
58
proto/zitadel/settings/v2/organization_settings.proto
Normal file
@@ -0,0 +1,58 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings";
|
||||
|
||||
import "google/api/field_behavior.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "validate/validate.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "zitadel/filter/v2/filter.proto";
|
||||
|
||||
message OrganizationSettings {
|
||||
// The unique identifier of the organization the settings belong to.
|
||||
string organization_id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629012906488334\"";
|
||||
}
|
||||
];
|
||||
|
||||
// The timestamp of the organization settings creation.
|
||||
google.protobuf.Timestamp creation_date = 2[
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
|
||||
// The timestamp of the last change to the organization settings.
|
||||
google.protobuf.Timestamp change_date = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
|
||||
// Defines if the usernames have to be unique in the organization context.
|
||||
bool organization_scoped_usernames = 4;
|
||||
}
|
||||
|
||||
enum OrganizationSettingsFieldName {
|
||||
ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED = 0;
|
||||
ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID = 1;
|
||||
ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE = 2;
|
||||
ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE = 3;
|
||||
}
|
||||
|
||||
message OrganizationSettingsSearchFilter {
|
||||
oneof filter {
|
||||
option (validate.required) = true;
|
||||
|
||||
zitadel.filter.v2.InIDsFilter in_organization_ids_filter = 1;
|
||||
OrganizationScopedUsernamesFilter organization_scoped_usernames_filter = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Query for organization settings with specific scopes usernames.
|
||||
message OrganizationScopedUsernamesFilter {
|
||||
bool organization_scoped_usernames = 1;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import "google/protobuf/struct.proto";
|
||||
import "zitadel/settings/v2/settings.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "zitadel/filter/v2/filter.proto";
|
||||
import "zitadel/settings/v2/organization_settings.proto";
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings";
|
||||
|
||||
@@ -418,6 +419,99 @@ service SettingsService {
|
||||
};
|
||||
}
|
||||
|
||||
// Set Organization Settings
|
||||
//
|
||||
// Sets the settings specific to an organization.
|
||||
// Organization scopes usernames defines that the usernames have to be unique in the organization scope,
|
||||
// can only be changed if the usernames of the users are unique in the scope.
|
||||
//
|
||||
// Required permissions:
|
||||
// - `iam.policy.write`
|
||||
rpc SetOrganizationSettings(SetOrganizationSettingsRequest) returns (SetOrganizationSettingsResponse) {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The translations was successfully set.";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
option (google.api.http) = {
|
||||
post: "/v2/settings/organization";
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Delete Organization Settings
|
||||
//
|
||||
// Delete the settings specific to an organization.
|
||||
//
|
||||
// Required permissions:
|
||||
// - `iam.policy.delete`
|
||||
rpc DeleteOrganizationSettings(DeleteOrganizationSettingsRequest) returns (DeleteOrganizationSettingsResponse) {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The translations was successfully set.";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
option (google.api.http) = {
|
||||
delete: "/v2/settings/organization";
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List Organization Settings
|
||||
//
|
||||
// Returns a list of organization settings.
|
||||
//
|
||||
// Required permission:
|
||||
// - `iam.policy.read`
|
||||
// - `org.policy.read`
|
||||
rpc ListOrganizationSettings(ListOrganizationSettingsRequest) returns (ListOrganizationSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v2/settings/organization/search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "A list of all project grants matching the query";
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
key: "400";
|
||||
value: {
|
||||
description: "invalid list query";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get Hosted Login Translation
|
||||
//
|
||||
// Returns the translations in the requested locale for the hosted login.
|
||||
@@ -640,6 +734,64 @@ message SetSecuritySettingsResponse{
|
||||
zitadel.object.v2.Details details = 1;
|
||||
}
|
||||
|
||||
|
||||
message SetOrganizationSettingsRequest {
|
||||
// Organization ID in which this settings are set.
|
||||
string organization_id = 1;
|
||||
|
||||
// Force the usernames in the organization to be unique,
|
||||
// only possible to set if the existing users already have unique usernames in the organization context.
|
||||
optional bool organization_scoped_usernames = 2;
|
||||
}
|
||||
|
||||
message SetOrganizationSettingsResponse {
|
||||
// The timestamp of the set of the organization settings.
|
||||
google.protobuf.Timestamp set_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message DeleteOrganizationSettingsRequest {
|
||||
// Organization ID in which this settings are set.
|
||||
string organization_id = 1;
|
||||
}
|
||||
|
||||
message DeleteOrganizationSettingsResponse {
|
||||
// The timestamp of the deletion of the organization settings.
|
||||
google.protobuf.Timestamp deletion_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message ListOrganizationSettingsRequest{
|
||||
// List limitations and ordering.
|
||||
optional zitadel.filter.v2.PaginationRequest pagination = 1;
|
||||
|
||||
// The field the result is sorted by. The default is the creation date.
|
||||
// Beware that if you change this, your result pagination might be inconsistent.
|
||||
optional OrganizationSettingsFieldName sorting_column = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
default: "\"ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE\""
|
||||
}
|
||||
];
|
||||
|
||||
// Define the criteria to query for.
|
||||
repeated OrganizationSettingsSearchFilter filters = 4;
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
example: "{\"pagination\":{\"offset\":0,\"limit\":0,\"asc\":true},\"sortingColumn\":\"ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE\",\"filters\":[{\"inOrganizationIdsFilter\":{\"ids\":[\"28746028909593987\"]}}]}";
|
||||
};
|
||||
}
|
||||
|
||||
message ListOrganizationSettingsResponse {
|
||||
zitadel.filter.v2.PaginationResponse pagination = 1;
|
||||
repeated OrganizationSettings organization_settings = 2;
|
||||
}
|
||||
|
||||
message GetHostedLoginTranslationRequest {
|
||||
// Specify the level from which the translation should be returned.
|
||||
// If the requested level doesn't contain all translations, and ignore_inheritance is set to false
|
||||
|
||||
Reference in New Issue
Block a user