feat(api): reworking AddOrganization() API call to return all admins (#9900)

This commit is contained in:
Iraq
2025-06-05 11:05:35 +02:00
committed by GitHub
parent 85e3b7449c
commit 7df4f76f3c
9 changed files with 160 additions and 63 deletions

View File

@@ -93,8 +93,8 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*
return nil, err return nil, err
} }
var userID string var userID string
if len(createdOrg.CreatedAdmins) == 1 { if len(createdOrg.OrgAdmins) == 1 {
userID = createdOrg.CreatedAdmins[0].ID userID = createdOrg.OrgAdmins[0].GetID()
} }
return &admin_pb.SetUpOrgResponse{ return &admin_pb.SetUpOrgResponse{
Details: object.DomainToAddDetailsPb(createdOrg.ObjectDetails), Details: object.DomainToAddDetailsPb(createdOrg.ObjectDetails),

View File

@@ -69,12 +69,15 @@ func addOrganizationRequestAdminToCommand(admin *org.AddOrganizationRequest_Admi
} }
func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.AddOrganizationResponse, err error) { func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.AddOrganizationResponse, err error) {
admins := make([]*org.AddOrganizationResponse_CreatedAdmin, len(createdOrg.CreatedAdmins)) admins := make([]*org.AddOrganizationResponse_CreatedAdmin, 0, len(createdOrg.OrgAdmins))
for i, admin := range createdOrg.CreatedAdmins { for _, admin := range createdOrg.OrgAdmins {
admins[i] = &org.AddOrganizationResponse_CreatedAdmin{ admin, ok := admin.(*command.CreatedOrgAdmin)
UserId: admin.ID, if ok {
admins = append(admins, &org.AddOrganizationResponse_CreatedAdmin{
UserId: admin.GetID(),
EmailCode: admin.EmailCode, EmailCode: admin.EmailCode,
PhoneCode: admin.PhoneCode, PhoneCode: admin.PhoneCode,
})
} }
} }
return &org.AddOrganizationResponse{ return &org.AddOrganizationResponse{

View File

@@ -150,8 +150,8 @@ func Test_createdOrganizationToPb(t *testing.T) {
EventDate: now, EventDate: now,
ResourceOwner: "orgID", ResourceOwner: "orgID",
}, },
CreatedAdmins: []*command.CreatedOrgAdmin{ OrgAdmins: []command.OrgAdmin{
{ &command.CreatedOrgAdmin{
ID: "id", ID: "id",
EmailCode: gu.Ptr("emailCode"), EmailCode: gu.Ptr("emailCode"),
PhoneCode: gu.Ptr("phoneCode"), PhoneCode: gu.Ptr("phoneCode"),

View File

@@ -72,18 +72,33 @@ func OrgStateToPb(state domain.OrgState) v2beta_org.OrgState {
} }
func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.CreateOrganizationResponse, err error) { func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.CreateOrganizationResponse, err error) {
admins := make([]*org.CreatedAdmin, len(createdOrg.CreatedAdmins)) admins := make([]*org.OrganizationAdmin, len(createdOrg.OrgAdmins))
for i, admin := range createdOrg.CreatedAdmins { for i, admin := range createdOrg.OrgAdmins {
admins[i] = &org.CreatedAdmin{ switch admin := admin.(type) {
case *command.CreatedOrgAdmin:
admins[i] = &org.OrganizationAdmin{
OrganizationAdmin: &org.OrganizationAdmin_CreatedAdmin{
CreatedAdmin: &org.CreatedAdmin{
UserId: admin.ID, UserId: admin.ID,
EmailCode: admin.EmailCode, EmailCode: admin.EmailCode,
PhoneCode: admin.PhoneCode, PhoneCode: admin.PhoneCode,
},
},
}
case *command.AssignedOrgAdmin:
admins[i] = &org.OrganizationAdmin{
OrganizationAdmin: &org.OrganizationAdmin_AssignedAdmin{
AssignedAdmin: &org.AssignedAdmin{
UserId: admin.ID,
},
},
}
} }
} }
return &org.CreateOrganizationResponse{ return &org.CreateOrganizationResponse{
CreationDate: timestamppb.New(createdOrg.ObjectDetails.EventDate), CreationDate: timestamppb.New(createdOrg.ObjectDetails.EventDate),
Id: createdOrg.ObjectDetails.ResourceOwner, Id: createdOrg.ObjectDetails.ResourceOwner,
CreatedAdmins: admins, OrganizationAdmins: admins,
}, nil }, nil
} }

View File

@@ -18,7 +18,6 @@ import (
"github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/admin"
v2beta_object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" v2beta_object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/user/v2" "github.com/zitadel/zitadel/pkg/grpc/user/v2"
user_v2beta "github.com/zitadel/zitadel/pkg/grpc/user/v2beta" user_v2beta "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
@@ -85,6 +84,29 @@ func TestServer_CreateOrganization(t *testing.T) {
}, },
wantErr: true, wantErr: true,
}, },
{
name: "existing user as admin",
ctx: CTX,
req: &v2beta_org.CreateOrganizationRequest{
Name: gofakeit.AppName(),
Admins: []*v2beta_org.CreateOrganizationRequest_Admin{
{
UserType: &v2beta_org.CreateOrganizationRequest_Admin_UserId{UserId: User.GetUserId()},
},
},
},
want: &v2beta_org.CreateOrganizationResponse{
OrganizationAdmins: []*v2beta_org.OrganizationAdmin{
{
OrganizationAdmin: &v2beta_org.OrganizationAdmin_AssignedAdmin{
AssignedAdmin: &v2beta_org.AssignedAdmin{
UserId: User.GetUserId(),
},
},
},
},
},
},
{ {
name: "admin with init", name: "admin with init",
ctx: CTX, ctx: CTX,
@@ -111,8 +133,10 @@ func TestServer_CreateOrganization(t *testing.T) {
}, },
want: &v2beta_org.CreateOrganizationResponse{ want: &v2beta_org.CreateOrganizationResponse{
Id: integration.NotEmpty, Id: integration.NotEmpty,
CreatedAdmins: []*v2beta_org.CreatedAdmin{ OrganizationAdmins: []*v2beta_org.OrganizationAdmin{
{ {
OrganizationAdmin: &v2beta_org.OrganizationAdmin_CreatedAdmin{
CreatedAdmin: &v2beta_org.CreatedAdmin{
UserId: integration.NotEmpty, UserId: integration.NotEmpty,
EmailCode: gu.Ptr(integration.NotEmpty), EmailCode: gu.Ptr(integration.NotEmpty),
PhoneCode: nil, PhoneCode: nil,
@@ -120,6 +144,8 @@ func TestServer_CreateOrganization(t *testing.T) {
}, },
}, },
}, },
},
},
{ {
name: "existing user and new human with idp", name: "existing user and new human with idp",
ctx: CTX, ctx: CTX,
@@ -155,14 +181,25 @@ func TestServer_CreateOrganization(t *testing.T) {
}, },
}, },
want: &v2beta_org.CreateOrganizationResponse{ want: &v2beta_org.CreateOrganizationResponse{
CreatedAdmins: []*v2beta_org.CreatedAdmin{ // OrganizationId: integration.NotEmpty,
// a single admin is expected, because the first provided already exists OrganizationAdmins: []*v2beta_org.OrganizationAdmin{
{ {
OrganizationAdmin: &v2beta_org.OrganizationAdmin_AssignedAdmin{
AssignedAdmin: &v2beta_org.AssignedAdmin{
UserId: User.GetUserId(),
},
},
},
{
OrganizationAdmin: &v2beta_org.OrganizationAdmin_CreatedAdmin{
CreatedAdmin: &v2beta_org.CreatedAdmin{
UserId: integration.NotEmpty, UserId: integration.NotEmpty,
}, },
}, },
}, },
}, },
},
},
{ {
name: "create with ID", name: "create with ID",
ctx: CTX, ctx: CTX,
@@ -192,13 +229,16 @@ func TestServer_CreateOrganization(t *testing.T) {
now := time.Now() now := time.Now()
assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute))
// organization id must be the same as the resourceOwner
// check the admins // check the admins
require.Len(t, got.GetCreatedAdmins(), len(tt.want.GetCreatedAdmins())) require.Equal(t, len(tt.want.GetOrganizationAdmins()), len(got.GetOrganizationAdmins()))
for i, admin := range tt.want.GetCreatedAdmins() { for i, admin := range tt.want.GetOrganizationAdmins() {
gotAdmin := got.GetCreatedAdmins()[i] gotAdmin := got.GetOrganizationAdmins()[i].OrganizationAdmin
assertCreatedAdmin(t, admin, gotAdmin) switch admin := admin.OrganizationAdmin.(type) {
case *v2beta_org.OrganizationAdmin_CreatedAdmin:
assertCreatedAdmin(t, admin.CreatedAdmin, gotAdmin.(*v2beta_org.OrganizationAdmin_CreatedAdmin).CreatedAdmin)
case *v2beta_org.OrganizationAdmin_AssignedAdmin:
assert.Equal(t, admin.AssignedAdmin.GetUserId(), gotAdmin.(*v2beta_org.OrganizationAdmin_AssignedAdmin).AssignedAdmin.GetUserId())
}
} }
}) })
} }
@@ -472,8 +512,8 @@ func TestServer_ListOrganizations(t *testing.T) {
ctx: listOrgIAmOwnerCtx, ctx: listOrgIAmOwnerCtx,
query: []*v2beta_org.OrganizationSearchFilter{ query: []*v2beta_org.OrganizationSearchFilter{
{ {
Filter: &org.OrganizationSearchFilter_DomainFilter{ Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{
DomainFilter: &org.OrgDomainFilter{ DomainFilter: &v2beta_org.OrgDomainFilter{
Domain: func() string { Domain: func() string {
listOrgRes, err := listOrgClient.ListOrganizations(listOrgIAmOwnerCtx, &v2beta_org.ListOrganizationsRequest{ listOrgRes, err := listOrgClient.ListOrganizations(listOrgIAmOwnerCtx, &v2beta_org.ListOrganizationsRequest{
Filter: []*v2beta_org.OrganizationSearchFilter{ Filter: []*v2beta_org.OrganizationSearchFilter{
@@ -507,8 +547,8 @@ func TestServer_ListOrganizations(t *testing.T) {
ctx: listOrgIAmOwnerCtx, ctx: listOrgIAmOwnerCtx,
query: []*v2beta_org.OrganizationSearchFilter{ query: []*v2beta_org.OrganizationSearchFilter{
{ {
Filter: &org.OrganizationSearchFilter_DomainFilter{ Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{
DomainFilter: &org.OrgDomainFilter{ DomainFilter: &v2beta_org.OrgDomainFilter{
Domain: func() string { Domain: func() string {
domain := strings.ToLower(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-")) domain := strings.ToLower(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-"))
return domain return domain
@@ -530,8 +570,8 @@ func TestServer_ListOrganizations(t *testing.T) {
ctx: listOrgIAmOwnerCtx, ctx: listOrgIAmOwnerCtx,
query: []*v2beta_org.OrganizationSearchFilter{ query: []*v2beta_org.OrganizationSearchFilter{
{ {
Filter: &org.OrganizationSearchFilter_DomainFilter{ Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{
DomainFilter: &org.OrgDomainFilter{ DomainFilter: &v2beta_org.OrgDomainFilter{
Domain: func() string { Domain: func() string {
domain := strings.ToUpper(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-")) domain := strings.ToUpper(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-"))
return domain return domain
@@ -1374,7 +1414,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) {
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: orgId, OrganizationId: orgId,
Domain: domain, Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP, Type: v2beta_org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
}, },
}, },
{ {
@@ -1383,7 +1423,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) {
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: "non existent org id", OrganizationId: "non existent org id",
Domain: domain, Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP, Type: v2beta_org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
}, },
// BUG: this should be 'organization does not exist' // BUG: this should be 'organization does not exist'
err: errors.New("Domain doesn't exist on organization"), err: errors.New("Domain doesn't exist on organization"),
@@ -1394,7 +1434,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) {
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: orgId, OrganizationId: orgId,
Domain: domain, Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS, Type: v2beta_org.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS,
}, },
}, },
{ {
@@ -1403,7 +1443,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) {
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: "non existent org id", OrganizationId: "non existent org id",
Domain: domain, Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS, Type: v2beta_org.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS,
}, },
// BUG: this should be 'organization does not exist' // BUG: this should be 'organization does not exist'
err: errors.New("Domain doesn't exist on organization"), err: errors.New("Domain doesn't exist on organization"),
@@ -1414,7 +1454,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) {
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: orgId, OrganizationId: orgId,
Domain: "non existent domain", Domain: "non existent domain",
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP, Type: v2beta_org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
}, },
err: errors.New("Domain doesn't exist on organization"), err: errors.New("Domain doesn't exist on organization"),
}, },

View File

@@ -150,8 +150,8 @@ func Test_createdOrganizationToPb(t *testing.T) {
EventDate: now, EventDate: now,
ResourceOwner: "orgID", ResourceOwner: "orgID",
}, },
CreatedAdmins: []*command.CreatedOrgAdmin{ OrgAdmins: []command.OrgAdmin{
{ &command.CreatedOrgAdmin{
ID: "id", ID: "id",
EmailCode: gu.Ptr("emailCode"), EmailCode: gu.Ptr("emailCode"),
PhoneCode: gu.Ptr("phoneCode"), PhoneCode: gu.Ptr("phoneCode"),
@@ -162,8 +162,10 @@ func Test_createdOrganizationToPb(t *testing.T) {
want: &org.CreateOrganizationResponse{ want: &org.CreateOrganizationResponse{
CreationDate: timestamppb.New(now), CreationDate: timestamppb.New(now),
Id: "orgID", Id: "orgID",
CreatedAdmins: []*org.CreatedAdmin{ OrganizationAdmins: []*org.OrganizationAdmin{
{ {
OrganizationAdmin: &org.OrganizationAdmin_CreatedAdmin{
CreatedAdmin: &org.CreatedAdmin{
UserId: "id", UserId: "id",
EmailCode: gu.Ptr("emailCode"), EmailCode: gu.Ptr("emailCode"),
PhoneCode: gu.Ptr("phoneCode"), PhoneCode: gu.Ptr("phoneCode"),
@@ -171,6 +173,8 @@ func Test_createdOrganizationToPb(t *testing.T) {
}, },
}, },
}, },
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -54,7 +54,11 @@ type orgSetupCommands struct {
type CreatedOrg struct { type CreatedOrg struct {
ObjectDetails *domain.ObjectDetails ObjectDetails *domain.ObjectDetails
CreatedAdmins []*CreatedOrgAdmin OrgAdmins []OrgAdmin
}
type OrgAdmin interface {
GetID() string
} }
type CreatedOrgAdmin struct { type CreatedOrgAdmin struct {
@@ -65,6 +69,18 @@ type CreatedOrgAdmin struct {
MachineKey *MachineKey MachineKey *MachineKey
} }
func (a *CreatedOrgAdmin) GetID() string {
return a.ID
}
type AssignedOrgAdmin struct {
ID string
}
func (a *AssignedOrgAdmin) GetID() string {
return a.ID
}
func (o *OrgSetup) Validate() (err error) { func (o *OrgSetup) Validate() (err error) {
if o.OrgID != "" && strings.TrimSpace(o.OrgID) == "" { if o.OrgID != "" && strings.TrimSpace(o.OrgID) == "" {
return zerrors.ThrowInvalidArgument(nil, "ORG-4ABd3", "Errors.Invalid.Argument") return zerrors.ThrowInvalidArgument(nil, "ORG-4ABd3", "Errors.Invalid.Argument")
@@ -188,14 +204,15 @@ func (c *orgSetupCommands) push(ctx context.Context) (_ *CreatedOrg, err error)
EventDate: events[len(events)-1].CreatedAt(), EventDate: events[len(events)-1].CreatedAt(),
ResourceOwner: c.aggregate.ID, ResourceOwner: c.aggregate.ID,
}, },
CreatedAdmins: c.createdAdmins(), OrgAdmins: c.createdAdmins(),
}, nil }, nil
} }
func (c *orgSetupCommands) createdAdmins() []*CreatedOrgAdmin { func (c *orgSetupCommands) createdAdmins() []OrgAdmin {
users := make([]*CreatedOrgAdmin, 0, len(c.admins)) users := make([]OrgAdmin, 0, len(c.admins))
for _, admin := range c.admins { for _, admin := range c.admins {
if admin.ID != "" && admin.Human == nil { if admin.ID != "" && admin.Human == nil {
users = append(users, &AssignedOrgAdmin{ID: admin.ID})
continue continue
} }
if admin.Human != nil { if admin.Human != nil {

View File

@@ -1531,8 +1531,8 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
ObjectDetails: &domain.ObjectDetails{ ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "orgID", ResourceOwner: "orgID",
}, },
CreatedAdmins: []*CreatedOrgAdmin{ OrgAdmins: []OrgAdmin{
{ &CreatedOrgAdmin{
ID: "userID", ID: "userID",
}, },
}, },
@@ -1574,7 +1574,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
ObjectDetails: &domain.ObjectDetails{ ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "custom-org-ID", ResourceOwner: "custom-org-ID",
}, },
CreatedAdmins: []*CreatedOrgAdmin{}, OrgAdmins: []OrgAdmin{},
}, },
}, },
}, },
@@ -1641,7 +1641,11 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
ObjectDetails: &domain.ObjectDetails{ ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "orgID", ResourceOwner: "orgID",
}, },
CreatedAdmins: []*CreatedOrgAdmin{}, OrgAdmins: []OrgAdmin{
&AssignedOrgAdmin{
ID: "userID",
},
},
}, },
}, },
}, },
@@ -1751,8 +1755,8 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
ObjectDetails: &domain.ObjectDetails{ ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "orgID", ResourceOwner: "orgID",
}, },
CreatedAdmins: []*CreatedOrgAdmin{ OrgAdmins: []OrgAdmin{
{ &CreatedOrgAdmin{
ID: "userID", ID: "userID",
PAT: &PersonalAccessToken{ PAT: &PersonalAccessToken{
ObjectRoot: models.ObjectRoot{ ObjectRoot: models.ObjectRoot{

View File

@@ -558,6 +558,18 @@ message CreatedAdmin {
optional string phone_code = 3; optional string phone_code = 3;
} }
message AssignedAdmin {
string user_id = 1;
}
message OrganizationAdmin {
// The admins created/assigned for the Organization.
oneof OrganizationAdmin {
CreatedAdmin created_admin = 1;
AssignedAdmin assigned_admin = 2;
}
}
message CreateOrganizationResponse{ message CreateOrganizationResponse{
// The timestamp of the organization was created. // The timestamp of the organization was created.
google.protobuf.Timestamp creation_date = 1 [ google.protobuf.Timestamp creation_date = 1 [
@@ -577,8 +589,8 @@ message CreateOrganizationResponse{
} }
]; ];
// The admins created for the Organization // The admins created/assigned for the Organization
repeated CreatedAdmin created_admins = 3; repeated OrganizationAdmin organization_admins = 3;
} }
message UpdateOrganizationRequest { message UpdateOrganizationRequest {
@@ -939,3 +951,5 @@ message DeleteOrganizationMetadataResponse{
} }
]; ];
} }