From 7df4f76f3c6d41320bc4639446afa08d9c631032 Mon Sep 17 00:00:00 2001 From: Iraq <66622793+kkrime@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:05:35 +0200 Subject: [PATCH] feat(api): reworking AddOrganization() API call to return all admins (#9900) --- internal/api/grpc/admin/org.go | 6 +- internal/api/grpc/org/v2/org.go | 15 ++-- internal/api/grpc/org/v2/org_test.go | 4 +- internal/api/grpc/org/v2beta/helper.go | 33 +++++-- .../org/v2beta/integration_test/org_test.go | 90 +++++++++++++------ internal/api/grpc/org/v2beta/org_test.go | 16 ++-- internal/command/org.go | 25 +++++- internal/command/org_test.go | 16 ++-- proto/zitadel/org/v2beta/org_service.proto | 18 +++- 9 files changed, 160 insertions(+), 63 deletions(-) diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index 293e7c74d7..ef97e47bb0 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -78,7 +78,7 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (* if err != nil { return nil, err } - human := setUpOrgHumanToCommand(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine + human := setUpOrgHumanToCommand(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) // TODO: handle machine createdOrg, err := s.command.SetUpOrg(ctx, &command.OrgSetup{ Name: req.Org.Name, CustomDomain: req.Org.Domain, @@ -93,8 +93,8 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (* return nil, err } var userID string - if len(createdOrg.CreatedAdmins) == 1 { - userID = createdOrg.CreatedAdmins[0].ID + if len(createdOrg.OrgAdmins) == 1 { + userID = createdOrg.OrgAdmins[0].GetID() } return &admin_pb.SetUpOrgResponse{ Details: object.DomainToAddDetailsPb(createdOrg.ObjectDetails), diff --git a/internal/api/grpc/org/v2/org.go b/internal/api/grpc/org/v2/org.go index bbc3caca85..b876826365 100644 --- a/internal/api/grpc/org/v2/org.go +++ b/internal/api/grpc/org/v2/org.go @@ -69,12 +69,15 @@ func addOrganizationRequestAdminToCommand(admin *org.AddOrganizationRequest_Admi } func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.AddOrganizationResponse, err error) { - admins := make([]*org.AddOrganizationResponse_CreatedAdmin, len(createdOrg.CreatedAdmins)) - for i, admin := range createdOrg.CreatedAdmins { - admins[i] = &org.AddOrganizationResponse_CreatedAdmin{ - UserId: admin.ID, - EmailCode: admin.EmailCode, - PhoneCode: admin.PhoneCode, + admins := make([]*org.AddOrganizationResponse_CreatedAdmin, 0, len(createdOrg.OrgAdmins)) + for _, admin := range createdOrg.OrgAdmins { + admin, ok := admin.(*command.CreatedOrgAdmin) + if ok { + admins = append(admins, &org.AddOrganizationResponse_CreatedAdmin{ + UserId: admin.GetID(), + EmailCode: admin.EmailCode, + PhoneCode: admin.PhoneCode, + }) } } return &org.AddOrganizationResponse{ diff --git a/internal/api/grpc/org/v2/org_test.go b/internal/api/grpc/org/v2/org_test.go index 7ae252a209..37a3dca41a 100644 --- a/internal/api/grpc/org/v2/org_test.go +++ b/internal/api/grpc/org/v2/org_test.go @@ -150,8 +150,8 @@ func Test_createdOrganizationToPb(t *testing.T) { EventDate: now, ResourceOwner: "orgID", }, - CreatedAdmins: []*command.CreatedOrgAdmin{ - { + OrgAdmins: []command.OrgAdmin{ + &command.CreatedOrgAdmin{ ID: "id", EmailCode: gu.Ptr("emailCode"), PhoneCode: gu.Ptr("phoneCode"), diff --git a/internal/api/grpc/org/v2beta/helper.go b/internal/api/grpc/org/v2beta/helper.go index 39bad0dae2..6f47819bb4 100644 --- a/internal/api/grpc/org/v2beta/helper.go +++ b/internal/api/grpc/org/v2beta/helper.go @@ -72,18 +72,33 @@ func OrgStateToPb(state domain.OrgState) v2beta_org.OrgState { } func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.CreateOrganizationResponse, err error) { - admins := make([]*org.CreatedAdmin, len(createdOrg.CreatedAdmins)) - for i, admin := range createdOrg.CreatedAdmins { - admins[i] = &org.CreatedAdmin{ - UserId: admin.ID, - EmailCode: admin.EmailCode, - PhoneCode: admin.PhoneCode, + admins := make([]*org.OrganizationAdmin, len(createdOrg.OrgAdmins)) + for i, admin := range createdOrg.OrgAdmins { + switch admin := admin.(type) { + case *command.CreatedOrgAdmin: + admins[i] = &org.OrganizationAdmin{ + OrganizationAdmin: &org.OrganizationAdmin_CreatedAdmin{ + CreatedAdmin: &org.CreatedAdmin{ + UserId: admin.ID, + EmailCode: admin.EmailCode, + PhoneCode: admin.PhoneCode, + }, + }, + } + case *command.AssignedOrgAdmin: + admins[i] = &org.OrganizationAdmin{ + OrganizationAdmin: &org.OrganizationAdmin_AssignedAdmin{ + AssignedAdmin: &org.AssignedAdmin{ + UserId: admin.ID, + }, + }, + } } } return &org.CreateOrganizationResponse{ - CreationDate: timestamppb.New(createdOrg.ObjectDetails.EventDate), - Id: createdOrg.ObjectDetails.ResourceOwner, - CreatedAdmins: admins, + CreationDate: timestamppb.New(createdOrg.ObjectDetails.EventDate), + Id: createdOrg.ObjectDetails.ResourceOwner, + OrganizationAdmins: admins, }, nil } diff --git a/internal/api/grpc/org/v2beta/integration_test/org_test.go b/internal/api/grpc/org/v2beta/integration_test/org_test.go index 4e0ec26121..0d3b920afe 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -18,7 +18,6 @@ import ( "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/admin" 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" "github.com/zitadel/zitadel/pkg/grpc/user/v2" user_v2beta "github.com/zitadel/zitadel/pkg/grpc/user/v2beta" @@ -85,6 +84,29 @@ func TestServer_CreateOrganization(t *testing.T) { }, 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", ctx: CTX, @@ -111,11 +133,15 @@ func TestServer_CreateOrganization(t *testing.T) { }, want: &v2beta_org.CreateOrganizationResponse{ Id: integration.NotEmpty, - CreatedAdmins: []*v2beta_org.CreatedAdmin{ + OrganizationAdmins: []*v2beta_org.OrganizationAdmin{ { - UserId: integration.NotEmpty, - EmailCode: gu.Ptr(integration.NotEmpty), - PhoneCode: nil, + OrganizationAdmin: &v2beta_org.OrganizationAdmin_CreatedAdmin{ + CreatedAdmin: &v2beta_org.CreatedAdmin{ + UserId: integration.NotEmpty, + EmailCode: gu.Ptr(integration.NotEmpty), + PhoneCode: nil, + }, + }, }, }, }, @@ -155,10 +181,21 @@ func TestServer_CreateOrganization(t *testing.T) { }, }, want: &v2beta_org.CreateOrganizationResponse{ - CreatedAdmins: []*v2beta_org.CreatedAdmin{ - // a single admin is expected, because the first provided already exists + // OrganizationId: integration.NotEmpty, + OrganizationAdmins: []*v2beta_org.OrganizationAdmin{ { - UserId: integration.NotEmpty, + OrganizationAdmin: &v2beta_org.OrganizationAdmin_AssignedAdmin{ + AssignedAdmin: &v2beta_org.AssignedAdmin{ + UserId: User.GetUserId(), + }, + }, + }, + { + OrganizationAdmin: &v2beta_org.OrganizationAdmin_CreatedAdmin{ + CreatedAdmin: &v2beta_org.CreatedAdmin{ + UserId: integration.NotEmpty, + }, + }, }, }, }, @@ -192,13 +229,16 @@ func TestServer_CreateOrganization(t *testing.T) { now := time.Now() assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) - // organization id must be the same as the resourceOwner - // check the admins - require.Len(t, got.GetCreatedAdmins(), len(tt.want.GetCreatedAdmins())) - for i, admin := range tt.want.GetCreatedAdmins() { - gotAdmin := got.GetCreatedAdmins()[i] - assertCreatedAdmin(t, admin, gotAdmin) + require.Equal(t, len(tt.want.GetOrganizationAdmins()), len(got.GetOrganizationAdmins())) + for i, admin := range tt.want.GetOrganizationAdmins() { + gotAdmin := got.GetOrganizationAdmins()[i].OrganizationAdmin + 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, query: []*v2beta_org.OrganizationSearchFilter{ { - Filter: &org.OrganizationSearchFilter_DomainFilter{ - DomainFilter: &org.OrgDomainFilter{ + Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{ + DomainFilter: &v2beta_org.OrgDomainFilter{ Domain: func() string { listOrgRes, err := listOrgClient.ListOrganizations(listOrgIAmOwnerCtx, &v2beta_org.ListOrganizationsRequest{ Filter: []*v2beta_org.OrganizationSearchFilter{ @@ -507,8 +547,8 @@ func TestServer_ListOrganizations(t *testing.T) { ctx: listOrgIAmOwnerCtx, query: []*v2beta_org.OrganizationSearchFilter{ { - Filter: &org.OrganizationSearchFilter_DomainFilter{ - DomainFilter: &org.OrgDomainFilter{ + Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{ + DomainFilter: &v2beta_org.OrgDomainFilter{ Domain: func() string { domain := strings.ToLower(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-")) return domain @@ -530,8 +570,8 @@ func TestServer_ListOrganizations(t *testing.T) { ctx: listOrgIAmOwnerCtx, query: []*v2beta_org.OrganizationSearchFilter{ { - Filter: &org.OrganizationSearchFilter_DomainFilter{ - DomainFilter: &org.OrgDomainFilter{ + Filter: &v2beta_org.OrganizationSearchFilter_DomainFilter{ + DomainFilter: &v2beta_org.OrgDomainFilter{ Domain: func() string { domain := strings.ToUpper(strings.ReplaceAll(orgsName[1][1:len(orgsName[1])-2], " ", "-")) return domain @@ -1374,7 +1414,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) { req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ OrganizationId: orgId, 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{ OrganizationId: "non existent org id", 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' err: errors.New("Domain doesn't exist on organization"), @@ -1394,7 +1434,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) { req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ OrganizationId: orgId, 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{ OrganizationId: "non existent org id", 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' err: errors.New("Domain doesn't exist on organization"), @@ -1414,7 +1454,7 @@ func TestServer_ValidateOrganizationDomain(t *testing.T) { req: &v2beta_org.GenerateOrganizationDomainValidationRequest{ OrganizationId: orgId, 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"), }, diff --git a/internal/api/grpc/org/v2beta/org_test.go b/internal/api/grpc/org/v2beta/org_test.go index 2047f665a1..346d6b88c1 100644 --- a/internal/api/grpc/org/v2beta/org_test.go +++ b/internal/api/grpc/org/v2beta/org_test.go @@ -150,8 +150,8 @@ func Test_createdOrganizationToPb(t *testing.T) { EventDate: now, ResourceOwner: "orgID", }, - CreatedAdmins: []*command.CreatedOrgAdmin{ - { + OrgAdmins: []command.OrgAdmin{ + &command.CreatedOrgAdmin{ ID: "id", EmailCode: gu.Ptr("emailCode"), PhoneCode: gu.Ptr("phoneCode"), @@ -162,11 +162,15 @@ func Test_createdOrganizationToPb(t *testing.T) { want: &org.CreateOrganizationResponse{ CreationDate: timestamppb.New(now), Id: "orgID", - CreatedAdmins: []*org.CreatedAdmin{ + OrganizationAdmins: []*org.OrganizationAdmin{ { - UserId: "id", - EmailCode: gu.Ptr("emailCode"), - PhoneCode: gu.Ptr("phoneCode"), + OrganizationAdmin: &org.OrganizationAdmin_CreatedAdmin{ + CreatedAdmin: &org.CreatedAdmin{ + UserId: "id", + EmailCode: gu.Ptr("emailCode"), + PhoneCode: gu.Ptr("phoneCode"), + }, + }, }, }, }, diff --git a/internal/command/org.go b/internal/command/org.go index b6650ef7f2..ddf99797e3 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -54,7 +54,11 @@ type orgSetupCommands struct { type CreatedOrg struct { ObjectDetails *domain.ObjectDetails - CreatedAdmins []*CreatedOrgAdmin + OrgAdmins []OrgAdmin +} + +type OrgAdmin interface { + GetID() string } type CreatedOrgAdmin struct { @@ -65,6 +69,18 @@ type CreatedOrgAdmin struct { 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) { if o.OrgID != "" && strings.TrimSpace(o.OrgID) == "" { 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(), ResourceOwner: c.aggregate.ID, }, - CreatedAdmins: c.createdAdmins(), + OrgAdmins: c.createdAdmins(), }, nil } -func (c *orgSetupCommands) createdAdmins() []*CreatedOrgAdmin { - users := make([]*CreatedOrgAdmin, 0, len(c.admins)) +func (c *orgSetupCommands) createdAdmins() []OrgAdmin { + users := make([]OrgAdmin, 0, len(c.admins)) for _, admin := range c.admins { if admin.ID != "" && admin.Human == nil { + users = append(users, &AssignedOrgAdmin{ID: admin.ID}) continue } if admin.Human != nil { diff --git a/internal/command/org_test.go b/internal/command/org_test.go index 4b6fd7afe5..4239be760a 100644 --- a/internal/command/org_test.go +++ b/internal/command/org_test.go @@ -1531,8 +1531,8 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ObjectDetails: &domain.ObjectDetails{ ResourceOwner: "orgID", }, - CreatedAdmins: []*CreatedOrgAdmin{ - { + OrgAdmins: []OrgAdmin{ + &CreatedOrgAdmin{ ID: "userID", }, }, @@ -1574,7 +1574,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ObjectDetails: &domain.ObjectDetails{ ResourceOwner: "custom-org-ID", }, - CreatedAdmins: []*CreatedOrgAdmin{}, + OrgAdmins: []OrgAdmin{}, }, }, }, @@ -1641,7 +1641,11 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ObjectDetails: &domain.ObjectDetails{ ResourceOwner: "orgID", }, - CreatedAdmins: []*CreatedOrgAdmin{}, + OrgAdmins: []OrgAdmin{ + &AssignedOrgAdmin{ + ID: "userID", + }, + }, }, }, }, @@ -1751,8 +1755,8 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ObjectDetails: &domain.ObjectDetails{ ResourceOwner: "orgID", }, - CreatedAdmins: []*CreatedOrgAdmin{ - { + OrgAdmins: []OrgAdmin{ + &CreatedOrgAdmin{ ID: "userID", PAT: &PersonalAccessToken{ ObjectRoot: models.ObjectRoot{ diff --git a/proto/zitadel/org/v2beta/org_service.proto b/proto/zitadel/org/v2beta/org_service.proto index 28c823a89b..387b2cb825 100644 --- a/proto/zitadel/org/v2beta/org_service.proto +++ b/proto/zitadel/org/v2beta/org_service.proto @@ -558,6 +558,18 @@ message CreatedAdmin { 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{ // The timestamp of the organization was created. google.protobuf.Timestamp creation_date = 1 [ @@ -577,8 +589,8 @@ message CreateOrganizationResponse{ } ]; - // The admins created for the Organization - repeated CreatedAdmin created_admins = 3; + // The admins created/assigned for the Organization + repeated OrganizationAdmin organization_admins = 3; } message UpdateOrganizationRequest { @@ -939,3 +951,5 @@ message DeleteOrganizationMetadataResponse{ } ]; } + +