diff --git a/internal/api/grpc/org/v2beta/helper.go b/internal/api/grpc/org/v2beta/helper.go new file mode 100644 index 0000000000..9d28c5d7a5 --- /dev/null +++ b/internal/api/grpc/org/v2beta/helper.go @@ -0,0 +1,77 @@ +package org + +import ( + "time" + + object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" + org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" + + v2beta "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" + + org_pb "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func OrganizationViewToPb(org *query.Org) *org_pb.Organization { + return &org_pb.Organization{ + Id: org.ID, + State: OrgStateToPb(org.State), + Name: org.Name, + PrimaryDomain: org.Domain, + Details: ToViewDetailsPb( + org.Sequence, + org.CreationDate, + org.ChangeDate, + org.ResourceOwner, + ), + } +} + +func OrgStateToPb(state domain.OrgState) org_pb.OrganizationState { + switch state { + case domain.OrgStateActive: + return org_pb.OrganizationState_ORGANIZATION_STATE_ACTIVE + case domain.OrgStateInactive: + return org_pb.OrganizationState_ORGANIZATION_STATE_INACTIVE + default: + return org_pb.OrganizationState_ORGANIZATION_STATE_UNSPECIFIED + } +} + +func ToViewDetailsPb( + sequence uint64, + creationDate, + changeDate time.Time, + resourceOwner string, +) *v2beta.Details { + details := &v2beta.Details{ + Sequence: sequence, + ResourceOwner: resourceOwner, + } + if !creationDate.IsZero() { + details.CreationDate = timestamppb.New(creationDate) + } + if !changeDate.IsZero() { + details.ChangeDate = timestamppb.New(changeDate) + } + return details +} + +func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.CreateOrganizationResponse, err error) { + admins := make([]*org.CreateOrganizationResponse_CreatedAdmin, len(createdOrg.CreatedAdmins)) + for i, admin := range createdOrg.CreatedAdmins { + admins[i] = &org.CreateOrganizationResponse_CreatedAdmin{ + UserId: admin.ID, + EmailCode: admin.EmailCode, + PhoneCode: admin.PhoneCode, + } + } + return &org.CreateOrganizationResponse{ + Details: object.DomainToDetailsPb(createdOrg.ObjectDetails), + OrganizationId: createdOrg.ObjectDetails.ResourceOwner, + CreatedAdmins: 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 16ae45836c..5f46d51697 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - "github.com/brianvoe/gofakeit/v6" "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + gofakeit "github.com/brianvoe/gofakeit/v6" "github.com/zitadel/zitadel/internal/integration" org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" "github.com/zitadel/zitadel/pkg/grpc/user/v2" @@ -40,6 +40,51 @@ func TestMain(m *testing.M) { }()) } +func TestServer_GetOrganizationByID(t *testing.T) { + orgName := gofakeit.Name() + orgId, err := createOrg(orgName) + if err != nil { + assert.Fail(t, "unable to create org") + } + + tests := []struct { + name string + ctx context.Context + req *org.GetOrganizationByIDRequest + want *org.GetOrganizationByIDResponse + wantErr bool + }{ + { + name: "get organization happy path", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + req: &org.GetOrganizationByIDRequest{ + Id: orgId, + }, + }, + { + name: "get organization that doesn't exist", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + req: &org.GetOrganizationByIDRequest{ + Id: "non existing organization", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.GetOrganizationByID(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, orgId, got.Organization.Id) + require.Equal(t, orgName, got.Organization.Name) + }) + } +} + func TestServer_CreateOrganization(t *testing.T) { idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id) @@ -188,7 +233,7 @@ func TestServer_CreateOrganization(t *testing.T) { } func TestServer_UpdateOrganization(t *testing.T) { - orgName := "new_org_name" + orgName := gofakeit.Name() orgId, err := createOrg(orgName) if err != nil { assert.Fail(t, "unable to create org") @@ -252,8 +297,11 @@ func createOrg(orgName string) (string, error) { Name: orgName, }, ) + if err != nil { + return "", err + } - return org.OrganizationId, err + return org.OrganizationId, nil } func assertCreatedAdmin(t *testing.T, expected, got *org.CreateOrganizationResponse_CreatedAdmin) { diff --git a/internal/api/grpc/org/v2beta/org.go b/internal/api/grpc/org/v2beta/org.go index ccb001a793..2dead655a8 100644 --- a/internal/api/grpc/org/v2beta/org.go +++ b/internal/api/grpc/org/v2beta/org.go @@ -7,10 +7,10 @@ import ( user "github.com/zitadel/zitadel/internal/api/grpc/user/v2beta" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/zerrors" - org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" + v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" ) -func (s *Server) CreateOrganization(ctx context.Context, request *org.CreateOrganizationRequest) (*org.CreateOrganizationResponse, error) { +func (s *Server) CreateOrganization(ctx context.Context, request *v2beta_org.CreateOrganizationRequest) (*v2beta_org.CreateOrganizationResponse, error) { orgSetup, err := createOrganizationRequestToCommand(request) if err != nil { return nil, err @@ -22,18 +22,28 @@ func (s *Server) CreateOrganization(ctx context.Context, request *org.CreateOrga return createdOrganizationToPb(createdOrg) } -func (s *Server) UpdateOrganization(ctx context.Context, request *org.UpdateOrganizationRequest) (*org.UpdateOrganizationResponse, error) { - updated_org, err := s.command.UpdateOrg(ctx, request.Id, request.Name) +func (s *Server) UpdateOrganization(ctx context.Context, request *v2beta_org.UpdateOrganizationRequest) (*v2beta_org.UpdateOrganizationResponse, error) { + org, err := s.command.UpdateOrg(ctx, request.Id, request.Name) if err != nil { return nil, err } - return &org.UpdateOrganizationResponse{ - Details: object.DomainToDetailsPb(updated_org), + return &v2beta_org.UpdateOrganizationResponse{ + Details: object.DomainToDetailsPb(org), }, nil } -func createOrganizationRequestToCommand(request *org.CreateOrganizationRequest) (*command.OrgSetup, error) { +func (s *Server) GetOrganizationByID(ctx context.Context, request *v2beta_org.GetOrganizationByIDRequest) (*v2beta_org.GetOrganizationByIDResponse, error) { + org, err := s.query.OrgByID(ctx, true, request.Id) + if err != nil { + return nil, err + } + return &v2beta_org.GetOrganizationByIDResponse{ + Organization: OrganizationViewToPb(org), + }, nil +} + +func createOrganizationRequestToCommand(request *v2beta_org.CreateOrganizationRequest) (*command.OrgSetup, error) { admins, err := createOrganizationRequestAdminsToCommand(request.GetAdmins()) if err != nil { return nil, err @@ -45,7 +55,7 @@ func createOrganizationRequestToCommand(request *org.CreateOrganizationRequest) }, nil } -func createOrganizationRequestAdminsToCommand(requestAdmins []*org.CreateOrganizationRequest_Admin) (admins []*command.OrgSetupAdmin, err error) { +func createOrganizationRequestAdminsToCommand(requestAdmins []*v2beta_org.CreateOrganizationRequest_Admin) (admins []*command.OrgSetupAdmin, err error) { admins = make([]*command.OrgSetupAdmin, len(requestAdmins)) for i, admin := range requestAdmins { admins[i], err = createOrganizationRequestAdminToCommand(admin) @@ -56,14 +66,14 @@ func createOrganizationRequestAdminsToCommand(requestAdmins []*org.CreateOrganiz return admins, nil } -func createOrganizationRequestAdminToCommand(admin *org.CreateOrganizationRequest_Admin) (*command.OrgSetupAdmin, error) { +func createOrganizationRequestAdminToCommand(admin *v2beta_org.CreateOrganizationRequest_Admin) (*command.OrgSetupAdmin, error) { switch a := admin.GetUserType().(type) { - case *org.CreateOrganizationRequest_Admin_UserId: + case *v2beta_org.CreateOrganizationRequest_Admin_UserId: return &command.OrgSetupAdmin{ ID: a.UserId, Roles: admin.GetRoles(), }, nil - case *org.CreateOrganizationRequest_Admin_Human: + case *v2beta_org.CreateOrganizationRequest_Admin_Human: human, err := user.AddUserRequestToAddHuman(a.Human) if err != nil { return nil, err @@ -76,19 +86,3 @@ func createOrganizationRequestAdminToCommand(admin *org.CreateOrganizationReques return nil, zerrors.ThrowUnimplementedf(nil, "ORGv2-SD2r1", "userType oneOf %T in method AddOrganization not implemented", a) } } - -func createdOrganizationToPb(createdOrg *command.CreatedOrg) (_ *org.CreateOrganizationResponse, err error) { - admins := make([]*org.CreateOrganizationResponse_CreatedAdmin, len(createdOrg.CreatedAdmins)) - for i, admin := range createdOrg.CreatedAdmins { - admins[i] = &org.CreateOrganizationResponse_CreatedAdmin{ - UserId: admin.ID, - EmailCode: admin.EmailCode, - PhoneCode: admin.PhoneCode, - } - } - return &org.CreateOrganizationResponse{ - Details: object.DomainToDetailsPb(createdOrg.ObjectDetails), - OrganizationId: createdOrg.ObjectDetails.ResourceOwner, - CreatedAdmins: admins, - }, nil -} diff --git a/internal/command/org.go b/internal/command/org.go index 68c9ae982e..595dc042a1 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -356,7 +356,7 @@ func (c *Commands) UpdateOrg(ctx context.Context, orgID, name string) (*domain.O return nil, zerrors.ThrowNotFound(nil, "ORG-1MRds", "Errors.Org.NotFound") } if orgWriteModel.Name == name { - return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-4VSdf", "Errors.Org.NotChanged") + return writeModelToObjectDetails(&orgWriteModel.WriteModel), nil } orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel) events := make([]eventstore.Command, 0) diff --git a/proto/zitadel/org/v2beta/org_service.proto b/proto/zitadel/org/v2beta/org_service.proto index c1cfd10792..bd51e53952 100644 --- a/proto/zitadel/org/v2beta/org_service.proto +++ b/proto/zitadel/org/v2beta/org_service.proto @@ -6,10 +6,11 @@ package zitadel.org.v2beta; import "zitadel/object/v2beta/object.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; import "zitadel/user/v2beta/auth.proto"; -import "zitadel/user/v2beta/email.proto"; -import "zitadel/user/v2beta/phone.proto"; -import "zitadel/user/v2beta/idp.proto"; -import "zitadel/user/v2beta/password.proto"; +import "zitadel/org/v2beta/org.proto"; +// import "zitadel/user/v2beta/email.proto"; +// import "zitadel/user/v2beta/phone.proto"; +// import "zitadel/user/v2beta/idp.proto"; +// import "zitadel/user/v2beta/password.proto"; import "zitadel/user/v2beta/user.proto"; import "zitadel/user/v2beta/user_service.proto"; import "google/api/annotations.proto"; @@ -167,6 +168,33 @@ service OrganizationService { }; } + rpc GetOrganizationByID(GetOrganizationByIDRequest) returns (GetOrganizationByIDResponse) { + option (google.api.http) = { + get: "/v2beta/organizations" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "org.read"; + } + http_response: { + success_code: 200 + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Organizations"; + summary: "Get Organization By ID"; + description: "Returns an organization by its ID." + responses: { + key: "200"; + value: { + description: "requested organization found"; + }; + }; + }; + } + } message CreateOrganizationRequest{ @@ -228,3 +256,20 @@ message UpdateOrganizationRequest { message UpdateOrganizationResponse { zitadel.object.v2beta.Details details = 1; } + +message GetOrganizationByIDRequest { + string id = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + min_length: 1; + max_length: 200; + example: "\"69629012906488334\""; + description: "Organization ID of the organization you want to get." + } + ]; +} + +message GetOrganizationByIDResponse { + zitadel.org.v2beta.Organization organization = 1; +}