fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! refactor(api): moving organization API resourced based

This commit is contained in:
Iraq Jaber
2025-05-03 12:38:45 +02:00
parent 9dbcfc255d
commit 3e482e501e
5 changed files with 622 additions and 247 deletions

View File

@@ -2,9 +2,8 @@ package org
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/api/authz"
metadata "github.com/zitadel/zitadel/internal/api/grpc/metadata/v2beta"
v2beta_object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
@@ -17,8 +16,6 @@ import (
v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
v2beta "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
"google.golang.org/protobuf/types/known/timestamppb"
)
// NOTE: most of this code is copied from `internal/api/grpc/admin/*`, as we will eventually axe the previous versons of the API,
@@ -47,7 +44,7 @@ func OrganizationViewToPb(org *query.Org) *v2beta_org.Organization {
State: OrgStateToPb(org.State),
Name: org.Name,
PrimaryDomain: org.Domain,
Details: ToViewDetailsPb(
Details: v2beta_object.ToViewDetailsPb(
org.Sequence,
org.CreationDate,
org.ChangeDate,
@@ -67,25 +64,6 @@ func OrgStateToPb(state domain.OrgState) v2beta_org.OrgState {
}
}
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 {
@@ -166,7 +144,7 @@ func OrgViewToPb(org *query.Org) *v2beta_org.Organization {
State: OrgStateToPb(org.State),
Name: org.Name,
PrimaryDomain: org.Domain,
Details: ToViewDetailsPb(
Details: v2beta_object.ToViewDetailsPb(
org.Sequence,
org.CreationDate,
org.ChangeDate,
@@ -243,8 +221,35 @@ func GenerateOrgDomainValidationRequestToDomain(ctx context.Context, req *v2beta
func ValidateOrgDomainRequestToDomain(ctx context.Context, req *v2beta_org.VerifyOrganizationDomainRequest) *domain.OrgDomain {
return &domain.OrgDomain{
ObjectRoot: models.ObjectRoot{
AggregateID: authz.GetCtxData(ctx).OrgID,
AggregateID: req.OrganizationId,
},
Domain: req.Domain,
}
}
func BulkSetOrgMetadataToDomain(req *v2beta_org.SetOrganizationMetadataRequest) []*domain.Metadata {
metadata := make([]*domain.Metadata, len(req.Metadata))
for i, data := range req.Metadata {
metadata[i] = &domain.Metadata{
Key: data.Key,
Value: data.Value,
}
}
return metadata
}
func ListOrgMetadataToDomain(request *v2beta_org.ListOrganizationMetadataRequest) (*query.OrgMetadataSearchQueries, error) {
offset, limit, asc := v2beta_object.ListQueryToModel(request.Query)
queries, err := metadata.OrgMetadataQueriesToQuery(request.Queries)
if err != nil {
return nil, err
}
return &query.OrgMetadataSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
Queries: queries,
}, nil
}

View File

@@ -4,6 +4,7 @@ package org_test
import (
"context"
"errors"
"fmt"
"os"
"testing"
@@ -16,6 +17,7 @@ import (
gofakeit "github.com/brianvoe/gofakeit/v6"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/admin"
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"
@@ -23,10 +25,11 @@ import (
)
var (
CTX context.Context
Instance *integration.Instance
Client v2beta_org.OrganizationServiceClient
User *user.AddHumanUserResponse
CTX context.Context
Instance *integration.Instance
Client v2beta_org.OrganizationServiceClient
AdminClient admin.AdminServiceClient
User *user.AddHumanUserResponse
)
func TestMain(m *testing.M) {
@@ -36,6 +39,7 @@ func TestMain(m *testing.M) {
Instance = integration.NewInstance(ctx)
Client = Instance.Client.OrgV2beta
AdminClient = Instance.Client.Admin
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
@@ -567,7 +571,6 @@ func TestServer_AddOListDeleterganizationDomain(t *testing.T) {
Domain: domain,
})
require.NoError(t, err)
// check details
assert.NotZero(t, addOrgDomainRes.GetDetails().GetSequence())
gotCD := addOrgDomainRes.GetDetails().GetChangeDate().AsTime()
@@ -671,6 +674,407 @@ func TestServer_AddOListDeleterganizationDomain(t *testing.T) {
require.False(t, found, "deleted domain found")
}
func TestServer_ValidateOrganizationDomain(t *testing.T) {
orgs, _, err := createOrgs(1)
if err != nil {
assert.Fail(t, "unable to create org")
}
orgId := orgs[0].OrganizationId
_, err = AdminClient.UpdateDomainPolicy(CTX, &admin.UpdateDomainPolicyRequest{
ValidateOrgDomains: true,
})
require.NoError(t, err)
domain := "www.domainnn.com"
_, err = Client.AddOrganizationDomain(CTX, &v2beta_org.AddOrganizationDomainRequest{
OrganizationId: orgId,
Domain: domain,
})
require.NoError(t, err)
tests := []struct {
name string
ctx context.Context
req *v2beta_org.GenerateOrganizationDomainValidationRequest
err error
}{
{
name: "validate org http happy path",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: orgId,
Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
},
},
{
name: "validate org http non existnetn org id",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: "non existent org id",
Domain: domain,
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
},
// BUG: this should be 'organization does not exist'
err: errors.New("Domain doesn't exist on organization"),
},
{
name: "validate org http non existnetn domain",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
req: &v2beta_org.GenerateOrganizationDomainValidationRequest{
OrganizationId: orgId,
Domain: "non existent domain",
Type: org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP,
},
err: errors.New("Domain doesn't exist on organization"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.GenerateOrganizationDomainValidation(tt.ctx, tt.req)
if tt.err != nil {
require.Contains(t, err.Error(), tt.err.Error())
return
}
require.NoError(t, err)
require.NotEmpty(t, got.Token)
require.Contains(t, got.Url, domain)
})
}
}
func TestServer_SetOrganizationMetadata(t *testing.T) {
orgs, _, err := createOrgs(1)
if err != nil {
assert.Fail(t, "unable to create org")
}
orgId := orgs[0].OrganizationId
tests := []struct {
name string
ctx context.Context
setupFunc func()
orgId string
key string
value string
err error
}{
{
name: "set org metadata",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
orgId: orgId,
key: "key1",
value: "value1",
},
{
name: "set org metadata on non existant org",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
orgId: "non existant orgid",
key: "key2",
value: "value2",
err: errors.New("Organisation not found"),
},
{
name: "update org metadata",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
setupFunc: func() {
_, err := Client.SetOrganizationMetadata(CTX, &v2beta_org.SetOrganizationMetadataRequest{
Id: orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: "key3",
Value: []byte("value3"),
},
},
})
require.NoError(t, err)
},
orgId: orgId,
key: "key4",
value: "value4",
},
{
name: "update org metadata with same value",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
setupFunc: func() {
_, err := Client.SetOrganizationMetadata(CTX, &v2beta_org.SetOrganizationMetadataRequest{
Id: orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: "key5",
Value: []byte("value5"),
},
},
})
require.NoError(t, err)
},
orgId: orgId,
key: "key5",
value: "value5",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setupFunc != nil {
tt.setupFunc()
}
got, err := Client.SetOrganizationMetadata(tt.ctx, &v2beta_org.SetOrganizationMetadataRequest{
Id: tt.orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: tt.key,
Value: []byte(tt.value),
},
},
})
if tt.err != nil {
require.Contains(t, err.Error(), tt.err.Error())
return
}
require.NoError(t, err)
// check details
assert.NotZero(t, got.GetDetails().GetSequence())
gotCD := got.GetDetails().GetChangeDate().AsTime()
now := time.Now()
assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute))
assert.NotEmpty(t, got.GetDetails().GetResourceOwner())
// check metadata
listMetadataRes, err := Client.ListOrganizationMetadata(tt.ctx, &v2beta_org.ListOrganizationMetadataRequest{
Id: orgId,
})
require.NoError(t, err)
foundMetadata := false
foundMetadataKeyCount := 0
for _, res := range listMetadataRes.Result {
if res.Key == tt.key {
foundMetadataKeyCount += 1
}
if res.Key == tt.key &&
string(res.Value) == tt.value {
foundMetadata = true
}
}
require.True(t, foundMetadata, "unable to find added metadata")
require.Equal(t, 1, foundMetadataKeyCount, "same metadata key found multiple times")
})
}
}
func TestServer_ListOrganizationMetadata(t *testing.T) {
orgs, _, err := createOrgs(1)
if err != nil {
assert.Fail(t, "unable to create org")
}
orgId := orgs[0].OrganizationId
tests := []struct {
name string
ctx context.Context
setupFunc func()
orgId string
keyValuPars []struct {
key string
value string
}
}{
{
name: "list org metadata happy path",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
setupFunc: func() {
_, err := Client.SetOrganizationMetadata(CTX, &v2beta_org.SetOrganizationMetadataRequest{
Id: orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: "key1",
Value: []byte("value1"),
},
},
})
require.NoError(t, err)
},
orgId: orgId,
keyValuPars: []struct{ key, value string }{
{
key: "key1",
value: "value1",
},
},
},
{
name: "list multiple org metadata happy path",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
setupFunc: func() {
_, err := Client.SetOrganizationMetadata(CTX, &v2beta_org.SetOrganizationMetadataRequest{
Id: orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: "key2",
Value: []byte("value2"),
},
{
Key: "key3",
Value: []byte("value3"),
},
{
Key: "key4",
Value: []byte("value4"),
},
},
})
require.NoError(t, err)
},
orgId: orgId,
keyValuPars: []struct{ key, value string }{
{
key: "key2",
value: "value2",
},
{
key: "key3",
value: "value3",
},
{
key: "key4",
value: "value4",
},
},
},
{
name: "list org metadata for non existent org",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
orgId: "non existent orgid",
keyValuPars: []struct{ key, value string }{
// {
// key: "key1",
// value: "value1",
// },
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setupFunc != nil {
tt.setupFunc()
}
got, err := Client.ListOrganizationMetadata(tt.ctx, &v2beta_org.ListOrganizationMetadataRequest{
Id: tt.orgId,
})
// if tt.err != nil {
// require.Contains(t, err.Error(), tt.err.Error())
// return
// }
require.NoError(t, err)
foundMetadataCount := 0
for _, kv := range tt.keyValuPars {
for _, res := range got.Result {
if res.Key == kv.key &&
string(res.Value) == kv.value {
foundMetadataCount += 1
}
}
}
require.Equal(t, len(tt.keyValuPars), foundMetadataCount)
})
}
}
func TestServer_DeleteOrganizationMetadata(t *testing.T) {
orgs, _, err := createOrgs(1)
if err != nil {
assert.Fail(t, "unable to create org")
}
orgId := orgs[0].OrganizationId
tests := []struct {
name string
ctx context.Context
setupFunc func()
orgId string
keyValuPars []struct {
key string
value string
}
err error
}{
{
name: "delete org metadata happy path",
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner),
setupFunc: func() {
_, err := Client.SetOrganizationMetadata(CTX, &v2beta_org.SetOrganizationMetadataRequest{
Id: orgId,
Metadata: []*v2beta_org.Metadata{
{
Key: "key1",
Value: []byte("value1"),
},
},
})
require.NoError(t, err)
},
orgId: orgId,
keyValuPars: []struct{ key, value string }{
{
key: "key1",
value: "value1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setupFunc != nil {
tt.setupFunc()
}
// check metadata exists
listOrgMetadataRes, err := Client.ListOrganizationMetadata(tt.ctx, &v2beta_org.ListOrganizationMetadataRequest{
Id: tt.orgId,
})
require.NoError(t, err)
foundMetadataCount := 0
for _, kv := range tt.keyValuPars {
for _, res := range listOrgMetadataRes.Result {
if res.Key == kv.key &&
string(res.Value) == kv.value {
foundMetadataCount += 1
}
}
}
require.Equal(t, len(tt.keyValuPars), foundMetadataCount)
// run delete
_, err = Client.DeleteOrganizationMetadata(tt.ctx, &v2beta_org.DeleteOrganizationMetadataRequest{
Id: tt.orgId,
})
if tt.err != nil {
require.Contains(t, err.Error(), tt.err.Error())
return
}
require.NoError(t, err)
// check metadata was definitely deleted
listOrgMetadataRes, err = Client.ListOrganizationMetadata(tt.ctx, &v2beta_org.ListOrganizationMetadataRequest{
Id: tt.orgId,
})
require.NoError(t, err)
foundMetadataCount = 0
for _, kv := range tt.keyValuPars {
for _, res := range listOrgMetadataRes.Result {
if res.Key == kv.key &&
string(res.Value) == kv.value {
foundMetadataCount += 1
}
}
}
})
}
}
func assertCreatedAdmin(t *testing.T, expected, got *v2beta_org.CreateOrganizationResponse_CreatedAdmin) {
if expected.GetUserId() != "" {
assert.NotEmpty(t, got.GetUserId())

View File

@@ -3,6 +3,7 @@ package org
import (
"context"
metadata "github.com/zitadel/zitadel/internal/api/grpc/metadata/v2beta"
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
user "github.com/zitadel/zitadel/internal/api/grpc/user/v2beta"
"github.com/zitadel/zitadel/internal/command"
@@ -70,6 +71,41 @@ func (s *Server) DeleteOrganization(ctx context.Context, request *v2beta_org.Del
}, nil
}
func (s *Server) SetOrganizationMetadata(ctx context.Context, request *v2beta_org.SetOrganizationMetadataRequest) (*v2beta_org.SetOrganizationMetadataResponse, error) {
result, err := s.command.BulkSetOrgMetadata(ctx, request.Id, BulkSetOrgMetadataToDomain(request)...)
if err != nil {
return nil, err
}
return &org.SetOrganizationMetadataResponse{
Details: object.DomainToDetailsPb(result),
}, nil
}
func (s *Server) ListOrganizationMetadata(ctx context.Context, request *v2beta_org.ListOrganizationMetadataRequest) (*v2beta_org.ListOrganizationMetadataResponse, error) {
metadataQueries, err := ListOrgMetadataToDomain(request)
if err != nil {
return nil, err
}
res, err := s.query.SearchOrgMetadata(ctx, true, request.Id, metadataQueries, false)
if err != nil {
return nil, err
}
return &v2beta_org.ListOrganizationMetadataResponse{
Result: metadata.OrgMetadataListToPb(res.Metadata),
Details: object.ToListDetails(res.SearchResponse),
}, nil
}
func (s *Server) DeleteOrganizationMetadata(ctx context.Context, request *v2beta_org.DeleteOrganizationMetadataRequest) (*v2beta_org.DeleteOrganizationMetadataResponse, error) {
result, err := s.command.BulkRemoveOrgMetadata(ctx, request.Id, request.Keys...)
if err != nil {
return nil, err
}
return &v2beta_org.DeleteOrganizationMetadataResponse{
Details: object.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) DeactivateOrganization(ctx context.Context, request *org.DeactivateOrganizationRequest) (*org.DeactivateOrganizationResponse, error) {
objectDetails, err := s.command.DeactivateOrg(ctx, request.Id)
if err != nil {