mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-07 22:55:12 +00:00
feat: instance domain APIs with relational tables (#11051)
# Which Problems Are Solved This PR implements the endpoints listed in https://github.com/zitadel/zitadel/issues/10444 . # How the Problems Are Solved The implementation follows the same pattern as the Instance one. The `Add` and `Remove` are implementing the `Commander` interface, while `List` Domains endpoints are implementing the `Querier` interace. The PR adds also some forgotten calls to the relational logic for the Instance API. The commits are identifiable by the label `instance:` prepended to them. This is the complete list: bafce01b0 , 6fe7257ab , e27bc9c34 , 8133ede73 , fde667953 , a3e1135ed , ee1a6f9c1 # Additional Context - Closes #10444 --------- Co-authored-by: Elio Bischof <[email protected]> Co-authored-by: Lebogang Phoshoko <[email protected]> Co-authored-by: Wim Van Laer <[email protected]> Co-authored-by: Max Peintner <[email protected]> Co-authored-by: Gayathri Vijayan <[email protected]>
This commit is contained in:
co-authored by
Elio Bischof
Lebogang Phoshoko
Wim Van Laer
Max Peintner
Gayathri Vijayan
parent
d79bfe6eba
commit
b339a3e696
@@ -0,0 +1,191 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
filter_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
/*
|
||||
* Domain Model to GRPC v2Beta
|
||||
*/
|
||||
|
||||
func DomainInstanceDomainListModelToGRPCBetaResponse(dms []*domain.InstanceDomain) []*instance_v2beta.Domain {
|
||||
toReturn := make([]*instance_v2beta.Domain, len(dms))
|
||||
for i, domain := range dms {
|
||||
isGenerated := domain.IsGenerated != nil && *domain.IsGenerated
|
||||
isPrimary := domain.IsPrimary != nil && *domain.IsPrimary
|
||||
toReturn[i] = &instance_v2beta.Domain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
Primary: isPrimary,
|
||||
Generated: isGenerated,
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func TrustedDomainInstanceDomainListModelToGRPCBetaResponse(dms []*domain.InstanceDomain) []*instance_v2beta.TrustedDomain {
|
||||
toReturn := make([]*instance_v2beta.TrustedDomain, len(dms))
|
||||
for i, domain := range dms {
|
||||
toReturn[i] = &instance_v2beta.TrustedDomain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
/*
|
||||
* Domain Model to GRPC v2
|
||||
*/
|
||||
|
||||
func DomainInstanceDomainListModelToGRPCResponse(dms []*domain.InstanceDomain) []*instance_v2.CustomDomain {
|
||||
toReturn := make([]*instance_v2.CustomDomain, len(dms))
|
||||
for i, domain := range dms {
|
||||
isGenerated := domain.IsGenerated != nil && *domain.IsGenerated
|
||||
isPrimary := domain.IsPrimary != nil && *domain.IsPrimary
|
||||
toReturn[i] = &instance_v2.CustomDomain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
Primary: isPrimary,
|
||||
Generated: isGenerated,
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func TrustedDomainInstanceDomainListModelToGRPCResponse(dms []*domain.InstanceDomain) []*instance_v2.TrustedDomain {
|
||||
toReturn := make([]*instance_v2.TrustedDomain, len(dms))
|
||||
for i, domain := range dms {
|
||||
toReturn[i] = &instance_v2.TrustedDomain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
/*
|
||||
* GRPC v2Beta Requests to GRPC v2
|
||||
*/
|
||||
|
||||
func ListCustomDomainsBetaRequestToV2Request(in *instance_v2beta.ListCustomDomainsRequest) *instance_v2.ListCustomDomainsRequest {
|
||||
return &instance_v2.ListCustomDomainsRequest{
|
||||
InstanceId: in.GetInstanceId(),
|
||||
Pagination: &filter_v2.PaginationRequest{
|
||||
Offset: in.GetPagination().GetOffset(),
|
||||
Limit: in.GetPagination().GetLimit(),
|
||||
Asc: in.GetPagination().GetAsc(),
|
||||
},
|
||||
SortingColumn: listCustomDomainsBetaSortingColToV2Request(in.GetSortingColumn()),
|
||||
Filters: listCustomDomainsQueriesToV2Request(in.GetQueries()),
|
||||
}
|
||||
}
|
||||
|
||||
func listCustomDomainsBetaSortingColToV2Request(domainFieldName instance_v2beta.DomainFieldName) instance_v2.DomainFieldName {
|
||||
switch domainFieldName {
|
||||
case instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE
|
||||
case instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN
|
||||
case instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||
return instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED
|
||||
case instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
|
||||
return instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY
|
||||
case instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func listCustomDomainsQueriesToV2Request(domainSearchQuery []*instance_v2beta.DomainSearchQuery) []*instance_v2.CustomDomainFilter {
|
||||
toReturn := make([]*instance_v2.CustomDomainFilter, len(domainSearchQuery))
|
||||
|
||||
for i, query := range domainSearchQuery {
|
||||
switch assertedType := query.GetQuery().(type) {
|
||||
case *instance_v2beta.DomainSearchQuery_DomainQuery:
|
||||
filter := &instance_v2.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance_v2.DomainFilter{
|
||||
Domain: assertedType.DomainQuery.GetDomain(),
|
||||
Method: assertedType.DomainQuery.GetMethod(),
|
||||
},
|
||||
}
|
||||
toReturn[i] = &instance_v2.CustomDomainFilter{
|
||||
Filter: filter,
|
||||
}
|
||||
case *instance_v2beta.DomainSearchQuery_GeneratedQuery:
|
||||
filter := &instance_v2.CustomDomainFilter_GeneratedFilter{
|
||||
GeneratedFilter: assertedType.GeneratedQuery.GetGenerated(),
|
||||
}
|
||||
toReturn[i] = &instance_v2.CustomDomainFilter{
|
||||
Filter: filter,
|
||||
}
|
||||
case *instance_v2beta.DomainSearchQuery_PrimaryQuery:
|
||||
filter := &instance_v2.CustomDomainFilter_PrimaryFilter{
|
||||
PrimaryFilter: assertedType.PrimaryQuery.GetPrimary(),
|
||||
}
|
||||
toReturn[i] = &instance_v2.CustomDomainFilter{
|
||||
Filter: filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func ListTrustedDomainsBetaRequestToV2Request(in *instance_v2beta.ListTrustedDomainsRequest) *instance_v2.ListTrustedDomainsRequest {
|
||||
return &instance_v2.ListTrustedDomainsRequest{
|
||||
InstanceId: in.GetInstanceId(),
|
||||
Pagination: &filter_v2.PaginationRequest{
|
||||
Offset: in.GetPagination().GetOffset(),
|
||||
Limit: in.GetPagination().GetLimit(),
|
||||
Asc: in.GetPagination().GetAsc(),
|
||||
},
|
||||
SortingColumn: listTrustedDomainsBetaSortingColToV2Request(in.GetSortingColumn()),
|
||||
Filters: listTrustedDomainsQueriesToV2Request(in.GetQueries()),
|
||||
}
|
||||
}
|
||||
|
||||
func listTrustedDomainsBetaSortingColToV2Request(domainFieldName instance_v2beta.TrustedDomainFieldName) instance_v2.TrustedDomainFieldName {
|
||||
switch domainFieldName {
|
||||
case instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE
|
||||
case instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN
|
||||
case instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func listTrustedDomainsQueriesToV2Request(domainSearchQuery []*instance_v2beta.TrustedDomainSearchQuery) []*instance_v2.TrustedDomainFilter {
|
||||
toReturn := make([]*instance_v2.TrustedDomainFilter, len(domainSearchQuery))
|
||||
|
||||
for i, query := range domainSearchQuery {
|
||||
if assertedType, ok := query.GetQuery().(*instance_v2beta.TrustedDomainSearchQuery_DomainQuery); ok {
|
||||
filter := &instance_v2.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance_v2.DomainFilter{
|
||||
Domain: assertedType.DomainQuery.GetDomain(),
|
||||
Method: assertedType.DomainQuery.GetMethod(),
|
||||
},
|
||||
}
|
||||
toReturn[i] = &instance_v2.TrustedDomainFilter{
|
||||
Filter: filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
return toReturn
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
filter_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
filter_v2beta "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
)
|
||||
|
||||
func TestListCustomDomainsBetaRequestToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance_v2beta.ListCustomDomainsRequest
|
||||
expectedRequest *instance_v2.ListCustomDomainsRequest
|
||||
}{
|
||||
{
|
||||
testName: "with all fields",
|
||||
inputRequest: &instance_v2beta.ListCustomDomainsRequest{
|
||||
InstanceId: "instance1",
|
||||
Pagination: &filter_v2beta.PaginationRequest{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Queries: []*instance_v2beta.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance_v2beta.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance_v2beta.DomainQuery{
|
||||
Domain: "test.com",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Query: &instance_v2beta.DomainSearchQuery_GeneratedQuery{
|
||||
GeneratedQuery: &instance_v2beta.DomainGeneratedQuery{
|
||||
Generated: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Query: &instance_v2beta.DomainSearchQuery_PrimaryQuery{
|
||||
PrimaryQuery: &instance_v2beta.DomainPrimaryQuery{
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequest: &instance_v2.ListCustomDomainsRequest{
|
||||
InstanceId: "instance1",
|
||||
Pagination: &filter_v2.PaginationRequest{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Filters: []*instance_v2.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance_v2.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance_v2.DomainFilter{
|
||||
Domain: "test.com",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &instance_v2.CustomDomainFilter_GeneratedFilter{
|
||||
GeneratedFilter: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &instance_v2.CustomDomainFilter_PrimaryFilter{
|
||||
PrimaryFilter: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "empty request",
|
||||
inputRequest: &instance_v2beta.ListCustomDomainsRequest{},
|
||||
expectedRequest: &instance_v2.ListCustomDomainsRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
Filters: []*instance_v2.CustomDomainFilter{},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with sorting only",
|
||||
inputRequest: &instance_v2beta.ListCustomDomainsRequest{
|
||||
SortingColumn: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
expectedRequest: &instance_v2.ListCustomDomainsRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
SortingColumn: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance_v2.CustomDomainFilter{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := ListCustomDomainsBetaRequestToV2Request(tc.inputRequest)
|
||||
assert.Equal(t, tc.expectedRequest, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listCustomDomainsBetaSortingColToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputFieldName instance_v2beta.DomainFieldName
|
||||
expecteFieldName instance_v2.DomainFieldName
|
||||
}{
|
||||
{
|
||||
testName: "creation date",
|
||||
inputFieldName: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
{
|
||||
testName: "domain",
|
||||
inputFieldName: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
{
|
||||
testName: "generated",
|
||||
inputFieldName: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
|
||||
},
|
||||
{
|
||||
testName: "primary",
|
||||
inputFieldName: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
},
|
||||
{
|
||||
testName: "unspecified",
|
||||
inputFieldName: instance_v2beta.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
testName: "default",
|
||||
inputFieldName: instance_v2beta.DomainFieldName(99),
|
||||
expecteFieldName: instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := listCustomDomainsBetaSortingColToV2Request(tc.inputFieldName)
|
||||
assert.Equal(t, tc.expecteFieldName, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTrustedDomainsBetaRequestToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance_v2beta.ListTrustedDomainsRequest
|
||||
expectedRequest *instance_v2.ListTrustedDomainsRequest
|
||||
}{
|
||||
{
|
||||
testName: "with all fields",
|
||||
inputRequest: &instance_v2beta.ListTrustedDomainsRequest{
|
||||
InstanceId: "instance1",
|
||||
Pagination: &filter_v2beta.PaginationRequest{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Queries: []*instance_v2beta.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: &instance_v2beta.TrustedDomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance_v2beta.DomainQuery{
|
||||
Domain: "test.com",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequest: &instance_v2.ListTrustedDomainsRequest{
|
||||
InstanceId: "instance1",
|
||||
Pagination: &filter_v2.PaginationRequest{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Filters: []*instance_v2.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance_v2.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance_v2.DomainFilter{
|
||||
Domain: "test.com",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "empty request",
|
||||
inputRequest: &instance_v2beta.ListTrustedDomainsRequest{},
|
||||
expectedRequest: &instance_v2.ListTrustedDomainsRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
Filters: []*instance_v2.TrustedDomainFilter{},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with sorting only",
|
||||
inputRequest: &instance_v2beta.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
expectedRequest: &instance_v2.ListTrustedDomainsRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
SortingColumn: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance_v2.TrustedDomainFilter{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := ListTrustedDomainsBetaRequestToV2Request(tc.inputRequest)
|
||||
assert.Equal(t, tc.expectedRequest, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listTrustedDomainsBetaSortingColToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputFieldName instance_v2beta.TrustedDomainFieldName
|
||||
expecteFieldName instance_v2.TrustedDomainFieldName
|
||||
}{
|
||||
{
|
||||
testName: "creation date",
|
||||
inputFieldName: instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
expecteFieldName: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
{
|
||||
testName: "domain",
|
||||
inputFieldName: instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
expecteFieldName: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
{
|
||||
testName: "unspecified",
|
||||
inputFieldName: instance_v2beta.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
expecteFieldName: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
testName: "default",
|
||||
inputFieldName: instance_v2beta.TrustedDomainFieldName(99),
|
||||
expecteFieldName: instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := listTrustedDomainsBetaSortingColToV2Request(tc.inputFieldName)
|
||||
assert.Equal(t, tc.expecteFieldName, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedDomainInstanceDomainListModelToGRPCBetaResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testTime := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
input []*domain.InstanceDomain
|
||||
expected []*instance_v2beta.TrustedDomain
|
||||
}{
|
||||
{
|
||||
name: "single domain",
|
||||
input: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test.com",
|
||||
},
|
||||
},
|
||||
expected: []*instance_v2beta.TrustedDomain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple domains",
|
||||
input: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test1.com",
|
||||
},
|
||||
{
|
||||
InstanceID: "instance2",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test2.com",
|
||||
},
|
||||
},
|
||||
expected: []*instance_v2beta.TrustedDomain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test1.com",
|
||||
},
|
||||
{
|
||||
InstanceId: "instance2",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list",
|
||||
input: []*domain.InstanceDomain{},
|
||||
expected: []*instance_v2beta.TrustedDomain{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := TrustedDomainInstanceDomainListModelToGRPCBetaResponse(tc.input)
|
||||
assert.Equal(t, tc.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedDomainInstanceDomainListModelToGRPCResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testTime := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
input []*domain.InstanceDomain
|
||||
expected []*instance_v2.TrustedDomain
|
||||
}{
|
||||
{
|
||||
name: "single domain",
|
||||
input: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test.com",
|
||||
},
|
||||
},
|
||||
expected: []*instance_v2.TrustedDomain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple domains",
|
||||
input: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test1.com",
|
||||
},
|
||||
{
|
||||
InstanceID: "instance2",
|
||||
CreatedAt: testTime,
|
||||
Domain: "test2.com",
|
||||
},
|
||||
},
|
||||
expected: []*instance_v2.TrustedDomain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test1.com",
|
||||
},
|
||||
{
|
||||
InstanceId: "instance2",
|
||||
CreationDate: timestamppb.New(testTime),
|
||||
Domain: "test2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list",
|
||||
input: []*domain.InstanceDomain{},
|
||||
expected: []*instance_v2.TrustedDomain{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := TrustedDomainInstanceDomainListModelToGRPCResponse(tc.input)
|
||||
assert.Equal(t, tc.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,42 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
/*
|
||||
* Domain Model to GRPC v2Beta
|
||||
*/
|
||||
|
||||
func DomainInstanceListModelToGRPCBetaResponse(instances []*domain.Instance) []*instance_v2beta.Instance {
|
||||
toReturn := make([]*instance_v2beta.Instance, len(instances))
|
||||
|
||||
for i, inst := range instances {
|
||||
toReturn[i] = DomainInstanceModelToGRPCBetaResponse(inst)
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func DomainInstanceModelToGRPCBetaResponse(inst *domain.Instance) *instance_v2beta.Instance {
|
||||
return &instance_v2beta.Instance{
|
||||
Id: inst.ID,
|
||||
ChangeDate: timestamppb.New(inst.UpdatedAt),
|
||||
CreationDate: timestamppb.New(inst.CreatedAt),
|
||||
Name: inst.Name,
|
||||
Version: build.Version(),
|
||||
Domains: DomainInstanceDomainListModelToGRPCBetaResponse(inst.Domains),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Domain Model to GRPC v2
|
||||
*/
|
||||
|
||||
func DomainInstanceListModelToGRPCResponse(instances []*domain.Instance) []*instance.Instance {
|
||||
toReturn := make([]*instance.Instance, len(instances))
|
||||
func DomainInstanceListModelToGRPCResponse(instances []*domain.Instance) []*instance_v2.Instance {
|
||||
toReturn := make([]*instance_v2.Instance, len(instances))
|
||||
|
||||
for i, inst := range instances {
|
||||
toReturn[i] = DomainInstanceModelToGRPCResponse(inst)
|
||||
@@ -22,31 +49,72 @@ func DomainInstanceListModelToGRPCResponse(instances []*domain.Instance) []*inst
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func DomainInstanceModelToGRPCResponse(inst *domain.Instance) *instance.Instance {
|
||||
return &instance.Instance{
|
||||
Id: inst.ID,
|
||||
ChangeDate: timestamppb.New(inst.UpdatedAt),
|
||||
CreationDate: timestamppb.New(inst.CreatedAt),
|
||||
State: instance.State_STATE_RUNNING, // TODO(IAM-Marco): Not sure what to put here
|
||||
Name: inst.Name,
|
||||
Version: build.Version(),
|
||||
Domains: domainInstanceDomainListModelToGRPCResponse(inst.Domains),
|
||||
func DomainInstanceModelToGRPCResponse(inst *domain.Instance) *instance_v2.Instance {
|
||||
return &instance_v2.Instance{
|
||||
Id: inst.ID,
|
||||
ChangeDate: timestamppb.New(inst.UpdatedAt),
|
||||
CreationDate: timestamppb.New(inst.CreatedAt),
|
||||
State: instance_v2.State_STATE_RUNNING, // TODO(IAM-Marco): Not sure what to put here
|
||||
Name: inst.Name,
|
||||
Version: build.Version(),
|
||||
CustomDomains: DomainInstanceDomainListModelToGRPCResponse(inst.Domains),
|
||||
}
|
||||
}
|
||||
|
||||
func domainInstanceDomainListModelToGRPCResponse(dms []*domain.InstanceDomain) []*instance.Domain {
|
||||
toReturn := make([]*instance.Domain, len(dms))
|
||||
for i, domain := range dms {
|
||||
isGenerated := domain.IsGenerated != nil && *domain.IsGenerated
|
||||
isPrimary := domain.IsPrimary != nil && *domain.IsPrimary
|
||||
toReturn[i] = &instance.Domain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
Primary: isPrimary,
|
||||
Generated: isGenerated,
|
||||
/*
|
||||
* GRPC v2Beta Requests to GRPC v2
|
||||
*/
|
||||
|
||||
func ListInstancesBetaRequestToV2Request(in *instance_v2beta.ListInstancesRequest) *instance_v2.ListInstancesRequest {
|
||||
return &instance_v2.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Offset: in.GetPagination().GetOffset(),
|
||||
Limit: in.GetPagination().GetLimit(),
|
||||
Asc: in.GetPagination().GetAsc(),
|
||||
},
|
||||
SortingColumn: listInstancesBetaSortingColToV2Request(in.SortingColumn),
|
||||
Filters: listInstancesQueriesToV2Request(in.GetQueries()),
|
||||
}
|
||||
}
|
||||
|
||||
func listInstancesQueriesToV2Request(queries []*instance_v2beta.Query) []*instance_v2.Filter {
|
||||
toReturn := make([]*instance_v2.Filter, len(queries))
|
||||
for i, query := range queries {
|
||||
switch assertedQuery := query.GetQuery().(type) {
|
||||
case *instance_v2beta.Query_DomainQuery:
|
||||
filter := &instance_v2.Filter_CustomDomainsFilter{
|
||||
CustomDomainsFilter: &instance_v2.CustomDomainsFilter{
|
||||
Domains: assertedQuery.DomainQuery.GetDomains(),
|
||||
},
|
||||
}
|
||||
toReturn[i] = &instance_v2.Filter{Filter: filter}
|
||||
case *instance_v2beta.Query_IdQuery:
|
||||
filter := &instance_v2.Filter_InIdsFilter{
|
||||
InIdsFilter: &filter.InIDsFilter{
|
||||
Ids: assertedQuery.IdQuery.GetIds(),
|
||||
},
|
||||
}
|
||||
toReturn[i] = &instance_v2.Filter{Filter: filter}
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func listInstancesBetaSortingColToV2Request(fieldName *instance_v2beta.FieldName) instance_v2.FieldName {
|
||||
if fieldName == nil {
|
||||
return instance_v2.FieldName_FIELD_NAME_UNSPECIFIED
|
||||
}
|
||||
|
||||
switch *fieldName {
|
||||
case instance_v2beta.FieldName_FIELD_NAME_CREATION_DATE:
|
||||
return instance_v2.FieldName_FIELD_NAME_CREATION_DATE
|
||||
case instance_v2beta.FieldName_FIELD_NAME_ID:
|
||||
return instance_v2.FieldName_FIELD_NAME_ID
|
||||
case instance_v2beta.FieldName_FIELD_NAME_NAME:
|
||||
return instance_v2.FieldName_FIELD_NAME_NAME
|
||||
case instance_v2beta.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return instance_v2.FieldName_FIELD_NAME_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,99 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
filter_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
filter_v2beta "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func TestDomainInstanceModelToGRPCBetaResponse(t *testing.T) {
|
||||
// Given
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().UTC()
|
||||
yesterday := now.AddDate(0, 0, -1)
|
||||
|
||||
inputInstance := &domain.Instance{
|
||||
ID: "instance-1",
|
||||
Name: "Instance One",
|
||||
CreatedAt: yesterday,
|
||||
UpdatedAt: now,
|
||||
Domains: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d1.example.com",
|
||||
IsPrimary: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d2.example.com",
|
||||
IsGenerated: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d3.example.com",
|
||||
IsPrimary: gu.Ptr(true),
|
||||
IsGenerated: gu.Ptr(false),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d4.example.com",
|
||||
IsPrimary: gu.Ptr(false),
|
||||
IsGenerated: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedInstance := &instance_v2beta.Instance{
|
||||
Id: "instance-1",
|
||||
ChangeDate: timestamppb.New(now),
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Name: "Instance One",
|
||||
Version: "",
|
||||
Domains: []*instance_v2beta.Domain{
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d1.example.com",
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d2.example.com",
|
||||
Primary: false,
|
||||
Generated: true,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d3.example.com",
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d4.example.com",
|
||||
Primary: false,
|
||||
Generated: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test
|
||||
res := DomainInstanceModelToGRPCBetaResponse(inputInstance)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, expectedInstance, res)
|
||||
}
|
||||
|
||||
func TestDomainInstanceModelToGRPCResponse(t *testing.T) {
|
||||
// Given
|
||||
t.Parallel()
|
||||
@@ -54,14 +144,14 @@ func TestDomainInstanceModelToGRPCResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedInstance := &instance.Instance{
|
||||
expectedInstance := &instance_v2.Instance{
|
||||
Id: "instance-1",
|
||||
ChangeDate: timestamppb.New(now),
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
State: instance_v2.State_STATE_RUNNING,
|
||||
Name: "Instance One",
|
||||
Version: "",
|
||||
Domains: []*instance.Domain{
|
||||
CustomDomains: []*instance_v2.CustomDomain{
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
@@ -100,19 +190,19 @@ func TestDomainInstanceModelToGRPCResponse(t *testing.T) {
|
||||
assert.Equal(t, expectedInstance, res)
|
||||
}
|
||||
|
||||
func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
func TestDomainInstanceListModelToGRPCBetaResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
now := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputResult []*domain.Instance
|
||||
expectedResult []*instance.Instance
|
||||
expectedResult []*instance_v2beta.Instance
|
||||
}{
|
||||
{
|
||||
testName: "empty result",
|
||||
inputResult: []*domain.Instance{},
|
||||
expectedResult: []*instance.Instance{},
|
||||
expectedResult: []*instance_v2beta.Instance{},
|
||||
},
|
||||
{
|
||||
testName: "single instance without domains",
|
||||
@@ -125,14 +215,13 @@ func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance.Instance{
|
||||
expectedResult: []*instance_v2beta.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{},
|
||||
Domains: []*instance_v2beta.Domain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -162,14 +251,13 @@ func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance.Instance{
|
||||
expectedResult: []*instance_v2beta.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance-1",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{
|
||||
Domains: []*instance_v2beta.Domain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
Domain: "domain1.com",
|
||||
@@ -184,8 +272,110 @@ func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
Name: "test-instance-2",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{},
|
||||
Domains: []*instance_v2beta.Domain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test
|
||||
result := DomainInstanceListModelToGRPCBetaResponse(tc.inputResult)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
now := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputResult []*domain.Instance
|
||||
expectedResult []*instance_v2.Instance
|
||||
}{
|
||||
{
|
||||
testName: "empty result",
|
||||
inputResult: []*domain.Instance{},
|
||||
expectedResult: []*instance_v2.Instance{},
|
||||
},
|
||||
{
|
||||
testName: "single instance without domains",
|
||||
inputResult: []*domain.Instance{
|
||||
{
|
||||
ID: "instance1",
|
||||
Name: "test-instance",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance_v2.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance_v2.State_STATE_RUNNING,
|
||||
CustomDomains: []*instance_v2.CustomDomain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "multiple instances with domains",
|
||||
inputResult: []*domain.Instance{
|
||||
{
|
||||
ID: "instance1",
|
||||
Name: "test-instance-1",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
Domain: "domain1.com",
|
||||
CreatedAt: now,
|
||||
IsPrimary: gu.Ptr(true),
|
||||
IsGenerated: gu.Ptr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "instance2",
|
||||
Name: "test-instance-2",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance_v2.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance-1",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance_v2.State_STATE_RUNNING,
|
||||
CustomDomains: []*instance_v2.CustomDomain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
Domain: "domain1.com",
|
||||
CreationDate: timestamppb.New(now),
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "instance2",
|
||||
Name: "test-instance-2",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance_v2.State_STATE_RUNNING,
|
||||
CustomDomains: []*instance_v2.CustomDomain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -202,3 +392,140 @@ func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstancesBetaRequestToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance_v2beta.ListInstancesRequest
|
||||
want *instance_v2.ListInstancesRequest
|
||||
}{
|
||||
{
|
||||
name: "empty request",
|
||||
request: &instance_v2beta.ListInstancesRequest{
|
||||
Pagination: &filter_v2beta.PaginationRequest{},
|
||||
},
|
||||
want: &instance_v2.ListInstancesRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
SortingColumn: instance_v2.FieldName_FIELD_NAME_UNSPECIFIED,
|
||||
Filters: []*instance_v2.Filter{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "request with all fields",
|
||||
request: &instance_v2beta.ListInstancesRequest{
|
||||
Pagination: &filter_v2beta.PaginationRequest{
|
||||
Offset: 10,
|
||||
Limit: 20,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_NAME),
|
||||
Queries: []*instance_v2beta.Query{
|
||||
{
|
||||
Query: &instance_v2beta.Query_DomainQuery{
|
||||
DomainQuery: &instance_v2beta.DomainsQuery{
|
||||
Domains: []string{"domain1.com", "domain2.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Query: &instance_v2beta.Query_IdQuery{
|
||||
IdQuery: &instance_v2beta.IdsQuery{
|
||||
Ids: []string{"id1", "id2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &instance_v2.ListInstancesRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{
|
||||
Offset: 10,
|
||||
Limit: 20,
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: instance_v2.FieldName_FIELD_NAME_NAME,
|
||||
Filters: []*instance_v2.Filter{
|
||||
{
|
||||
Filter: &instance_v2.Filter_CustomDomainsFilter{
|
||||
CustomDomainsFilter: &instance_v2.CustomDomainsFilter{
|
||||
Domains: []string{"domain1.com", "domain2.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &instance_v2.Filter_InIdsFilter{
|
||||
InIdsFilter: &filter_v2.InIDsFilter{
|
||||
Ids: []string{"id1", "id2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "request with different sorting field",
|
||||
request: &instance_v2beta.ListInstancesRequest{
|
||||
Pagination: &filter_v2beta.PaginationRequest{},
|
||||
SortingColumn: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_CREATION_DATE),
|
||||
},
|
||||
want: &instance_v2.ListInstancesRequest{
|
||||
Pagination: &filter_v2.PaginationRequest{},
|
||||
SortingColumn: instance_v2.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance_v2.Filter{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := ListInstancesBetaRequestToV2Request(tc.request)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstancesBetaSortingColToV2Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fieldName *instance_v2beta.FieldName
|
||||
want instance_v2.FieldName
|
||||
}{
|
||||
{
|
||||
name: "nil field name",
|
||||
fieldName: nil,
|
||||
want: instance_v2.FieldName_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
name: "creation date field",
|
||||
fieldName: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_CREATION_DATE),
|
||||
want: instance_v2.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
{
|
||||
name: "id field",
|
||||
fieldName: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_ID),
|
||||
want: instance_v2.FieldName_FIELD_NAME_ID,
|
||||
},
|
||||
{
|
||||
name: "name field",
|
||||
fieldName: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_NAME),
|
||||
want: instance_v2.FieldName_FIELD_NAME_NAME,
|
||||
},
|
||||
{
|
||||
name: "unspecified field",
|
||||
fieldName: gu.Ptr(instance_v2beta.FieldName_FIELD_NAME_UNSPECIFIED),
|
||||
want: instance_v2.FieldName_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := listInstancesBetaSortingColToV2Request(tt.fieldName)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package instancev2
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api/instance/v2/convert"
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
filter_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
filter_v2beta "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
// =================
|
||||
// v2Beta endpoints
|
||||
// =================
|
||||
|
||||
func AddCustomDomainBeta(ctx context.Context, request *connect.Request[instance_v2beta.AddCustomDomainRequest]) (*connect.Response[instance_v2beta.AddCustomDomainResponse], error) {
|
||||
addCustomDomainCmd := domain.NewAddInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetDomain(), domain.DomainTypeCustom)
|
||||
oidcConfigUpdateCmd := domain.NewOIDCConfigurationUpdate(request.Msg.GetDomain(), authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID())
|
||||
|
||||
batchExec := domain.BatchExecutors(
|
||||
addCustomDomainCmd,
|
||||
oidcConfigUpdateCmd,
|
||||
)
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
batchExec,
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
// domain.WithOIDCConfigurationRepo(repository.OIDCConfigurationRepository()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2beta.AddCustomDomainResponse]{
|
||||
Msg: &instance_v2beta.AddCustomDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
CreationDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RemoveCustomDomainBeta(ctx context.Context, request *connect.Request[instance_v2beta.RemoveCustomDomainRequest]) (*connect.Response[instance_v2beta.RemoveCustomDomainResponse], error) {
|
||||
removeCustomDomainCmd := domain.NewRemoveInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetDomain(), domain.DomainTypeCustom)
|
||||
|
||||
err := domain.Invoke(ctx, removeCustomDomainCmd, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if removeCustomDomainCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*removeCustomDomainCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2beta.RemoveCustomDomainResponse]{
|
||||
Msg: &instance_v2beta.RemoveCustomDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListCustomDomainsBeta(ctx context.Context, request *connect.Request[instance_v2beta.ListCustomDomainsRequest]) (*connect.Response[instance_v2beta.ListCustomDomainsResponse], error) {
|
||||
listCustomDomainsQuery := domain.NewListInstanceDomainsQuery(convert.ListCustomDomainsBetaRequestToV2Request(request.Msg))
|
||||
|
||||
err := domain.Invoke(ctx, listCustomDomainsQuery, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customDomains := listCustomDomainsQuery.Result()
|
||||
return &connect.Response[instance_v2beta.ListCustomDomainsResponse]{
|
||||
Msg: &instance_v2beta.ListCustomDomainsResponse{
|
||||
Domains: convert.DomainInstanceDomainListModelToGRPCBetaResponse(customDomains),
|
||||
Pagination: &filter_v2beta.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(customDomains)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func AddTrustedDomainBeta(ctx context.Context, request *connect.Request[instance_v2beta.AddTrustedDomainRequest]) (*connect.Response[instance_v2beta.AddTrustedDomainResponse], error) {
|
||||
addTrustedDomainCmd := domain.NewAddInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetDomain(), domain.DomainTypeTrusted)
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
addTrustedDomainCmd,
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2beta.AddTrustedDomainResponse]{
|
||||
Msg: &instance_v2beta.AddTrustedDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
CreationDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RemoveTrustedDomainBeta(ctx context.Context, request *connect.Request[instance_v2beta.RemoveTrustedDomainRequest]) (*connect.Response[instance_v2beta.RemoveTrustedDomainResponse], error) {
|
||||
removeTrustedDomainCmd := domain.NewRemoveInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetDomain(), domain.DomainTypeTrusted)
|
||||
|
||||
err := domain.Invoke(ctx, removeTrustedDomainCmd, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if removeTrustedDomainCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*removeTrustedDomainCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2beta.RemoveTrustedDomainResponse]{
|
||||
Msg: &instance_v2beta.RemoveTrustedDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListTrustedDomainsBeta(ctx context.Context, request *connect.Request[instance_v2beta.ListTrustedDomainsRequest]) (*connect.Response[instance_v2beta.ListTrustedDomainsResponse], error) {
|
||||
listTrustedDomainsQuery := domain.NewListInstanceTrustedDomainsQuery(convert.ListTrustedDomainsBetaRequestToV2Request(request.Msg))
|
||||
|
||||
err := domain.Invoke(ctx, listTrustedDomainsQuery, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trustedDomains := listTrustedDomainsQuery.Result()
|
||||
return &connect.Response[instance_v2beta.ListTrustedDomainsResponse]{
|
||||
Msg: &instance_v2beta.ListTrustedDomainsResponse{
|
||||
TrustedDomain: convert.TrustedDomainInstanceDomainListModelToGRPCBetaResponse(trustedDomains),
|
||||
Pagination: &filter_v2beta.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(trustedDomains)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =================
|
||||
// v2 endpoints
|
||||
// =================
|
||||
|
||||
func AddCustomDomain(ctx context.Context, request *connect.Request[instance_v2.AddCustomDomainRequest]) (*connect.Response[instance_v2.AddCustomDomainResponse], error) {
|
||||
addCustomDomainCmd := domain.NewAddInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetCustomDomain(), domain.DomainTypeCustom)
|
||||
oidcConfigUpdateCmd := domain.NewOIDCConfigurationUpdate(request.Msg.GetCustomDomain(), authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID())
|
||||
|
||||
batchExec := domain.BatchExecutors(
|
||||
addCustomDomainCmd,
|
||||
oidcConfigUpdateCmd,
|
||||
)
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
batchExec,
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
// domain.WithOIDCConfigurationRepo(repository.OIDCConfigurationRepository()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2.AddCustomDomainResponse]{
|
||||
Msg: &instance_v2.AddCustomDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
CreationDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RemoveCustomDomain(ctx context.Context, request *connect.Request[instance_v2.RemoveCustomDomainRequest]) (*connect.Response[instance_v2.RemoveCustomDomainResponse], error) {
|
||||
removeCustomDomainCmd := domain.NewRemoveInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetCustomDomain(), domain.DomainTypeCustom)
|
||||
|
||||
err := domain.Invoke(ctx, removeCustomDomainCmd, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if removeCustomDomainCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*removeCustomDomainCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2.RemoveCustomDomainResponse]{
|
||||
Msg: &instance_v2.RemoveCustomDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListCustomDomains(ctx context.Context, request *connect.Request[instance_v2.ListCustomDomainsRequest]) (*connect.Response[instance_v2.ListCustomDomainsResponse], error) {
|
||||
listCustomDomainsQuery := domain.NewListInstanceDomainsQuery(request.Msg)
|
||||
|
||||
err := domain.Invoke(ctx, listCustomDomainsQuery, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customDomains := listCustomDomainsQuery.Result()
|
||||
return &connect.Response[instance_v2.ListCustomDomainsResponse]{
|
||||
Msg: &instance_v2.ListCustomDomainsResponse{
|
||||
Domains: convert.DomainInstanceDomainListModelToGRPCResponse(customDomains),
|
||||
Pagination: &filter_v2.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(customDomains)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func AddTrustedDomain(ctx context.Context, request *connect.Request[instance_v2.AddTrustedDomainRequest]) (*connect.Response[instance_v2.AddTrustedDomainResponse], error) {
|
||||
addTrustedDomainCmd := domain.NewAddInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetTrustedDomain(), domain.DomainTypeTrusted)
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
addTrustedDomainCmd,
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2.AddTrustedDomainResponse]{
|
||||
Msg: &instance_v2.AddTrustedDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
CreationDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RemoveTrustedDomain(ctx context.Context, request *connect.Request[instance_v2.RemoveTrustedDomainRequest]) (*connect.Response[instance_v2.RemoveTrustedDomainResponse], error) {
|
||||
removeTrustedDomainCmd := domain.NewRemoveInstanceDomainCommand(request.Msg.GetInstanceId(), request.Msg.GetTrustedDomain(), domain.DomainTypeTrusted)
|
||||
|
||||
err := domain.Invoke(ctx, removeTrustedDomainCmd, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if removeTrustedDomainCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*removeTrustedDomainCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2.RemoveTrustedDomainResponse]{
|
||||
Msg: &instance_v2.RemoveTrustedDomainResponse{
|
||||
// TODO(IAM-Marco): Return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10881
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListTrustedDomains(ctx context.Context, request *connect.Request[instance_v2.ListTrustedDomainsRequest]) (*connect.Response[instance_v2.ListTrustedDomainsResponse], error) {
|
||||
listTrustedDomainsQuery := domain.NewListInstanceTrustedDomainsQuery(request.Msg)
|
||||
|
||||
err := domain.Invoke(ctx, listTrustedDomainsQuery, domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trustedDomains := listTrustedDomainsQuery.Result()
|
||||
return &connect.Response[instance_v2.ListTrustedDomainsResponse]{
|
||||
Msg: &instance_v2.ListTrustedDomainsResponse{
|
||||
TrustedDomain: convert.TrustedDomainInstanceDomainListModelToGRPCResponse(trustedDomains),
|
||||
Pagination: &filter_v2.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(trustedDomains)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -2,61 +2,62 @@ package instancev2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api/instance/v2/convert"
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
filter_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
filter_v2beta "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
|
||||
// =================
|
||||
// v2Beta endpoints
|
||||
// =================
|
||||
|
||||
func DeleteInstanceBeta(ctx context.Context, request *connect.Request[instance_v2beta.DeleteInstanceRequest]) (*connect.Response[instance_v2beta.DeleteInstanceResponse], error) {
|
||||
instanceDeleteCmd := domain.NewDeleteInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceDeleteCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.DeleteInstanceResponse]{
|
||||
Msg: &instance.DeleteInstanceResponse{
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if instanceDeleteCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*instanceDeleteCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2beta.DeleteInstanceResponse]{
|
||||
Msg: &instance_v2beta.DeleteInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update()
|
||||
// returns the timestamp
|
||||
DeletionDate: timestamppb.Now(),
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetInstance(ctx context.Context, request *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
|
||||
func GetInstanceBeta(ctx context.Context, request *connect.Request[instance_v2beta.GetInstanceRequest]) (*connect.Response[instance_v2beta.GetInstanceResponse], error) {
|
||||
instanceGetCmd := domain.NewGetInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceGetCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.GetInstanceResponse]{
|
||||
Msg: &instance.GetInstanceResponse{
|
||||
Instance: convert.DomainInstanceModelToGRPCResponse(instanceGetCmd.Result()),
|
||||
return &connect.Response[instance_v2beta.GetInstanceResponse]{
|
||||
Msg: &instance_v2beta.GetInstanceResponse{
|
||||
Instance: convert.DomainInstanceModelToGRPCBetaResponse(instanceGetCmd.Result()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
|
||||
func UpdateInstanceBeta(ctx context.Context, request *connect.Request[instance_v2beta.UpdateInstanceRequest]) (*connect.Response[instance_v2beta.UpdateInstanceResponse], error) {
|
||||
instanceUpdateCmd := domain.NewUpdateInstanceCommand(request.Msg.GetInstanceId(), request.Msg.GetInstanceName())
|
||||
|
||||
err := domain.Invoke(ctx, instanceUpdateCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
@@ -65,8 +66,8 @@ func UpdateInstance(ctx context.Context, request *connect.Request[instance.Updat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.UpdateInstanceResponse]{
|
||||
Msg: &instance.UpdateInstanceResponse{
|
||||
return &connect.Response[instance_v2beta.UpdateInstanceResponse]{
|
||||
Msg: &instance_v2beta.UpdateInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when InstanceRepo.Update()
|
||||
// returns the timestamp
|
||||
ChangeDate: timestamppb.Now(),
|
||||
@@ -74,7 +75,93 @@ func UpdateInstance(ctx context.Context, request *connect.Request[instance.Updat
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListInstances(ctx context.Context, request *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
|
||||
func ListInstancesBeta(ctx context.Context, request *connect.Request[instance_v2beta.ListInstancesRequest]) (*connect.Response[instance_v2beta.ListInstancesResponse], error) {
|
||||
instancesListCmd := domain.NewListInstancesCommand(convert.ListInstancesBetaRequestToV2Request(request.Msg))
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
instancesListCmd,
|
||||
domain.WithInstanceRepo(repository.InstanceRepository()),
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances := instancesListCmd.Result()
|
||||
return &connect.Response[instance_v2beta.ListInstancesResponse]{
|
||||
Msg: &instance_v2beta.ListInstancesResponse{
|
||||
Instances: convert.DomainInstanceListModelToGRPCBetaResponse(instances),
|
||||
Pagination: &filter_v2beta.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(instances)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =================
|
||||
// v2 endpoints
|
||||
// =================
|
||||
|
||||
func DeleteInstance(ctx context.Context, request *connect.Request[instance_v2.DeleteInstanceRequest]) (*connect.Response[instance_v2.DeleteInstanceResponse], error) {
|
||||
instanceDeleteCmd := domain.NewDeleteInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceDeleteCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if instanceDeleteCmd.DeleteTime != nil {
|
||||
deletionDate = timestamppb.New(*instanceDeleteCmd.DeleteTime)
|
||||
}
|
||||
return &connect.Response[instance_v2.DeleteInstanceResponse]{
|
||||
Msg: &instance_v2.DeleteInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update()
|
||||
// returns the timestamp
|
||||
DeletionDate: deletionDate,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetInstance(ctx context.Context, request *connect.Request[instance_v2.GetInstanceRequest]) (*connect.Response[instance_v2.GetInstanceResponse], error) {
|
||||
instanceGetCmd := domain.NewGetInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceGetCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2.GetInstanceResponse]{
|
||||
Msg: &instance_v2.GetInstanceResponse{
|
||||
Instance: convert.DomainInstanceModelToGRPCResponse(instanceGetCmd.Result()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateInstance(ctx context.Context, request *connect.Request[instance_v2.UpdateInstanceRequest]) (*connect.Response[instance_v2.UpdateInstanceResponse], error) {
|
||||
instanceUpdateCmd := domain.NewUpdateInstanceCommand(request.Msg.GetInstanceId(), request.Msg.GetInstanceName())
|
||||
|
||||
err := domain.Invoke(ctx, instanceUpdateCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance_v2.UpdateInstanceResponse]{
|
||||
Msg: &instance_v2.UpdateInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when InstanceRepo.Update()
|
||||
// returns the timestamp
|
||||
ChangeDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListInstances(ctx context.Context, request *connect.Request[instance_v2.ListInstancesRequest]) (*connect.Response[instance_v2.ListInstancesResponse], error) {
|
||||
instancesListCmd := domain.NewListInstancesCommand(request.Msg)
|
||||
|
||||
err := domain.Invoke(
|
||||
@@ -88,10 +175,10 @@ func ListInstances(ctx context.Context, request *connect.Request[instance.ListIn
|
||||
}
|
||||
|
||||
instances := instancesListCmd.Result()
|
||||
return &connect.Response[instance.ListInstancesResponse]{
|
||||
Msg: &instance.ListInstancesResponse{
|
||||
return &connect.Response[instance_v2.ListInstancesResponse]{
|
||||
Msg: &instance_v2.ListInstancesResponse{
|
||||
Instances: convert.DomainInstanceListModelToGRPCResponse(instances),
|
||||
Pagination: &filter.PaginationResponse{
|
||||
Pagination: &filter_v2.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(instances)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
|
||||
@@ -17,39 +17,11 @@ import (
|
||||
v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
)
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// =================
|
||||
// v2Beta endpoints
|
||||
// =================
|
||||
|
||||
// "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
// "github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
// )
|
||||
|
||||
// func CreateOrg(ctx context.Context, req *org.AddOrganizationRequest) (resp *org.AddOrganizationResponse, err error) {
|
||||
// cmd := domain.NewAddOrgCommand(
|
||||
// req.GetName(),
|
||||
// addOrgAdminToCommand(req.GetAdmins()...)...,
|
||||
// )
|
||||
// err = domain.Invoke(ctx, cmd)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &org.AddOrganizationResponse{
|
||||
// OrganizationId: cmd.ID,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
// func addOrgAdminToCommand(admins ...*org.AddOrganizationRequest_Admin) []*domain.AddMemberCommand {
|
||||
// cmds := make([]*domain.AddMemberCommand, len(admins))
|
||||
// for i, admin := range admins {
|
||||
// cmds[i] = &domain.AddMemberCommand{
|
||||
// UserID: admin.GetUserId(),
|
||||
// Roles: admin.GetRoles(),
|
||||
// }
|
||||
// }
|
||||
// return cmds
|
||||
// }
|
||||
|
||||
func UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org.UpdateOrganizationRequest]) (*connect.Response[v2beta_org.UpdateOrganizationResponse], error) {
|
||||
func UpdateOrganizationBeta(ctx context.Context, request *connect.Request[v2beta_org.UpdateOrganizationRequest]) (*connect.Response[v2beta_org.UpdateOrganizationResponse], error) {
|
||||
orgUpdateCmd := domain.NewUpdateOrgCommand(request.Msg.GetId(), request.Msg.GetName())
|
||||
|
||||
// TODO(IAM-Marco) Finish implementation in https://github.com/zitadel/zitadel/issues/10447
|
||||
@@ -73,30 +45,6 @@ func UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListOrganizations(ctx context.Context, request *connect.Request[v2_org.ListOrganizationsRequest]) (*connect.Response[v2_org.ListOrganizationsResponse], error) {
|
||||
orgListQuery := domain.NewListOrgsQuery(request.Msg)
|
||||
|
||||
err := domain.Invoke(ctx, orgListQuery,
|
||||
domain.WithOrganizationRepo(repository.OrganizationRepository()),
|
||||
domain.WithOrganizationDomainRepo(repository.OrganizationDomainRepository()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgs := convert.DomainOrganizationListModelToGRPCResponse(orgListQuery.Result())
|
||||
return &connect.Response[v2_org.ListOrganizationsResponse]{
|
||||
Msg: &v2_org.ListOrganizationsResponse{
|
||||
Result: orgs,
|
||||
Details: &object.ListDetails{
|
||||
// TODO(IAM-Marco): Return correct result once permissions are in place
|
||||
TotalResult: uint64(len(orgs)),
|
||||
},
|
||||
SortingColumn: request.Msg.GetSortingColumn(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877)
|
||||
func ListOrganizationsBeta(ctx context.Context, request *connect.Request[v2beta_org.ListOrganizationsRequest]) (*connect.Response[v2beta_org.ListOrganizationsResponse], error) {
|
||||
orgListQuery := domain.NewListOrgsQuery(convert.OrganizationBetaRequestToV2Request(request.Msg))
|
||||
@@ -121,7 +69,7 @@ func ListOrganizationsBeta(ctx context.Context, request *connect.Request[v2beta_
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DeleteOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeleteOrganizationRequest]) (*connect.Response[v2beta_org.DeleteOrganizationResponse], error) {
|
||||
func DeleteOrganizationBeta(ctx context.Context, request *connect.Request[v2beta_org.DeleteOrganizationRequest]) (*connect.Response[v2beta_org.DeleteOrganizationResponse], error) {
|
||||
orgDeleteCmd := domain.NewDeleteOrgCommand(request.Msg.GetId())
|
||||
|
||||
err := domain.Invoke(ctx, orgDeleteCmd,
|
||||
@@ -145,7 +93,7 @@ func DeleteOrganization(ctx context.Context, request *connect.Request[v2beta_org
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DeactivateOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeactivateOrganizationRequest]) (*connect.Response[v2beta_org.DeactivateOrganizationResponse], error) {
|
||||
func DeactivateOrganizationBeta(ctx context.Context, request *connect.Request[v2beta_org.DeactivateOrganizationRequest]) (*connect.Response[v2beta_org.DeactivateOrganizationResponse], error) {
|
||||
orgDeactivateCmd := domain.NewDeactivateOrgCommand(request.Msg.GetId())
|
||||
|
||||
err := domain.Invoke(ctx, orgDeactivateCmd,
|
||||
@@ -164,7 +112,7 @@ func DeactivateOrganization(ctx context.Context, request *connect.Request[v2beta
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ActivateOrganization(ctx context.Context, request *connect.Request[v2beta_org.ActivateOrganizationRequest]) (*connect.Response[v2beta_org.ActivateOrganizationResponse], error) {
|
||||
func ActivateOrganizationBeta(ctx context.Context, request *connect.Request[v2beta_org.ActivateOrganizationRequest]) (*connect.Response[v2beta_org.ActivateOrganizationResponse], error) {
|
||||
orgActivateCmd := domain.NewActivateOrgCommand(request.Msg.GetId())
|
||||
|
||||
err := domain.Invoke(ctx, orgActivateCmd,
|
||||
@@ -182,3 +130,31 @@ func ActivateOrganization(ctx context.Context, request *connect.Request[v2beta_o
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =================
|
||||
// v2 endpoints
|
||||
// =================
|
||||
|
||||
func ListOrganizations(ctx context.Context, request *connect.Request[v2_org.ListOrganizationsRequest]) (*connect.Response[v2_org.ListOrganizationsResponse], error) {
|
||||
orgListQuery := domain.NewListOrgsQuery(request.Msg)
|
||||
|
||||
err := domain.Invoke(ctx, orgListQuery,
|
||||
domain.WithOrganizationRepo(repository.OrganizationRepository()),
|
||||
domain.WithOrganizationDomainRepo(repository.OrganizationDomainRepository()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgs := convert.DomainOrganizationListModelToGRPCResponse(orgListQuery.Result())
|
||||
return &connect.Response[v2_org.ListOrganizationsResponse]{
|
||||
Msg: &v2_org.ListOrganizationsResponse{
|
||||
Result: orgs,
|
||||
Details: &object.ListDetails{
|
||||
// TODO(IAM-Marco): Return correct result once permissions are in place
|
||||
TotalResult: uint64(len(orgs)),
|
||||
},
|
||||
SortingColumn: request.Msg.GetSortingColumn(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -20,6 +24,8 @@ type DeleteInstanceCommand struct {
|
||||
// InstanceName is public for testing purposes
|
||||
// do not use this field
|
||||
InstanceName string
|
||||
|
||||
DeleteTime *time.Time
|
||||
}
|
||||
|
||||
// RequiresTransaction implements [Transactional].
|
||||
@@ -46,6 +52,9 @@ func (d *DeleteInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (
|
||||
|
||||
instanceToDelete, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(d.ID)))
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -58,18 +67,22 @@ func (d *DeleteInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (
|
||||
|
||||
deletedRows, err := instanceRepo.Delete(ctx, opts.DB(), instanceToDelete.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowInternal(err, "DOM-caF4Vs", "Errors.Instance.Delete")
|
||||
}
|
||||
|
||||
if deletedRows > 1 {
|
||||
err = zerrors.ThrowInternalf(nil, "DOM-Od04Jx", "expecting 1 row deleted, got %d", deletedRows)
|
||||
return err
|
||||
return zerrors.ThrowInternal(nil, "DOM-Od04Jx", "Errors.Instsance.DeleteMismatch")
|
||||
}
|
||||
|
||||
if deletedRows < 1 {
|
||||
err = zerrors.ThrowNotFound(nil, "DOM-daglwD", "instance not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
d.DeleteTime = gu.Ptr(time.Now())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -85,7 +98,7 @@ func (d *DeleteInstanceCommand) Validate(ctx context.Context, opts *InvokeOpts)
|
||||
}
|
||||
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceWritePermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Yz8f1X", "permission denied")
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Yz8f1X", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestDeleteInstanceCommand_Validate(t *testing.T) {
|
||||
|
||||
return permChecker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Yz8f1X", "permission denied"),
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Yz8f1X", "Errors.PermissionDenied"),
|
||||
inputID: "instance-1",
|
||||
},
|
||||
{
|
||||
@@ -116,6 +116,7 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID())
|
||||
deleteErr := errors.New("delete error")
|
||||
getErr := errors.New("get error")
|
||||
noRowFoundErr := database.NewNoRowFoundError(nil)
|
||||
|
||||
tests := []struct {
|
||||
testName string
|
||||
@@ -126,12 +127,37 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
expectedError error
|
||||
expectedInstanceName string
|
||||
expectedInstanceDomains []string
|
||||
expectsDeleteTime bool
|
||||
}{
|
||||
{
|
||||
testName: "when retrieving instance fails should return error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
instanceRepo := domainmock.NewInstanceRepo(ctrl)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
LoadDomains().
|
||||
Times(1).
|
||||
Return(instanceRepo)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
Get(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
dbmock.QueryOptions(database.WithCondition(
|
||||
instanceRepo.IDCondition("instance-1"),
|
||||
)),
|
||||
).
|
||||
Times(1).
|
||||
Return(nil, noRowFoundErr)
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance fails with no row found should return no error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
instanceRepo := domainmock.NewInstanceRepo(ctrl)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
LoadDomains().
|
||||
Times(1).
|
||||
@@ -188,7 +214,46 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedError: deleteErr,
|
||||
expectedError: zerrors.ThrowInternal(deleteErr, "DOM-caF4Vs", "Errors.Instance.Delete"),
|
||||
expectedInstanceName: "My instance 1",
|
||||
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
|
||||
},
|
||||
{
|
||||
testName: "when delete instance fails with no row found should return no error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
instanceRepo := domainmock.NewInstanceRepo(ctrl)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
LoadDomains().
|
||||
Times(1).
|
||||
Return(instanceRepo)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
Get(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
dbmock.QueryOptions(database.WithCondition(
|
||||
instanceRepo.IDCondition("instance-1"),
|
||||
)),
|
||||
).
|
||||
Times(1).
|
||||
Return(&domain.Instance{
|
||||
ID: "instance-1",
|
||||
Name: "My instance 1",
|
||||
Domains: []*domain.InstanceDomain{
|
||||
{Domain: "d1.example.com"},
|
||||
{Domain: "d2.example.com"},
|
||||
{Domain: "d3.example.com"},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
Delete(gomock.Any(), gomock.Any(), "instance-1").
|
||||
Times(1).
|
||||
Return(int64(0), database.NewNoRowFoundError(nil))
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedInstanceName: "My instance 1",
|
||||
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
|
||||
},
|
||||
@@ -228,12 +293,12 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedError: zerrors.ThrowInternalf(nil, "DOM-Od04Jx", "expecting 1 row deleted, got %d", 2),
|
||||
expectedError: zerrors.ThrowInternal(nil, "DOM-Od04Jx", "Errors.Instsance.DeleteMismatch"),
|
||||
expectedInstanceName: "My instance 1",
|
||||
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
|
||||
},
|
||||
{
|
||||
testName: "when no rows deleted should return not found error",
|
||||
testName: "when no rows deleted should return no error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
instanceRepo := domainmock.NewInstanceRepo(ctrl)
|
||||
|
||||
@@ -268,7 +333,6 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedError: zerrors.ThrowNotFound(nil, "DOM-daglwD", "instance not found"),
|
||||
expectedInstanceName: "My instance 1",
|
||||
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
|
||||
},
|
||||
@@ -310,6 +374,7 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
inputInstanceID: "instance-1",
|
||||
expectedInstanceName: "My instance 1",
|
||||
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
|
||||
expectsDeleteTime: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -335,6 +400,7 @@ func TestDeleteInstanceCommand_Execute(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedInstanceName, d.InstanceName)
|
||||
assert.ElementsMatch(t, tc.expectedInstanceDomains, d.InstanceDomains)
|
||||
assert.Equal(t, tc.expectsDeleteTime, d.DeleteTime != nil && !d.DeleteTime.IsZero())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ type instanceDomainConditions interface {
|
||||
PrimaryKeyCondition(domain string) database.Condition
|
||||
// TypeCondition returns a filter for the type field.
|
||||
TypeCondition(typ DomainType) database.Condition
|
||||
// IsGeneratedCondition returns a filter for the is_generated field
|
||||
IsGeneratedCondition(isGenerated bool) database.Condition
|
||||
}
|
||||
|
||||
type instanceDomainChanges interface {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var allowedDomainCharacters = regexp.MustCompile(`^[a-zA-Z0-9\\.\\-]+$`)
|
||||
|
||||
const maxDomainNameLength = 253
|
||||
|
||||
type AddInstanceDomainCommand struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
DomainName string `json:"domain_name"`
|
||||
DType DomainType `json:"domain_type"`
|
||||
}
|
||||
|
||||
// RequiresTransaction implements [Transactional].
|
||||
func (a *AddInstanceDomainCommand) RequiresTransaction() {}
|
||||
|
||||
func NewAddInstanceDomainCommand(instanceID, domainName string, domainType DomainType) *AddInstanceDomainCommand {
|
||||
return &AddInstanceDomainCommand{
|
||||
InstanceID: instanceID,
|
||||
DomainName: domainName,
|
||||
DType: domainType,
|
||||
}
|
||||
}
|
||||
|
||||
// Events implements [Commander].
|
||||
func (a *AddInstanceDomainCommand) Events(ctx context.Context, _ *InvokeOpts) ([]eventstore.Command, error) {
|
||||
var event eventstore.Command
|
||||
switch a.DType {
|
||||
case DomainTypeCustom:
|
||||
event = instance.NewDomainAddedEvent(ctx, &instance.NewAggregate(a.InstanceID).Aggregate, a.DomainName, false)
|
||||
case DomainTypeTrusted:
|
||||
event = instance.NewTrustedDomainAddedEvent(ctx, &instance.NewAggregate(a.InstanceID).Aggregate, a.DomainName)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
event,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (a *AddInstanceDomainCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
instanceDomainRepo := opts.instanceDomainRepo
|
||||
|
||||
var isPrimary, isGenerated *bool
|
||||
if a.DType == DomainTypeCustom {
|
||||
isPrimary, isGenerated = gu.Ptr(false), gu.Ptr(false)
|
||||
}
|
||||
|
||||
err = instanceDomainRepo.Add(ctx, opts.DB(), &AddInstanceDomain{
|
||||
InstanceID: a.InstanceID,
|
||||
Domain: a.DomainName,
|
||||
IsPrimary: isPrimary,
|
||||
IsGenerated: isGenerated,
|
||||
Type: a.DType,
|
||||
})
|
||||
if err != nil {
|
||||
return zerrors.ThrowInternal(err, "DOM-uSCVn3", "Errors.Instance.Domain.Add")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (a *AddInstanceDomainCommand) String() string {
|
||||
return "AddInstanceDomainCommand"
|
||||
}
|
||||
|
||||
// Validate implements [Commander].
|
||||
func (a *AddInstanceDomainCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
if a.DomainName = strings.TrimSpace(a.DomainName); a.DomainName == "" || len(a.DomainName) > maxDomainNameLength {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-jieuM8", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if a.InstanceID = strings.TrimSpace(a.InstanceID); a.InstanceID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-YaUBp5", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if !allowedDomainCharacters.MatchString(a.DomainName) {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-98VcSQ", "Errors.Instance.Domain.InvalidCharacter")
|
||||
}
|
||||
|
||||
// TODO(IAM-Marco): Do we want to restrict to the instance in context?
|
||||
if a.InstanceID != authz.GetInstance(ctx).InstanceID() {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-x01cai", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, DomainWritePermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-c83vPX", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
domainRepo := opts.instanceDomainRepo
|
||||
_, err = domainRepo.Get(ctx, opts.DB(), database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, a.DomainName)))
|
||||
if err == nil {
|
||||
return zerrors.ThrowAlreadyExists(nil, "DOM-CvQ8tf", "Errors.Instance.Domain.AlreadyExists")
|
||||
}
|
||||
|
||||
if !errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return zerrors.ThrowInternal(err, "DOM-LrTy2z", "Errors.Instance.Domain.Get")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*AddInstanceDomainCommand)(nil)
|
||||
_ Transactional = (*AddInstanceDomainCommand)(nil)
|
||||
)
|
||||
@@ -0,0 +1,316 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestAddInstanceDomainCommand_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
getErr := errors.New("get error")
|
||||
permissionErr := errors.New("permission error")
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
instanceDomainRepo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
inputInstanceID string
|
||||
inputDomainName string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when no name should return invalid argument error",
|
||||
inputInstanceID: " ",
|
||||
inputDomainName: " ",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-jieuM8", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when name too long should return invalid argument error",
|
||||
inputInstanceID: " ",
|
||||
inputDomainName: "domain.is.to.loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-jieuM8", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when no ID should return invalid argument error",
|
||||
inputInstanceID: " ",
|
||||
inputDomainName: " domain.name",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-YaUBp5", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when domain name contains invalid characters should return invalid argument error",
|
||||
inputInstanceID: "instance-1 ",
|
||||
inputDomainName: "?",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-98VcSQ", "Errors.Instance.Domain.InvalidCharacter"),
|
||||
},
|
||||
{
|
||||
testName: "when input instance ID doesn't match instance in context should return invalid argument error",
|
||||
inputInstanceID: " instance-2",
|
||||
inputDomainName: "valid.domain",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-x01cai", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when user is missing permission should return permission denied",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
|
||||
return permChecker
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "valid.domain",
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-c83vPX", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance domain fails with generic error should return error",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
return permChecker
|
||||
},
|
||||
instanceDomainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
||||
database.WithCondition(
|
||||
repo.DomainCondition(database.TextOperationEqual, "valid.domain"),
|
||||
),
|
||||
)).
|
||||
Times(1).
|
||||
Return(nil, getErr)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "valid.domain",
|
||||
expectedError: zerrors.ThrowInternal(getErr, "DOM-LrTy2z", "Errors.Instance.Domain.Get"),
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance domain succeeds should return already exists error",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
return permChecker
|
||||
},
|
||||
instanceDomainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
||||
database.WithCondition(
|
||||
repo.DomainCondition(database.TextOperationEqual, "valid.domain"),
|
||||
),
|
||||
)).
|
||||
Times(1).
|
||||
Return(&domain.InstanceDomain{}, nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "valid.domain",
|
||||
expectedError: zerrors.ThrowAlreadyExists(nil, "DOM-CvQ8tf", "Errors.Instance.Domain.AlreadyExists"),
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance domain fails with not found error validation should succeed",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
return permChecker
|
||||
},
|
||||
instanceDomainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
||||
database.WithCondition(
|
||||
repo.DomainCondition(database.TextOperationEqual, "valid.domain"),
|
||||
),
|
||||
)).
|
||||
Times(1).
|
||||
Return(nil, &database.NoRowFoundError{})
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "valid.domain",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
ctx := authz.NewMockContext("instance-1", "org-1", "")
|
||||
ctrl := gomock.NewController(t)
|
||||
cmd := domain.NewAddInstanceDomainCommand(tc.inputInstanceID, tc.inputDomainName, domain.DomainTypeCustom)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
if tc.instanceDomainRepo != nil {
|
||||
domain.WithInstanceDomainRepo(tc.instanceDomainRepo(ctrl))(opts)
|
||||
}
|
||||
if tc.permissionChecker != nil {
|
||||
opts.Permissions = tc.permissionChecker(ctrl)
|
||||
}
|
||||
|
||||
err := cmd.Validate(ctx, opts)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddInstanceDomainCommand_Events(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
domainType domain.DomainType
|
||||
expectedAggregateID string
|
||||
expectedEventType eventstore.Command
|
||||
}{
|
||||
{
|
||||
testName: "trusted domain",
|
||||
domainType: domain.DomainTypeTrusted,
|
||||
expectedAggregateID: "instance-1",
|
||||
expectedEventType: &instance.TrustedDomainAddedEvent{},
|
||||
},
|
||||
{
|
||||
testName: "custom domain",
|
||||
domainType: domain.DomainTypeCustom,
|
||||
expectedAggregateID: "instance-1",
|
||||
expectedEventType: &instance.DomainAddedEvent{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
instanceID := "instance-1"
|
||||
domainName := "test.domain"
|
||||
ctx := authz.NewMockContext(instanceID, "org-1", "")
|
||||
cmd := domain.NewAddInstanceDomainCommand(instanceID, domainName, tc.domainType)
|
||||
|
||||
// When
|
||||
events, err := cmd.Events(ctx, &domain.InvokeOpts{})
|
||||
|
||||
// Then
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events, 1)
|
||||
require.NotNil(t, events[0].Aggregate())
|
||||
assert.Equal(t, instanceID, events[0].Aggregate().ID)
|
||||
|
||||
require.IsType(t, tc.expectedEventType, events[0])
|
||||
|
||||
switch asserted := events[0].(type) {
|
||||
case *instance.DomainAddedEvent:
|
||||
assert.Equal(t, domainName, asserted.Domain)
|
||||
assert.False(t, asserted.Generated)
|
||||
case *instance.TrustedDomainAddedEvent:
|
||||
assert.Equal(t, domainName, asserted.Domain)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddInstanceDomainCommand_Execute(t *testing.T) {
|
||||
t.Parallel()
|
||||
addErr := errors.New("add error")
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
instanceDomainRepo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
inputDomainType domain.DomainType
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when adding domain succeeds should return nil",
|
||||
inputDomainType: domain.DomainTypeCustom,
|
||||
instanceDomainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
Add(gomock.Any(), gomock.Any(), &domain.AddInstanceDomain{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "valid.domain",
|
||||
IsPrimary: gu.Ptr(false),
|
||||
IsGenerated: gu.Ptr(false),
|
||||
Type: domain.DomainTypeCustom,
|
||||
}).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return repo
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when adding domain fails should return error",
|
||||
inputDomainType: domain.DomainTypeTrusted,
|
||||
instanceDomainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
Add(gomock.Any(), gomock.Any(), &domain.AddInstanceDomain{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "valid.domain",
|
||||
Type: domain.DomainTypeTrusted,
|
||||
}).
|
||||
Times(1).
|
||||
Return(addErr)
|
||||
return repo
|
||||
},
|
||||
expectedError: zerrors.ThrowInternal(addErr, "DOM-uSCVn3", "Errors.Instance.Domain.Add"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
ctx := authz.NewMockContext("instance-1", "org-1", "")
|
||||
ctrl := gomock.NewController(t)
|
||||
cmd := domain.NewAddInstanceDomainCommand("instance-1", "valid.domain", tc.inputDomainType)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
if tc.instanceDomainRepo != nil {
|
||||
domain.WithInstanceDomainRepo(tc.instanceDomainRepo(ctrl))(opts)
|
||||
}
|
||||
|
||||
// When
|
||||
err := cmd.Execute(ctx, opts)
|
||||
|
||||
// Then
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api/object"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
type ListInstanceDomainsQuery struct {
|
||||
Request *instance_v2.ListCustomDomainsRequest
|
||||
|
||||
toReturn []*InstanceDomain
|
||||
}
|
||||
|
||||
var _ Querier[[]*InstanceDomain] = (*ListInstanceDomainsQuery)(nil)
|
||||
|
||||
func NewListInstanceDomainsQuery(inputRequest *instance_v2.ListCustomDomainsRequest) *ListInstanceDomainsQuery {
|
||||
return &ListInstanceDomainsQuery{
|
||||
Request: inputRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// Result implements [Querier].
|
||||
func (l *ListInstanceDomainsQuery) Result() []*InstanceDomain {
|
||||
return l.toReturn
|
||||
}
|
||||
|
||||
// Execute implements [Querier].
|
||||
func (l *ListInstanceDomainsQuery) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
domainRepo := opts.instanceDomainRepo
|
||||
|
||||
sorting := l.Sorting(domainRepo)
|
||||
limit, offset := l.Pagination()
|
||||
conds, err := l.conditions(domainRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instances, err := domainRepo.List(ctx, opts.DB(), conds, sorting, limit, offset)
|
||||
if err != nil {
|
||||
return zerrors.ThrowInternal(err, "DOM-ubaPNU", "Errors.Instance.Domain.List")
|
||||
}
|
||||
|
||||
l.toReturn = instances
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ListInstanceDomainsQuery) Pagination() (database.QueryOption, database.QueryOption) {
|
||||
return database.WithLimit(l.Request.GetPagination().GetLimit()),
|
||||
database.WithOffset(uint32(l.Request.GetPagination().GetOffset()))
|
||||
}
|
||||
|
||||
func (l *ListInstanceDomainsQuery) conditions(domainRepo InstanceDomainRepository) (database.QueryOption, error) {
|
||||
conditions := make([]database.Condition, len(l.Request.GetFilters()))
|
||||
for i, query := range l.Request.GetFilters() {
|
||||
switch assertedType := query.GetFilter().(type) {
|
||||
case *instance_v2.CustomDomainFilter_DomainFilter:
|
||||
method, err := object.TextQueryMethodToTextOperation(assertedType.DomainFilter.GetMethod())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = domainRepo.DomainCondition(
|
||||
method,
|
||||
assertedType.DomainFilter.GetDomain(),
|
||||
)
|
||||
case *instance_v2.CustomDomainFilter_GeneratedFilter:
|
||||
conditions[i] = domainRepo.IsGeneratedCondition(assertedType.GeneratedFilter)
|
||||
case *instance_v2.CustomDomainFilter_PrimaryFilter:
|
||||
conditions[i] = domainRepo.IsPrimaryCondition(assertedType.PrimaryFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(NewUnexpectedQueryTypeError(assertedType), "DOM-CjM93P", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
conditions = append(conditions, domainRepo.TypeCondition(DomainTypeCustom))
|
||||
return database.WithCondition(database.And(conditions...)), nil
|
||||
}
|
||||
|
||||
func (l *ListInstanceDomainsQuery) Sorting(domainRepo InstanceDomainRepository) database.QueryOption {
|
||||
var sortingCol database.Column
|
||||
|
||||
switch l.Request.GetSortingColumn() {
|
||||
|
||||
case instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE, instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
sortingCol = domainRepo.CreatedAtColumn()
|
||||
case instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
sortingCol = domainRepo.DomainColumn()
|
||||
case instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||
sortingCol = domainRepo.IsGeneratedColumn()
|
||||
case instance_v2.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
|
||||
sortingCol = domainRepo.IsPrimaryColumn()
|
||||
default:
|
||||
return func(opts *database.QueryOpts) {}
|
||||
}
|
||||
|
||||
if l.Request.GetPagination().GetAsc() {
|
||||
return database.WithOrderByAscending(sortingCol)
|
||||
}
|
||||
|
||||
return database.WithOrderByDescending(sortingCol)
|
||||
}
|
||||
|
||||
// String implements [Querier].
|
||||
func (l *ListInstanceDomainsQuery) String() string {
|
||||
return "ListInstanceDomainsQuery"
|
||||
}
|
||||
|
||||
// Validate implements [Querier].
|
||||
func (l *ListInstanceDomainsQuery) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
instanceID := strings.TrimSpace(l.Request.GetInstanceId())
|
||||
if instanceID == "" || instanceID == authz.GetInstance(ctx).InstanceID() {
|
||||
return l.checkDomainPerms(ctx, opts)
|
||||
}
|
||||
|
||||
// TODO(IAM-Marco): This is wrong, as it should check the permission for the input instance and not the one in context
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-yN7oCp", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
return l.checkDomainPerms(ctx, opts)
|
||||
}
|
||||
|
||||
func (l *ListInstanceDomainsQuery) checkDomainPerms(ctx context.Context, opts *InvokeOpts) error {
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, DomainReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-RyCEyr", "Errors.PermissionDenied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
)
|
||||
|
||||
func TestListInstanceDomainsQuery_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
permissionErr := errors.New("permission error")
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListCustomDomainsRequest
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "when missing domain read permission should return permission denied (empty instance id)",
|
||||
request: &instance.ListCustomDomainsRequest{InstanceId: " "},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-RyCEyr", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when valid permissions should return no error (ctx instance same as input)",
|
||||
request: &instance.ListCustomDomainsRequest{InstanceId: "instance-1"},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return checker
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and return error",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-yN7oCp", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and succeed then check domain and fail",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-RyCEyr", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and succeed then check domain and succeed",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return checker
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
ctx := authz.NewMockContext("instance-1", "org-1", "")
|
||||
|
||||
query := domain.NewListInstanceDomainsQuery(tc.request)
|
||||
err := query.Validate(ctx, &domain.InvokeOpts{
|
||||
Permissions: tc.permissionChecker(ctrl),
|
||||
})
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstanceDomainsQuery_Execute(t *testing.T) {
|
||||
t.Parallel()
|
||||
listErr := errors.New("list mock error")
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListCustomDomainsRequest
|
||||
repo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
expectedError error
|
||||
expectedDomains []*domain.InstanceDomain
|
||||
}{
|
||||
{
|
||||
name: "when parsing conditions fails should return invalid argument error",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: 99,
|
||||
},
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
return repo
|
||||
},
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "OBJ-iBRBVe", "invalid text query method"),
|
||||
},
|
||||
{
|
||||
name: "when listing domains fails should return error",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS,
|
||||
},
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
List(gomock.Any(), gomock.Any(),
|
||||
dbmock.QueryOptions(
|
||||
database.WithCondition(database.And(
|
||||
repo.DomainCondition(database.TextOperationContains, "test.domain"),
|
||||
repo.TypeCondition(domain.DomainTypeCustom),
|
||||
)),
|
||||
),
|
||||
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, repo.DomainColumn())),
|
||||
dbmock.QueryOptions(database.WithLimit(2)),
|
||||
dbmock.QueryOptions(database.WithOffset(1)),
|
||||
).
|
||||
Return(nil, listErr)
|
||||
return repo
|
||||
},
|
||||
expectedError: zerrors.ThrowInternal(listErr, "DOM-ubaPNU", "Errors.Instance.Domain.List"),
|
||||
},
|
||||
{
|
||||
name: "when listing domains succeeds should return domains",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS,
|
||||
},
|
||||
}},
|
||||
{Filter: &instance.CustomDomainFilter_GeneratedFilter{
|
||||
GeneratedFilter: true,
|
||||
}},
|
||||
{Filter: &instance.CustomDomainFilter_PrimaryFilter{
|
||||
PrimaryFilter: true,
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
domains := []*domain.InstanceDomain{
|
||||
{Domain: "test1.domain"},
|
||||
{Domain: "test2.domain"},
|
||||
}
|
||||
repo.EXPECT().
|
||||
List(gomock.Any(), gomock.Any(),
|
||||
dbmock.QueryOptions(
|
||||
database.WithCondition(database.And(
|
||||
repo.DomainCondition(database.TextOperationContains, "test.domain"),
|
||||
repo.IsGeneratedCondition(true),
|
||||
repo.IsPrimaryCondition(true),
|
||||
repo.TypeCondition(domain.DomainTypeCustom),
|
||||
)),
|
||||
),
|
||||
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, repo.DomainColumn())),
|
||||
dbmock.QueryOptions(database.WithLimit(0)),
|
||||
dbmock.QueryOptions(database.WithOffset(0)),
|
||||
).
|
||||
Return(domains, nil)
|
||||
return repo
|
||||
},
|
||||
expectedDomains: []*domain.InstanceDomain{
|
||||
{Domain: "test1.domain"},
|
||||
{Domain: "test2.domain"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
|
||||
if tc.repo != nil {
|
||||
instDomain := tc.repo(ctrl)
|
||||
domain.WithInstanceDomainRepo(instDomain)(opts)
|
||||
}
|
||||
query := domain.NewListInstanceDomainsQuery(tc.request)
|
||||
err := query.Execute(t.Context(), opts)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedDomains, query.Result())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstanceDomainsQuery_Sorting(t *testing.T) {
|
||||
t.Parallel()
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListCustomDomainsRequest
|
||||
|
||||
expectedSortingDirection database.OrderDirection
|
||||
expectedOrderBy database.Columns
|
||||
}{
|
||||
{
|
||||
name: "sort by creation date DESC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "created_at")},
|
||||
},
|
||||
{
|
||||
name: "sort by creation date ASC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "created_at")},
|
||||
},
|
||||
{
|
||||
name: "sort by domain DESC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "domain")},
|
||||
},
|
||||
{
|
||||
name: "sort by domain ASC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "domain")},
|
||||
},
|
||||
{
|
||||
name: "sort by generated DESC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "is_generated")},
|
||||
},
|
||||
{
|
||||
name: "sort by primary DESC",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "is_primary")},
|
||||
},
|
||||
{
|
||||
name: "unspecified field",
|
||||
request: &instance.ListCustomDomainsRequest{
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "created_at")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
mockCtrl := gomock.NewController(t)
|
||||
mockRepo := domainmock.NewInstancesDomainRepo(mockCtrl)
|
||||
|
||||
cmd := domain.NewListInstanceDomainsQuery(tc.request)
|
||||
opts := &database.QueryOpts{}
|
||||
|
||||
// Test
|
||||
queryOpt := cmd.Sorting(mockRepo)
|
||||
queryOpt(opts)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedOrderBy, opts.OrderBy)
|
||||
assert.Equal(t, tc.expectedSortingDirection, opts.Ordering)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type RemoveInstanceDomainCommand struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
DomainName string `json:"domain_name"`
|
||||
DType DomainType `json:"domain_type"`
|
||||
|
||||
DeleteTime *time.Time `json:"delete_date"`
|
||||
}
|
||||
|
||||
// RequiresTransaction implements [Transactional].
|
||||
func (r *RemoveInstanceDomainCommand) RequiresTransaction() {}
|
||||
|
||||
func NewRemoveInstanceDomainCommand(instanceID, domainName string, domainType DomainType) *RemoveInstanceDomainCommand {
|
||||
return &RemoveInstanceDomainCommand{
|
||||
InstanceID: instanceID,
|
||||
DomainName: domainName,
|
||||
DType: domainType,
|
||||
}
|
||||
}
|
||||
|
||||
// Events implements [Commander].
|
||||
func (r *RemoveInstanceDomainCommand) Events(ctx context.Context, _ *InvokeOpts) ([]eventstore.Command, error) {
|
||||
var toReturn eventstore.Command
|
||||
switch r.DType {
|
||||
case DomainTypeCustom:
|
||||
toReturn = instance.NewDomainRemovedEvent(ctx, &instance.NewAggregate(r.InstanceID).Aggregate, r.DomainName)
|
||||
case DomainTypeTrusted:
|
||||
toReturn = instance.NewTrustedDomainRemovedEvent(ctx, &instance.NewAggregate(r.InstanceID).Aggregate, r.DomainName)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return []eventstore.Command{toReturn}, nil
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (r *RemoveInstanceDomainCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
instanceRepo := opts.instanceDomainRepo
|
||||
|
||||
deletedRows, err := instanceRepo.Remove(ctx, opts.DB(), database.And(instanceRepo.InstanceIDCondition(r.InstanceID), instanceRepo.PrimaryKeyCondition(r.DomainName)))
|
||||
if err != nil {
|
||||
return zerrors.ThrowInternal(err, "DOM-KH7AuJ", "Errors.Instance.Domain.Remove")
|
||||
}
|
||||
|
||||
if deletedRows > 1 {
|
||||
return zerrors.ThrowInternal(nil, "DOM-XSCnJB", "Errors.Instsance.Domain.DeleteMismatch")
|
||||
}
|
||||
|
||||
if deletedRows < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.DeleteTime = gu.Ptr(time.Now())
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (r *RemoveInstanceDomainCommand) String() string {
|
||||
return "RemoveInstanceDomainCommand"
|
||||
}
|
||||
|
||||
// Validate implements [Commander].
|
||||
func (r *RemoveInstanceDomainCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
if r.DomainName = strings.TrimSpace(r.DomainName); r.DomainName == "" || len(r.DomainName) > 253 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-PLpYix", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if r.InstanceID = strings.TrimSpace(r.InstanceID); r.InstanceID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-VSsTTf", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if !allowedDomainCharacters.MatchString(r.DomainName) {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-dYD5I7", "Errors.Instance.Domain.InvalidCharacter")
|
||||
}
|
||||
|
||||
if r.InstanceID != authz.GetInstance(ctx).InstanceID() {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-83FUdY", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, DomainWritePermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-eroxID", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
domainRepo := opts.instanceDomainRepo
|
||||
d, err := domainRepo.Get(ctx, opts.DB(), database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, r.DomainName)))
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowInternal(err, "DOM-Zvv1fi", "Errors.Instance.Domain.Get")
|
||||
}
|
||||
|
||||
if d.Type == DomainTypeCustom && d.IsGenerated != nil && *d.IsGenerated {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "DOM-cSfCVG", "Errors.Instance.Domain.GeneratedNotRemovable")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*RemoveInstanceDomainCommand)(nil)
|
||||
_ Transactional = (*RemoveInstanceDomainCommand)(nil)
|
||||
)
|
||||
@@ -0,0 +1,434 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestRemoveInstanceDomainCommand_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
getErr := errors.New("get error")
|
||||
noRowFoundErr := &database.NoRowFoundError{}
|
||||
permissionErr := errors.New("permission error")
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
domainRepo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
inputInstanceID string
|
||||
inputDomainName string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when no instance ID should return invalid argument error",
|
||||
inputInstanceID: "",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-VSsTTf", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when no domain name should return invalid argument error",
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-PLpYix", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when name too long should return invalid argument error",
|
||||
inputInstanceID: " ",
|
||||
inputDomainName: "domain.is.to.loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-PLpYix", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when instance ID does not match context should return invalid argument error",
|
||||
inputInstanceID: "different-instance",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-83FUdY", "Errors.Invalid.Argument"),
|
||||
},
|
||||
{
|
||||
testName: "when user is missing permission should return permission denied",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return permChecker
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-eroxID", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
testName: "when domain not found should return no error",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return permChecker
|
||||
},
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
DomainCondition(database.TextOperationEqual, "test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(domainCond))).
|
||||
Times(1).
|
||||
Return(nil, noRowFoundErr)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
},
|
||||
{
|
||||
testName: "when get domain fails should return error",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return permChecker
|
||||
},
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
DomainCondition(database.TextOperationEqual, "test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(domainCond))).
|
||||
Times(1).
|
||||
Return(nil, getErr)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowInternal(getErr, "DOM-Zvv1fi", "Errors.Instance.Domain.Get"),
|
||||
},
|
||||
{
|
||||
testName: "when domain is generated should return precondition failed error",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return permChecker
|
||||
},
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
isGenerated := true
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
DomainCondition(database.TextOperationEqual, "test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(domainCond))).
|
||||
Times(1).
|
||||
Return(&domain.InstanceDomain{IsGenerated: &isGenerated, Type: domain.DomainTypeCustom}, nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-cSfCVG", "Errors.Instance.Domain.GeneratedNotRemovable"),
|
||||
},
|
||||
{
|
||||
testName: "when domain exists and is not generated should validate successfully",
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
permChecker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
permChecker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainWritePermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return permChecker
|
||||
},
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
isGenerated := false
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
DomainCondition(database.TextOperationEqual, "test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
repo.EXPECT().
|
||||
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(domainCond))).
|
||||
Times(1).
|
||||
Return(&domain.InstanceDomain{IsGenerated: &isGenerated, Type: domain.DomainTypeCustom}, nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
ctx := authz.NewMockContext("instance-1", "", "")
|
||||
ctrl := gomock.NewController(t)
|
||||
cmd := domain.NewRemoveInstanceDomainCommand(tc.inputInstanceID, tc.inputDomainName, domain.DomainTypeCustom)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
if tc.domainRepo != nil {
|
||||
domain.WithInstanceDomainRepo(tc.domainRepo(ctrl))(opts)
|
||||
}
|
||||
if tc.permissionChecker != nil {
|
||||
opts.Permissions = tc.permissionChecker(ctrl)
|
||||
}
|
||||
|
||||
err := cmd.Validate(ctx, opts)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveInstanceDomainCommand_Execute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
removeErr := errors.New("remove error")
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
domainRepo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
inputInstanceID string
|
||||
inputDomainName string
|
||||
expectedError error
|
||||
expectsDeleteTime bool
|
||||
}{
|
||||
{
|
||||
testName: "when domain remove fails should return error",
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
PrimaryKeyCondition("test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
instanceIDCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "instance_id"),
|
||||
database.TextOperationEqual,
|
||||
"instance-1",
|
||||
)
|
||||
repo.EXPECT().
|
||||
InstanceIDCondition("instance-1").
|
||||
Times(1).
|
||||
Return(instanceIDCond)
|
||||
repo.EXPECT().
|
||||
Remove(gomock.Any(), gomock.Any(), database.And(instanceIDCond, domainCond)).
|
||||
Times(1).
|
||||
Return(int64(0), removeErr)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowInternal(removeErr, "DOM-KH7AuJ", "Errors.Instance.Domain.Remove"),
|
||||
},
|
||||
{
|
||||
testName: "when domain remove returns 0 rows removed should return no error",
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
PrimaryKeyCondition("test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
instanceIDCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "instance_id"),
|
||||
database.TextOperationEqual,
|
||||
"instance-1",
|
||||
)
|
||||
repo.EXPECT().
|
||||
InstanceIDCondition("instance-1").
|
||||
Times(1).
|
||||
Return(instanceIDCond)
|
||||
repo.EXPECT().
|
||||
Remove(gomock.Any(), gomock.Any(), database.And(instanceIDCond, domainCond)).
|
||||
Times(1).
|
||||
Return(int64(0), nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
},
|
||||
{
|
||||
testName: "when domain remove returns more than 1 row removed should return internal error",
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
PrimaryKeyCondition("test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
instanceIDCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "instance_id"),
|
||||
database.TextOperationEqual,
|
||||
"instance-1",
|
||||
)
|
||||
repo.EXPECT().
|
||||
InstanceIDCondition("instance-1").
|
||||
Times(1).
|
||||
Return(instanceIDCond)
|
||||
repo.EXPECT().
|
||||
Remove(gomock.Any(), gomock.Any(), database.And(instanceIDCond, domainCond)).
|
||||
Times(1).
|
||||
Return(int64(2), nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectedError: zerrors.ThrowInternal(nil, "DOM-XSCnJB", "Errors.Instsance.Domain.DeleteMismatch"),
|
||||
},
|
||||
{
|
||||
testName: "when domain remove returns 1 row removed should return no error",
|
||||
domainRepo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewMockInstanceDomainRepository(ctrl)
|
||||
domainCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "domain"),
|
||||
database.TextOperationEqual,
|
||||
"test-domain.com")
|
||||
repo.EXPECT().
|
||||
PrimaryKeyCondition("test-domain.com").
|
||||
Times(1).
|
||||
Return(domainCond)
|
||||
instanceIDCond := database.NewTextCondition(
|
||||
database.NewColumn("instance_domains", "instance_id"),
|
||||
database.TextOperationEqual,
|
||||
"instance-1",
|
||||
)
|
||||
repo.EXPECT().
|
||||
InstanceIDCondition("instance-1").
|
||||
Times(1).
|
||||
Return(instanceIDCond)
|
||||
repo.EXPECT().
|
||||
Remove(gomock.Any(), gomock.Any(), database.And(instanceIDCond, domainCond)).
|
||||
Times(1).
|
||||
Return(int64(1), nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputDomainName: "test-domain.com",
|
||||
expectsDeleteTime: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
ctx := authz.NewMockContext("instance-1", "", "")
|
||||
ctrl := gomock.NewController(t)
|
||||
cmd := domain.NewRemoveInstanceDomainCommand(tc.inputInstanceID, tc.inputDomainName, domain.DomainTypeCustom)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
if tc.domainRepo != nil {
|
||||
domain.WithInstanceDomainRepo(tc.domainRepo(ctrl))(opts)
|
||||
}
|
||||
|
||||
err := cmd.Execute(ctx, opts)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectsDeleteTime, cmd.DeleteTime != nil && !cmd.DeleteTime.IsZero())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveInstanceDomainCommand_Events(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
domainType domain.DomainType
|
||||
expectedAggregateID string
|
||||
expectedEventType eventstore.Command
|
||||
}{
|
||||
{
|
||||
testName: "trusted domain",
|
||||
domainType: domain.DomainTypeTrusted,
|
||||
expectedAggregateID: "instance-1",
|
||||
expectedEventType: &instance.TrustedDomainRemovedEvent{},
|
||||
},
|
||||
{
|
||||
testName: "custom domain",
|
||||
domainType: domain.DomainTypeCustom,
|
||||
expectedAggregateID: "instance-1",
|
||||
expectedEventType: &instance.DomainRemovedEvent{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
instanceID := "instance-1"
|
||||
domainName := "test.domain"
|
||||
ctx := authz.NewMockContext(instanceID, "org-1", "")
|
||||
cmd := domain.NewRemoveInstanceDomainCommand(instanceID, domainName, tc.domainType)
|
||||
|
||||
// When
|
||||
events, err := cmd.Events(ctx, &domain.InvokeOpts{})
|
||||
|
||||
// Then
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events, 1)
|
||||
require.NotNil(t, events[0].Aggregate())
|
||||
assert.Equal(t, instanceID, events[0].Aggregate().ID)
|
||||
|
||||
require.IsType(t, tc.expectedEventType, events[0])
|
||||
|
||||
switch asserted := events[0].(type) {
|
||||
case *instance.DomainRemovedEvent:
|
||||
assert.Equal(t, domainName, asserted.Domain)
|
||||
case *instance.TrustedDomainRemovedEvent:
|
||||
assert.Equal(t, domainName, asserted.Domain)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
@@ -29,7 +30,10 @@ func (g *GetInstanceQuery) Execute(ctx context.Context, opts *InvokeOpts) (err e
|
||||
|
||||
instance, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(g.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return zerrors.ThrowNotFound(err, "DOM-QVrUwc", "Errors.Instance.NotFound")
|
||||
}
|
||||
return zerrors.ThrowInternal(err, "DOM-lvsRce", "Errors.Instance.Get")
|
||||
}
|
||||
|
||||
g.returnedInstance = instance
|
||||
@@ -45,14 +49,14 @@ func (g *GetInstanceQuery) String() string {
|
||||
func (g *GetInstanceQuery) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
g.ID = strings.TrimSpace(g.ID)
|
||||
if g.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID")
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "Errors.Instance.ID")
|
||||
}
|
||||
if g.ID != authz.GetInstance(ctx).InstanceID() {
|
||||
return zerrors.ThrowPermissionDenied(nil, "DOM-n0SvVB", "input instance ID doesn't match context instance")
|
||||
}
|
||||
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Uq6b00", "permission denied")
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Uq6b00", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -34,13 +34,13 @@ func TestGetInstanceCommand_Validate(t *testing.T) {
|
||||
name: "empty instance id, error",
|
||||
inputInstanceID: "",
|
||||
ctx: context.Background(),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID"),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "Errors.Instance.ID"),
|
||||
},
|
||||
{
|
||||
name: "whitespace instance id, error",
|
||||
inputInstanceID: " ",
|
||||
ctx: context.Background(),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID"),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "Errors.Instance.ID"),
|
||||
},
|
||||
{
|
||||
name: "instance id mismatch, error",
|
||||
@@ -62,7 +62,7 @@ func TestGetInstanceCommand_Validate(t *testing.T) {
|
||||
},
|
||||
inputInstanceID: "instance1",
|
||||
ctx: authz.NewMockContext("instance1", "", ""),
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Uq6b00", "permission denied"),
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Uq6b00", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "valid instance id, success",
|
||||
@@ -107,6 +107,7 @@ func TestGetInstanceCommand_Execute(t *testing.T) {
|
||||
|
||||
ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID())
|
||||
getErr := errors.New("get error")
|
||||
notFoundErr := database.NewNoRowFoundError(nil)
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
@@ -141,7 +142,32 @@ func TestGetInstanceCommand_Execute(t *testing.T) {
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedError: getErr,
|
||||
expectedError: zerrors.ThrowInternal(getErr, "DOM-lvsRce", "Errors.Instance.Get"),
|
||||
},
|
||||
{
|
||||
testName: "when instance is not found should return not found error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
instanceRepo := domainmock.NewInstanceRepo(ctrl)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
LoadDomains().
|
||||
Times(1).
|
||||
Return(instanceRepo)
|
||||
|
||||
instanceRepo.EXPECT().
|
||||
Get(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
dbmock.QueryOptions(database.WithCondition(
|
||||
instanceRepo.IDCondition("instance-1"),
|
||||
)),
|
||||
).
|
||||
Times(1).
|
||||
Return(nil, notFoundErr)
|
||||
return instanceRepo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-QVrUwc", "Errors.Instance.NotFound"),
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance succeeds should set instance on struct and return no error",
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
type ListInstancesQuery struct {
|
||||
Request *instance.ListInstancesRequest
|
||||
Request *instance_v2.ListInstancesRequest
|
||||
|
||||
toReturn []*Instance
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func (l *ListInstancesQuery) Result() []*Instance {
|
||||
|
||||
var _ Querier[[]*Instance] = (*ListInstancesQuery)(nil)
|
||||
|
||||
func NewListInstancesCommand(inputRequest *instance.ListInstancesRequest) *ListInstancesQuery {
|
||||
func NewListInstancesCommand(inputRequest *instance_v2.ListInstancesRequest) *ListInstancesQuery {
|
||||
return &ListInstancesQuery{
|
||||
Request: inputRequest,
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (l *ListInstancesQuery) Execute(ctx context.Context, opts *InvokeOpts) (err
|
||||
|
||||
instances, err := instanceRepo.List(ctx, opts.DB(), conds, sorting, limit, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
return zerrors.ThrowInternal(err, "DOM-AIRPxN", "Errors.Instance.List")
|
||||
}
|
||||
|
||||
l.toReturn = instances
|
||||
@@ -55,20 +55,20 @@ func (l *ListInstancesQuery) Pagination() (database.QueryOption, database.QueryO
|
||||
}
|
||||
|
||||
func (l *ListInstancesQuery) conditions(instanceRepo InstanceRepository, domainRepo InstanceDomainRepository) (database.QueryOption, error) {
|
||||
conditions := make([]database.Condition, len(l.Request.GetQueries()))
|
||||
for i, query := range l.Request.GetQueries() {
|
||||
switch assertedType := query.GetQuery().(type) {
|
||||
case *instance.Query_DomainQuery:
|
||||
domainConditions := make([]database.Condition, len(assertedType.DomainQuery.GetDomains()))
|
||||
for j, domain := range assertedType.DomainQuery.GetDomains() {
|
||||
conditions := make([]database.Condition, len(l.Request.GetFilters()))
|
||||
for i, query := range l.Request.GetFilters() {
|
||||
switch assertedType := query.GetFilter().(type) {
|
||||
case *instance_v2.Filter_CustomDomainsFilter:
|
||||
domainConditions := make([]database.Condition, len(assertedType.CustomDomainsFilter.GetDomains()))
|
||||
for j, domain := range assertedType.CustomDomainsFilter.GetDomains() {
|
||||
domainConditions[j] = domainRepo.DomainCondition(database.TextOperationEqual, domain)
|
||||
}
|
||||
conditions[i] = instanceRepo.ExistsDomain(
|
||||
database.Or(domainConditions...),
|
||||
)
|
||||
case *instance.Query_IdQuery:
|
||||
idConditions := make([]database.Condition, len(assertedType.IdQuery.GetIds()))
|
||||
for j, id := range assertedType.IdQuery.GetIds() {
|
||||
case *instance_v2.Filter_InIdsFilter:
|
||||
idConditions := make([]database.Condition, len(assertedType.InIdsFilter.GetIds()))
|
||||
for j, id := range assertedType.InIdsFilter.GetIds() {
|
||||
idConditions[j] = instanceRepo.IDCondition(id)
|
||||
}
|
||||
conditions[i] = database.Or(idConditions...)
|
||||
@@ -84,13 +84,13 @@ func (l *ListInstancesQuery) Sorting(instanceRepo InstanceRepository) database.Q
|
||||
var sortingCol database.Column
|
||||
|
||||
switch l.Request.GetSortingColumn() {
|
||||
case instance.FieldName_FIELD_NAME_CREATION_DATE:
|
||||
case instance_v2.FieldName_FIELD_NAME_CREATION_DATE:
|
||||
sortingCol = instanceRepo.CreatedAtColumn()
|
||||
case instance.FieldName_FIELD_NAME_ID:
|
||||
case instance_v2.FieldName_FIELD_NAME_ID:
|
||||
sortingCol = instanceRepo.IDColumn()
|
||||
case instance.FieldName_FIELD_NAME_NAME:
|
||||
case instance_v2.FieldName_FIELD_NAME_NAME:
|
||||
sortingCol = instanceRepo.NameColumn()
|
||||
case instance.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
case instance_v2.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return func(opts *database.QueryOpts) {}
|
||||
@@ -115,7 +115,7 @@ func (l *ListInstancesQuery) Validate(ctx context.Context, opts *InvokeOpts) (er
|
||||
// the instance in context. In here, instead we should probably loop through all retrieved
|
||||
// instances and check their permissions.
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-cuT6Ws", "permission denied")
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-cuT6Ws", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
@@ -31,7 +31,7 @@ func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
{
|
||||
name: "sort by creation date DESC",
|
||||
request: &instance.ListInstancesRequest{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
@@ -40,7 +40,7 @@ func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
{
|
||||
name: "sort by creation date ASC",
|
||||
request: &instance.ListInstancesRequest{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
{
|
||||
name: "sort by id DESC",
|
||||
request: &instance.ListInstancesRequest{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instances", "id")},
|
||||
@@ -58,7 +58,7 @@ func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
{
|
||||
name: "sort by id ASC",
|
||||
request: &instance.ListInstancesRequest{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
@@ -67,7 +67,7 @@ func TestListInstancesCommand_Sorting(t *testing.T) {
|
||||
{
|
||||
name: "unspecified field",
|
||||
request: &instance.ListInstancesRequest{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_UNSPECIFIED.Enum(),
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
},
|
||||
@@ -151,17 +151,17 @@ func TestListInstancesCommand_Execute(t *testing.T) {
|
||||
},
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_NAME.Enum(),
|
||||
Queries: []*instance.Query{
|
||||
{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_NAME,
|
||||
Filters: []*instance.Filter{
|
||||
{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{"instance-1", "instance-2"},
|
||||
}}},
|
||||
{Query: &instance.Query_DomainQuery{DomainQuery: &instance.DomainsQuery{
|
||||
{Filter: &instance.Filter_CustomDomainsFilter{CustomDomainsFilter: &instance.CustomDomainsFilter{
|
||||
Domains: []string{"domain1.example.com", "domain2.example.com"},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
expectedError: listErr,
|
||||
expectedError: zerrors.ThrowInternal(listErr, "DOM-AIRPxN", "Errors.Instance.List"),
|
||||
},
|
||||
{
|
||||
testName: "when listing instances succeeds should save into result and return nil",
|
||||
@@ -216,12 +216,12 @@ func TestListInstancesCommand_Execute(t *testing.T) {
|
||||
},
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_NAME.Enum(),
|
||||
Queries: []*instance.Query{
|
||||
{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_NAME,
|
||||
Filters: []*instance.Filter{
|
||||
{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{"instance-1", "instance-2"},
|
||||
}}},
|
||||
{Query: &instance.Query_DomainQuery{DomainQuery: &instance.DomainsQuery{
|
||||
{Filter: &instance.Filter_CustomDomainsFilter{CustomDomainsFilter: &instance.CustomDomainsFilter{
|
||||
Domains: []string{"domain1.example.com", "domain2.example.com"},
|
||||
}}},
|
||||
},
|
||||
@@ -288,7 +288,7 @@ func TestListInstancesCommand_Validate(t *testing.T) {
|
||||
|
||||
return permChecker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-cuT6Ws", "permission denied"),
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-cuT6Ws", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when valid permission should return no error",
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api/object"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
type ListInstanceTrustedDomainsQuery struct {
|
||||
Request *instance_v2.ListTrustedDomainsRequest
|
||||
|
||||
toReturn []*InstanceDomain
|
||||
}
|
||||
|
||||
var _ Querier[[]*InstanceDomain] = (*ListInstanceTrustedDomainsQuery)(nil)
|
||||
|
||||
func NewListInstanceTrustedDomainsQuery(inputRequest *instance_v2.ListTrustedDomainsRequest) *ListInstanceTrustedDomainsQuery {
|
||||
return &ListInstanceTrustedDomainsQuery{
|
||||
Request: inputRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// Result implements [Querier].
|
||||
func (l *ListInstanceTrustedDomainsQuery) Result() []*InstanceDomain {
|
||||
return l.toReturn
|
||||
}
|
||||
|
||||
// Execute implements [Querier].
|
||||
func (l *ListInstanceTrustedDomainsQuery) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
domainRepo := opts.instanceDomainRepo
|
||||
|
||||
sorting := l.Sorting(domainRepo)
|
||||
limit, offset := l.Pagination()
|
||||
conds, err := l.conditions(domainRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instances, err := domainRepo.List(ctx, opts.DB(), conds, sorting, limit, offset)
|
||||
if err != nil {
|
||||
return zerrors.ThrowInternal(err, "DOM-8eoWqY", "Errors.Instance.TrustedDomain.List")
|
||||
}
|
||||
|
||||
l.toReturn = instances
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ListInstanceTrustedDomainsQuery) Pagination() (database.QueryOption, database.QueryOption) {
|
||||
return database.WithLimit(l.Request.GetPagination().GetLimit()),
|
||||
database.WithOffset(uint32(l.Request.GetPagination().GetOffset()))
|
||||
}
|
||||
|
||||
func (l *ListInstanceTrustedDomainsQuery) conditions(domainRepo InstanceDomainRepository) (database.QueryOption, error) {
|
||||
conditions := make([]database.Condition, len(l.Request.GetFilters()))
|
||||
for i, query := range l.Request.GetFilters() {
|
||||
switch assertedType := query.GetFilter().(type) {
|
||||
|
||||
case *instance_v2.TrustedDomainFilter_DomainFilter:
|
||||
method, err := object.TextQueryMethodToTextOperation(assertedType.DomainFilter.GetMethod())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = domainRepo.DomainCondition(
|
||||
method,
|
||||
assertedType.DomainFilter.GetDomain(),
|
||||
)
|
||||
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(NewUnexpectedQueryTypeError(assertedType), "DOM-qMUBMr", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
conditions = append(conditions, domainRepo.TypeCondition(DomainTypeTrusted))
|
||||
return database.WithCondition(database.And(conditions...)), nil
|
||||
}
|
||||
|
||||
func (l *ListInstanceTrustedDomainsQuery) Sorting(domainRepo InstanceDomainRepository) database.QueryOption {
|
||||
var sortingCol database.Column
|
||||
|
||||
switch l.Request.GetSortingColumn() {
|
||||
case instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
sortingCol = domainRepo.CreatedAtColumn()
|
||||
|
||||
case instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
sortingCol = domainRepo.DomainColumn()
|
||||
case instance_v2.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return func(opts *database.QueryOpts) {}
|
||||
}
|
||||
|
||||
orderDirection := database.OrderDirectionDesc
|
||||
if l.Request.GetPagination().GetAsc() {
|
||||
orderDirection = database.OrderDirectionAsc
|
||||
}
|
||||
|
||||
return database.WithOrderBy(orderDirection, sortingCol)
|
||||
}
|
||||
|
||||
// String implements [Querier].
|
||||
func (l *ListInstanceTrustedDomainsQuery) String() string {
|
||||
return "ListInstanceTrustedDomainsQuery"
|
||||
}
|
||||
|
||||
// Validate implements [Querier].
|
||||
func (l *ListInstanceTrustedDomainsQuery) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
// TODO(IAM-Marco): These validations are copied from custom domains.
|
||||
// The eventstore side has no validation in place. To decide if we want to keep this or remove it
|
||||
instanceID := strings.TrimSpace(l.Request.GetInstanceId())
|
||||
if instanceID == "" || instanceID == authz.GetInstance(ctx).InstanceID() {
|
||||
return l.checkDomainPerms(ctx, opts)
|
||||
}
|
||||
|
||||
// TODO(IAM-Marco): This is wrong, as it should check the permission for the input instance and not the one in context
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-nx0QKI", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
return l.checkDomainPerms(ctx, opts)
|
||||
}
|
||||
|
||||
func (l *ListInstanceTrustedDomainsQuery) checkDomainPerms(ctx context.Context, opts *InvokeOpts) error {
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, DomainReadPermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-RyCEyr", "Errors.PermissionDenied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
)
|
||||
|
||||
func TestListInstanceTrustedDomainsQuery_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
permissionErr := errors.New("permission error")
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListTrustedDomainsRequest
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "when missing domain read permission should return permission denied (empty instance id)",
|
||||
request: &instance.ListTrustedDomainsRequest{InstanceId: " "},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-RyCEyr", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when valid permissions should return no error (ctx instance same as input)",
|
||||
request: &instance.ListTrustedDomainsRequest{InstanceId: "instance-1"},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return checker
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and return error",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-nx0QKI", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and succeed then check domain and fail",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(permissionErr)
|
||||
return checker
|
||||
},
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-RyCEyr", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "when input instance doesn't match context should check instance and succeed then check domain and succeed",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: "different-instance",
|
||||
},
|
||||
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
|
||||
checker := domainmock.NewMockPermissionChecker(ctrl)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
checker.EXPECT().
|
||||
CheckInstancePermission(gomock.Any(), domain.DomainReadPermission).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
return checker
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
ctx := authz.NewMockContext("instance-1", "org-1", "")
|
||||
|
||||
query := domain.NewListInstanceTrustedDomainsQuery(tc.request)
|
||||
err := query.Validate(ctx, &domain.InvokeOpts{
|
||||
Permissions: tc.permissionChecker(ctrl),
|
||||
})
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstanceTrustedDomainsQuery_Execute(t *testing.T) {
|
||||
t.Parallel()
|
||||
listErr := errors.New("list mock error")
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListTrustedDomainsRequest
|
||||
repo func(ctrl *gomock.Controller) domain.InstanceDomainRepository
|
||||
expectedError error
|
||||
expectedDomains []*domain.InstanceDomain
|
||||
}{
|
||||
{
|
||||
name: "when parsing conditions fails should return invalid argument error",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: 99,
|
||||
},
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
return repo
|
||||
},
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "OBJ-iBRBVe", "invalid text query method"),
|
||||
},
|
||||
{
|
||||
name: "when listing domains fails should return error",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS,
|
||||
},
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
repo.EXPECT().
|
||||
List(gomock.Any(), gomock.Any(),
|
||||
dbmock.QueryOptions(
|
||||
database.WithCondition(database.And(
|
||||
repo.DomainCondition(database.TextOperationContains, "test.domain"),
|
||||
repo.TypeCondition(domain.DomainTypeTrusted),
|
||||
)),
|
||||
),
|
||||
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, repo.DomainColumn())),
|
||||
dbmock.QueryOptions(database.WithLimit(2)),
|
||||
dbmock.QueryOptions(database.WithOffset(1)),
|
||||
).
|
||||
Return(nil, listErr)
|
||||
return repo
|
||||
},
|
||||
expectedError: zerrors.ThrowInternal(listErr, "DOM-8eoWqY", "Errors.Instance.TrustedDomain.List"),
|
||||
},
|
||||
{
|
||||
name: "when listing domains succeeds should return domains",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{
|
||||
Domain: "test.domain",
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS,
|
||||
},
|
||||
}},
|
||||
},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
repo: func(ctrl *gomock.Controller) domain.InstanceDomainRepository {
|
||||
repo := domainmock.NewInstancesDomainRepo(ctrl)
|
||||
domains := []*domain.InstanceDomain{
|
||||
{Domain: "test1.domain"},
|
||||
{Domain: "test2.domain"},
|
||||
}
|
||||
repo.EXPECT().
|
||||
List(gomock.Any(), gomock.Any(),
|
||||
dbmock.QueryOptions(
|
||||
database.WithCondition(database.And(
|
||||
repo.DomainCondition(database.TextOperationContains, "test.domain"),
|
||||
repo.TypeCondition(domain.DomainTypeTrusted),
|
||||
)),
|
||||
),
|
||||
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, repo.DomainColumn())),
|
||||
dbmock.QueryOptions(database.WithLimit(0)),
|
||||
dbmock.QueryOptions(database.WithOffset(0)),
|
||||
).
|
||||
Return(domains, nil)
|
||||
return repo
|
||||
},
|
||||
expectedDomains: []*domain.InstanceDomain{
|
||||
{Domain: "test1.domain"},
|
||||
{Domain: "test2.domain"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
}
|
||||
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
||||
|
||||
if tc.repo != nil {
|
||||
instDomain := tc.repo(ctrl)
|
||||
domain.WithInstanceDomainRepo(instDomain)(opts)
|
||||
}
|
||||
query := domain.NewListInstanceTrustedDomainsQuery(tc.request)
|
||||
err := query.Execute(t.Context(), opts)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedDomains, query.Result())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstanceTrustedDomainsQuery_Sorting(t *testing.T) {
|
||||
t.Parallel()
|
||||
tt := []struct {
|
||||
name string
|
||||
request *instance.ListTrustedDomainsRequest
|
||||
|
||||
expectedSortingDirection database.OrderDirection
|
||||
expectedOrderBy database.Columns
|
||||
}{
|
||||
{
|
||||
name: "sort by creation date DESC",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "created_at")},
|
||||
},
|
||||
{
|
||||
name: "sort by creation date ASC",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "created_at")},
|
||||
},
|
||||
{
|
||||
name: "sort by domain DESC",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionDesc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "domain")},
|
||||
},
|
||||
{
|
||||
name: "sort by domain ASC",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
expectedOrderBy: database.Columns{database.NewColumn("instance_domains", "domain")},
|
||||
},
|
||||
{
|
||||
name: "unspecified field",
|
||||
request: &instance.ListTrustedDomainsRequest{
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
},
|
||||
expectedSortingDirection: database.OrderDirectionAsc,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
mockCtrl := gomock.NewController(t)
|
||||
mockRepo := domainmock.NewInstancesDomainRepo(mockCtrl)
|
||||
|
||||
cmd := domain.NewListInstanceTrustedDomainsQuery(tc.request)
|
||||
opts := &database.QueryOpts{}
|
||||
|
||||
// Test
|
||||
queryOpt := cmd.Sorting(mockRepo)
|
||||
queryOpt(opts)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedOrderBy, opts.OrderBy)
|
||||
assert.Equal(t, tc.expectedSortingDirection, opts.Ordering)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type UpdateInstanceCommand struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ShouldSkipUpdate bool `json:"should_skip_update"`
|
||||
}
|
||||
|
||||
// RequiresTransaction implements [Transactional].
|
||||
@@ -20,6 +21,10 @@ func (u *UpdateInstanceCommand) RequiresTransaction() {}
|
||||
|
||||
// Events implements Commander.
|
||||
func (u *UpdateInstanceCommand) Events(ctx context.Context, _ *InvokeOpts) ([]eventstore.Command, error) {
|
||||
if u.ShouldSkipUpdate {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
instance.NewInstanceChangedEvent(ctx, &instance.NewAggregate(u.ID).Aggregate, u.Name),
|
||||
}, nil
|
||||
@@ -39,6 +44,10 @@ func NewUpdateInstanceCommand(id, name string) *UpdateInstanceCommand {
|
||||
|
||||
// Execute implements [Commander]
|
||||
func (u *UpdateInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
if u.ShouldSkipUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
instanceRepo := opts.instanceRepo
|
||||
|
||||
updateCount, err := instanceRepo.Update(
|
||||
@@ -48,16 +57,14 @@ func (u *UpdateInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (
|
||||
database.NewChange(instanceRepo.NameColumn(), u.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return zerrors.ThrowInternal(err, "DOM-PkVMNR", "Errors.Instance.Update")
|
||||
}
|
||||
|
||||
if updateCount == 0 {
|
||||
err = zerrors.ThrowNotFound(nil, "DOM-ghfov1", "Errors.Instance.NotFound")
|
||||
return err
|
||||
return zerrors.ThrowNotFound(nil, "DOM-ghfov1", "Errors.Instance.NotFound")
|
||||
}
|
||||
if updateCount > 1 {
|
||||
err = zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-HlrNmD", "unexpected number of rows updated")
|
||||
return err
|
||||
return zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-HlrNmD", "Errors.Instance.UpdateMismatch")
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -71,26 +78,24 @@ func (u *UpdateInstanceCommand) String() string {
|
||||
// Validate implements [Commander]
|
||||
func (u *UpdateInstanceCommand) Validate(ctx context.Context, opts *InvokeOpts) error {
|
||||
if u.ID = strings.TrimSpace(u.ID); u.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "invalid instance ID")
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "Errors.Instance.ID")
|
||||
}
|
||||
if u.Name = strings.TrimSpace(u.Name); u.Name == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "invalid instance name")
|
||||
return zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "Errors.Instance.Name")
|
||||
}
|
||||
|
||||
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceWritePermission); authZErr != nil {
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-M5ObLP", "permission denied")
|
||||
return zerrors.ThrowPermissionDenied(authZErr, "DOM-M5ObLP", "Errors.PermissionDenied")
|
||||
}
|
||||
|
||||
instanceRepo := opts.instanceRepo
|
||||
|
||||
instance, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(u.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
return zerrors.ThrowInternal(err, "DOM-j05Hdo", "Errors.Instance.Get")
|
||||
}
|
||||
|
||||
if instance.Name == u.Name {
|
||||
err = zerrors.ThrowPreconditionFailed(nil, "DOM-5MrT21", "Errors.Instance.NotChanged")
|
||||
return err
|
||||
}
|
||||
u.ShouldSkipUpdate = instance.Name == u.Name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -24,24 +25,25 @@ func TestUpdateInstanceCommand_Validate(t *testing.T) {
|
||||
permissionErr := errors.New("permission error")
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
inputInstanceID string
|
||||
inputInstanceName string
|
||||
expectedError error
|
||||
testName string
|
||||
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
|
||||
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
|
||||
inputInstanceID string
|
||||
inputInstanceName string
|
||||
expectedError error
|
||||
expectedUpdateSkip bool
|
||||
}{
|
||||
{
|
||||
testName: "when no ID should return invalid argument error",
|
||||
inputInstanceID: "",
|
||||
inputInstanceName: "test-name",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "invalid instance ID"),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "Errors.Instance.ID"),
|
||||
},
|
||||
{
|
||||
testName: "when no name shuld return invalid argument error",
|
||||
inputInstanceID: "test-id",
|
||||
inputInstanceName: "",
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "invalid instance name"),
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "Errors.Instance.Name"),
|
||||
},
|
||||
{
|
||||
testName: "when user is missing permission should return permission denied",
|
||||
@@ -57,7 +59,7 @@ func TestUpdateInstanceCommand_Validate(t *testing.T) {
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputInstanceName: "test instance update",
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-M5ObLP", "permission denied"),
|
||||
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-M5ObLP", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
testName: "when retrieving instance fails should return error",
|
||||
@@ -85,7 +87,7 @@ func TestUpdateInstanceCommand_Validate(t *testing.T) {
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputInstanceName: "test instance update",
|
||||
expectedError: getErr,
|
||||
expectedError: zerrors.ThrowInternal(getErr, "DOM-j05Hdo", "Errors.Instance.Get"),
|
||||
},
|
||||
{
|
||||
testName: "when instance name is not changed should return name not changed error",
|
||||
@@ -112,9 +114,9 @@ func TestUpdateInstanceCommand_Validate(t *testing.T) {
|
||||
}, nil)
|
||||
return repo
|
||||
},
|
||||
inputInstanceID: "instance-1",
|
||||
inputInstanceName: "test instance update",
|
||||
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-5MrT21", "Errors.Instance.NotChanged"),
|
||||
inputInstanceID: "instance-1",
|
||||
inputInstanceName: "test instance update",
|
||||
expectedUpdateSkip: true,
|
||||
},
|
||||
{
|
||||
testName: "when instance name is changed should validate successfully and return no error",
|
||||
@@ -167,6 +169,7 @@ func TestUpdateInstanceCommand_Validate(t *testing.T) {
|
||||
|
||||
err := cmd.Validate(ctx, opts)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedUpdateSkip, cmd.ShouldSkipUpdate)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -181,11 +184,16 @@ func TestUpdateInstanceCommand_Execute(t *testing.T) {
|
||||
|
||||
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
|
||||
|
||||
inputID string
|
||||
inputName string
|
||||
inputID string
|
||||
inputName string
|
||||
shouldSkipUpdate bool
|
||||
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when ShouldSkipUpdate is true should return nil",
|
||||
shouldSkipUpdate: true,
|
||||
},
|
||||
{
|
||||
testName: "when instance update fails should return error",
|
||||
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
|
||||
@@ -201,7 +209,7 @@ func TestUpdateInstanceCommand_Execute(t *testing.T) {
|
||||
},
|
||||
inputID: "instance-1",
|
||||
inputName: "test instance update",
|
||||
expectedError: updateErr,
|
||||
expectedError: zerrors.ThrowInternal(updateErr, "DOM-PkVMNR", "Errors.Instance.Update"),
|
||||
},
|
||||
{
|
||||
testName: "when instance update returns 0 rows updated should return not found error",
|
||||
@@ -235,7 +243,7 @@ func TestUpdateInstanceCommand_Execute(t *testing.T) {
|
||||
},
|
||||
inputID: "instance-1",
|
||||
inputName: "test instance update",
|
||||
expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-HlrNmD", "unexpected number of rows updated"),
|
||||
expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-HlrNmD", "Errors.Instance.UpdateMismatch"),
|
||||
},
|
||||
{
|
||||
testName: "when instance update returns 1 row updated should return no error and set non-primary verified domain",
|
||||
@@ -263,6 +271,7 @@ func TestUpdateInstanceCommand_Execute(t *testing.T) {
|
||||
ctx := authz.NewMockContext("instance-1", "", "")
|
||||
ctrl := gomock.NewController(t)
|
||||
cmd := domain.NewUpdateInstanceCommand(tc.inputID, tc.inputName)
|
||||
cmd.ShouldSkipUpdate = tc.shouldSkipUpdate
|
||||
|
||||
opts := &domain.InvokeOpts{
|
||||
Invoker: domain.NewTransactionInvoker(nil),
|
||||
@@ -283,18 +292,46 @@ func TestUpdateInstanceCommand_Execute(t *testing.T) {
|
||||
func TestUpdateInstanceCommand_Events(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx := authz.NewMockContext("instance-1", "", "")
|
||||
cmd := domain.NewUpdateInstanceCommand("instance-1", "test-name")
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputName string
|
||||
inputInstanceID string
|
||||
shouldSkipUpdate bool
|
||||
expectedEvent eventstore.Command
|
||||
}{
|
||||
{
|
||||
testName: "when ShouldSkipUpdate is true should return no event and no error",
|
||||
},
|
||||
{
|
||||
testName: "when ShouldSkipUpdate is true should return expected event",
|
||||
inputName: "test-name",
|
||||
inputInstanceID: "instance-1",
|
||||
expectedEvent: instance.NewInstanceChangedEvent(
|
||||
t.Context(),
|
||||
&instance.NewAggregate("instance-1").Aggregate,
|
||||
"test-name",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
// Test
|
||||
events, err := cmd.Events(ctx, &domain.InvokeOpts{})
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Given
|
||||
ctx := authz.NewMockContext(tc.inputInstanceID, "", "")
|
||||
cmd := domain.NewUpdateInstanceCommand(tc.inputInstanceID, tc.inputName)
|
||||
|
||||
// Verify
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, events, 1)
|
||||
// Test
|
||||
events, err := cmd.Events(ctx, &domain.InvokeOpts{})
|
||||
|
||||
event := events[0].(*instance.InstanceChangedEvent)
|
||||
assert.Equal(t, "instance-1", event.Aggregate().ID)
|
||||
assert.Equal(t, "test-name", event.Name)
|
||||
// Verify
|
||||
assert.NoError(t, err)
|
||||
if tc.expectedEvent != nil {
|
||||
require.Len(t, events, 1)
|
||||
|
||||
event := events[0].(*instance.InstanceChangedEvent)
|
||||
assert.Equal(t, tc.inputInstanceID, event.Aggregate().ID)
|
||||
assert.Equal(t, tc.inputName, event.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +353,44 @@ func (c *MockInstanceDomainRepositoryIsGeneratedColumnCall) DoAndReturn(f func()
|
||||
return c
|
||||
}
|
||||
|
||||
// IsGeneratedCondition mocks base method.
|
||||
func (m *MockInstanceDomainRepository) IsGeneratedCondition(arg0 bool) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsGeneratedCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsGeneratedCondition indicates an expected call of IsGeneratedCondition.
|
||||
func (mr *MockInstanceDomainRepositoryMockRecorder) IsGeneratedCondition(arg0 any) *MockInstanceDomainRepositoryIsGeneratedConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsGeneratedCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).IsGeneratedCondition), arg0)
|
||||
return &MockInstanceDomainRepositoryIsGeneratedConditionCall{Call: call}
|
||||
}
|
||||
|
||||
// MockInstanceDomainRepositoryIsGeneratedConditionCall wrap *gomock.Call
|
||||
type MockInstanceDomainRepositoryIsGeneratedConditionCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockInstanceDomainRepositoryIsGeneratedConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryIsGeneratedConditionCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockInstanceDomainRepositoryIsGeneratedConditionCall) Do(f func(bool) database.Condition) *MockInstanceDomainRepositoryIsGeneratedConditionCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockInstanceDomainRepositoryIsGeneratedConditionCall) DoAndReturn(f func(bool) database.Condition) *MockInstanceDomainRepositoryIsGeneratedConditionCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// IsPrimaryColumn mocks base method.
|
||||
func (m *MockInstanceDomainRepository) IsPrimaryColumn() database.Column {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
// TODO(IAM-Marco): Finish implementation
|
||||
type OIDCConfigurationUpdate struct {
|
||||
instanceDomainName string
|
||||
projectID string
|
||||
consoleAppID string
|
||||
|
||||
// oidcConfigChanges []OIDConfigChange
|
||||
// appID string
|
||||
}
|
||||
|
||||
// RequiresTransaction implements [Transactional].
|
||||
func (o *OIDCConfigurationUpdate) RequiresTransaction() {}
|
||||
|
||||
func NewOIDCConfigurationUpdate(instanceDomainName, projectID, consoleAppID string) *OIDCConfigurationUpdate {
|
||||
return &OIDCConfigurationUpdate{
|
||||
instanceDomainName: instanceDomainName,
|
||||
projectID: projectID,
|
||||
consoleAppID: consoleAppID,
|
||||
}
|
||||
}
|
||||
|
||||
// Events implements [Commander].
|
||||
func (o *OIDCConfigurationUpdate) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) {
|
||||
// oidcConfigChangedEvent, err := project.NewOIDCConfigChangedEvent(
|
||||
// ctx,
|
||||
// &project.NewAggregate(o.appID, o.projectID).Aggregate,
|
||||
// o.appID,
|
||||
// changes,
|
||||
// o.oidcConfigChanges,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return []eventstore.Command{
|
||||
// oidcConfigChangedEvent,
|
||||
// }, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (o *OIDCConfigurationUpdate) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (o *OIDCConfigurationUpdate) String() string {
|
||||
return "OIDCConfigurationUpdate"
|
||||
}
|
||||
|
||||
// Validate implements [Commander].
|
||||
func (o *OIDCConfigurationUpdate) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ (Commander) = (*OIDCConfigurationUpdate)(nil)
|
||||
_ (Transactional) = (*OIDCConfigurationUpdate)(nil)
|
||||
)
|
||||
@@ -5,6 +5,8 @@ import "context"
|
||||
const (
|
||||
InstanceReadPermission = "instance.read"
|
||||
InstanceWritePermission = "instance.write"
|
||||
DomainReadPermission = "domain.read"
|
||||
DomainWritePermission = "domain.write"
|
||||
OrganizationReadPermission = "organization.read"
|
||||
OrganizationWritePermission = "organization.write"
|
||||
)
|
||||
|
||||
@@ -163,6 +163,11 @@ func (i instanceDomain) TypeCondition(typ domain.DomainType) database.Condition
|
||||
return database.NewTextCondition(i.TypeColumn(), database.TextOperationEqual, typ.String())
|
||||
}
|
||||
|
||||
// IsGeneratedCondition implements domain.InstanceDomainRepository.
|
||||
func (i *instanceDomain) IsGeneratedCondition(isGenerated bool) database.Condition {
|
||||
return database.NewBooleanCondition(i.IsGeneratedColumn(), isGenerated)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *Server) AddInstanceTrustedDomain(ctx context.Context, req *admin_pb.Add
|
||||
}
|
||||
|
||||
func (s *Server) RemoveInstanceTrustedDomain(ctx context.Context, req *admin_pb.RemoveInstanceTrustedDomainRequest) (*admin_pb.RemoveInstanceTrustedDomainResponse, error) {
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Domain)
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Domain, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@ import (
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddCustomDomain(ctx context.Context, req *connect.Request[instance.AddCustomDomainRequest]) (*connect.Response[instance.AddCustomDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.AddCustomDomain(ctx, req)
|
||||
}
|
||||
|
||||
// Adding a custom domain is currently only allowed with system permissions,
|
||||
// so we directly check for them in the auth interceptor and do not check here again.
|
||||
details, err := s.command.AddInstanceDomain(ctx, req.Msg.GetCustomDomain())
|
||||
@@ -23,18 +29,32 @@ func (s *Server) AddCustomDomain(ctx context.Context, req *connect.Request[insta
|
||||
}
|
||||
|
||||
func (s *Server) RemoveCustomDomain(ctx context.Context, req *connect.Request[instance.RemoveCustomDomainRequest]) (*connect.Response[instance.RemoveCustomDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.RemoveCustomDomain(ctx, req)
|
||||
}
|
||||
|
||||
// Removing a custom domain is currently only allowed with system permissions,
|
||||
// so we directly check for them in the auth interceptor and do not check here again.
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Msg.GetCustomDomain())
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Msg.GetCustomDomain(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if details != nil {
|
||||
deletionDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&instance.RemoveCustomDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) AddTrustedDomain(ctx context.Context, req *connect.Request[instance.AddTrustedDomainRequest]) (*connect.Response[instance.AddTrustedDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.AddTrustedDomain(ctx, req)
|
||||
}
|
||||
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,15 +68,23 @@ func (s *Server) AddTrustedDomain(ctx context.Context, req *connect.Request[inst
|
||||
}
|
||||
|
||||
func (s *Server) RemoveTrustedDomain(ctx context.Context, req *connect.Request[instance.RemoveTrustedDomainRequest]) (*connect.Response[instance.RemoveTrustedDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.RemoveTrustedDomain(ctx, req)
|
||||
}
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Msg.GetTrustedDomain())
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Msg.GetTrustedDomain(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if details != nil {
|
||||
deletionDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&instance.RemoveTrustedDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
@@ -13,11 +15,19 @@ import (
|
||||
func (s *Server) DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
|
||||
// Deleting an instance is currently only allowed with system permissions,
|
||||
// so we directly check for them in the auth interceptor and do not check here again.
|
||||
obj, err := s.command.RemoveInstance(ctx, request.Msg.GetInstanceId())
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.DeleteInstance(ctx, request)
|
||||
}
|
||||
|
||||
obj, err := s.command.RemoveInstance(ctx, request.Msg.GetInstanceId(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// obj is nil when the instance is not found, so we return an empty message.
|
||||
if obj == nil {
|
||||
return &connect.Response[instance.DeleteInstanceResponse]{}, nil
|
||||
}
|
||||
return connect.NewResponse(&instance.DeleteInstanceResponse{
|
||||
DeletionDate: timestamppb.New(obj.EventDate),
|
||||
}), nil
|
||||
@@ -25,6 +35,10 @@ func (s *Server) DeleteInstance(ctx context.Context, request *connect.Request[in
|
||||
}
|
||||
|
||||
func (s *Server) UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.UpdateInstance(ctx, request)
|
||||
}
|
||||
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,16 +4,19 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
@@ -29,77 +32,95 @@ func TestAddCustomDomain(t *testing.T) {
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
relationalInst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerRelationalCtx := relationalInst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, relationalInst, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
relationalInst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: relationalInst.ID()})
|
||||
})
|
||||
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: inst, ctx: iamOwnerCtx},
|
||||
{testType: "relational", instance: relationalInst, ctx: iamOwnerRelationalCtx},
|
||||
}
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " ",
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-28nlD)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " " + integration.DomainName(),
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
instAndCtx.instance.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{CustomDomain: strings.TrimSpace(tc.inputRequest.CustomDomain)})
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{CustomDomain: strings.TrimSpace(tc.inputRequest.CustomDomain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,84 +128,108 @@ func TestRemoveCustomDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxES := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
|
||||
customDomain := integration.DomainName()
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxRelational := instRelational.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
|
||||
customDomainES := integration.DomainName()
|
||||
customDomainRelational := integration.DomainName()
|
||||
|
||||
_, err := instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: customDomainES})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: customDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: customDomainES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: customDomainRelational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-39nls)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " " + customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
customDomain string
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: instES, ctx: iamOwnerCtxES, customDomain: customDomainES},
|
||||
{testType: "relational", instance: instRelational, ctx: iamOwnerCtxRelational, customDomain: customDomainRelational},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " " + instAndCtx.customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,80 +241,111 @@ func TestAddTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1ES := integration.DomainName()
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1Relational := integration.DomainName()
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (COMMA-Stk21)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: d1ES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: d1Relational},
|
||||
}
|
||||
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: stateCase.domain,
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: stateCase.domain,
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
stateCase.inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,69 +357,101 @@ func TestRemoveTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
trustedDomain := integration.DomainName()
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainES := integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: trustedDomainES})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainRelational := integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: trustedDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: trustedDomainES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: trustedDomainRelational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " " + trustedDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: trustedDomainES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: trustedDomainRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917) testName: "when unauthZ context should return unauthZ error",
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
@@ -20,105 +23,140 @@ func TestDeleteInstance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
// Event store instances
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceESOwnerCtx := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
instES2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
instanceRelationalOwnerCtx := instRelational.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
instRelational2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational2, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.DeleteInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstanceID string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for own instance should return unauthZ error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (COMMA-AE3GS)",
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
i1 *integration.Instance
|
||||
i2 *integration.Instance
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.DeleteInstance(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
testedInstancesAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", i1: instES, i2: instES2, ctx: instanceESOwnerCtx},
|
||||
{testType: "relational", i1: instRelational, i2: instRelational2, ctx: instanceRelationalOwnerCtx},
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instES2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES2.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
instRelational2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational2.ID()})
|
||||
})
|
||||
|
||||
for _, instanceWithCtx := range testedInstancesAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.DeleteInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstanceID string
|
||||
expectsNullTimestamp bool
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: t.Context(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: t.Context(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i2.ID(),
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for own instance should return unauthZ error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when invalid id should return no error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectsNullTimestamp: true,
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instanceWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instanceWithCtx.i1.Client.InstanceV2.DeleteInstance(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateInstance(t *testing.T) {
|
||||
@@ -130,123 +168,163 @@ func TestUpdateInstance(t *testing.T) {
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2ES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
// Relational
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2Relational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, inst2Relational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.UpdateInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedNewName string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (INST-nuso2m)",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds (instance owner) should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedNewName: "an-updated-name",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
InstanceName: "an-updated-name2",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedNewName: "an-updated-name2",
|
||||
},
|
||||
t.Cleanup(func() {
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
inst2ES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2ES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
inst2Relational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2Relational.ID()})
|
||||
})
|
||||
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
inst2 *integration.Instance
|
||||
instanceOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.UpdateInstance(tc.inputContext, tc.inputRequest)
|
||||
testedInstancesAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, inst2: inst2ES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES},
|
||||
{testType: "relational", inst: instRelational, inst2: inst2Relational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
for _, instancesWithCtx := range testedInstancesAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.UpdateInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedNewName string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst2.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID(),
|
||||
InstanceName: "a valid name",
|
||||
},
|
||||
inputContext: instancesWithCtx.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (INST-nuso2m)",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds (instance owner) should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID(),
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedNewName: "an-updated-name",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst2.ID(),
|
||||
InstanceName: "an-updated-name2",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedNewName: "an-updated-name2",
|
||||
},
|
||||
}
|
||||
|
||||
require.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetChangeDate())
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
retrievedInstance, err := inst.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputRequest.GetInstanceId()})
|
||||
require.Nil(tt, err)
|
||||
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && instancesWithCtx.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", instancesWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instES.Client.InstanceV2.UpdateInstance(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
|
||||
require.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetChangeDate())
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
retrievedInstance, err := instES.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputRequest.GetInstanceId()})
|
||||
require.Nil(tt, err)
|
||||
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,19 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
@@ -27,70 +30,110 @@ func TestGetInstance(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
// Relational
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputInstanceID string
|
||||
expectedInstanceID string
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when unauthN context should return unauthN error",
|
||||
inputContext: context.Background(),
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputContext: organizationOwnerCtx,
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (systemUser)",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (own context)",
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
{
|
||||
testName: "when instance not found should return not found error",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: "invalid",
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.IAM.NotFound (QUERY-n0wng)",
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
instanceOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputInstanceID})
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: organizationOwnerCtxES},
|
||||
{testType: "relational", inst: instRelational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: organizationOwnerCtxRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, instWithCtx := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputInstanceID string
|
||||
expectedInstanceID string
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when unauthN context should return unauthN error",
|
||||
inputContext: context.Background(),
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputContext: instWithCtx.orgOwnerCtx,
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (systemUser)",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedInstanceID: instWithCtx.inst.ID(),
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Decide if we should set the instance in context.
|
||||
testName: "when request succeeds should return matching instance (own context)",
|
||||
inputContext: instWithCtx.instanceOwnerCtx,
|
||||
expectedInstanceID: instWithCtx.inst.ID(),
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when instance not found should return not found error",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: "invalid",
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.IAM.NotFound (QUERY-n0wng)",
|
||||
},
|
||||
}
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
|
||||
faultyTestCasesForRelational := []string{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
"when unauthZ context should return unauthZ error",
|
||||
// TODO(IAM-Marco): Decide if we should set the instance in context.
|
||||
"when request succeeds should return matching instance (own context)",
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
if instWithCtx.testType == "relational" && slices.Contains(faultyTestCasesForRelational, tc.testName) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", instWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instWithCtx.inst.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputInstanceID})
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,27 +146,27 @@ func TestListInstances(t *testing.T) {
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
instances := make([]*integration.Instance, 2)
|
||||
instances := make([]*integration.Instance, 5)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instances[0], instances[1] = inst, inst2
|
||||
inst3 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
inst4 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
inst5 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instances[0], instances[1], instances[2], instances[3], instances[4] = inst, inst2, inst3, inst4, inst5
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.FeatureV2.ResetInstanceFeatures(ctxWithSysAuthZ, &feature.ResetInstanceFeaturesRequest{})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
|
||||
})
|
||||
|
||||
// Sort in descending order
|
||||
slices.SortFunc(instances, func(i1, i2 *integration.Instance) int {
|
||||
res := i1.Instance.Details.CreationDate.AsTime().Compare(i2.Instance.Details.CreationDate.AsTime())
|
||||
if res == 0 {
|
||||
return res
|
||||
}
|
||||
return -res
|
||||
inst2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
|
||||
inst3.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst3.ID()})
|
||||
inst4.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst4.ID()})
|
||||
inst5.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst5.ID()})
|
||||
})
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
relTableState := integration.RelationalTablesEnableMatrix()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListInstancesRequest
|
||||
@@ -142,6 +185,7 @@ func TestListInstances(t *testing.T) {
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
@@ -153,43 +197,61 @@ func TestListInstances(t *testing.T) {
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
Pagination: &filter.PaginationRequest{Offset: 1, Limit: 3},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.Filter{
|
||||
{
|
||||
Filter: &instance.Filter_InIdsFilter{
|
||||
InIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{inst.ID(), inst2.ID()},
|
||||
Ids: []string{inst.ID(), inst2.ID(), inst3.ID(), inst4.ID(), inst5.ID()},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &instance.Filter_CustomDomainsFilter{
|
||||
CustomDomainsFilter: &instance.CustomDomainsFilter{
|
||||
Domains: []string{inst4.Domain, inst3.Domain, inst2.Domain, inst.Domain},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstances: []string{inst2.ID(), inst.ID()},
|
||||
expectedInstances: []string{inst3.ID(), inst2.ID(), inst.ID()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListInstances(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
|
||||
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
|
||||
|
||||
for i, ins := range res.GetInstances() {
|
||||
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
|
||||
}
|
||||
}
|
||||
for _, stateCase := range relTableState {
|
||||
integration.EnsureInstanceFeature(t, ctx, inst, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.State == "when relational tables are enabled" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListInstances(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
|
||||
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
|
||||
|
||||
for i, ins := range res.GetInstances() {
|
||||
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestListCustomDomains(t *testing.T) {
|
||||
@@ -200,115 +262,150 @@ func TestListCustomDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1, d2 := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1ES, d2ES := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
|
||||
_, err = instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1Relational, d2Relational := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d1})
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d1ES})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d2ES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d1Relational})
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d2Relational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
sysOwnerCtx context.Context
|
||||
instOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedDomains: []string{d1},
|
||||
},
|
||||
{testType: "eventstore", inst: instES, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.sysOwnerCtx,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.instOwnerCtx,
|
||||
expectedDomains: []string{td.expectedDomains[0]},
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && td.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,108 +417,144 @@ func TestListTrustedDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1, d2 := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1ES, d2ES := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
|
||||
_, err = instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1Relational, d2Relational := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d1})
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d1ES})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d2ES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d1Relational})
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d2Relational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
sysOwnerCtx context.Context
|
||||
instOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedDomains: []string{d1},
|
||||
},
|
||||
{testType: "eventstore", inst: instES, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.sysOwnerCtx,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.instOwnerCtx,
|
||||
expectedDomains: []string{td.expectedDomains[0]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && td.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,17 @@ import (
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
func (s *Server) GetInstance(ctx context.Context, _ *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
|
||||
func (s *Server) GetInstance(ctx context.Context, req *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.GetInstance(ctx, req)
|
||||
}
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -25,6 +30,10 @@ func (s *Server) GetInstance(ctx context.Context, _ *connect.Request[instance.Ge
|
||||
}
|
||||
|
||||
func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListInstances(ctx, req)
|
||||
}
|
||||
|
||||
// List instances is currently only allowed with system permissions,
|
||||
// so we directly check for them in the auth interceptor and do not check here again.
|
||||
queries, err := ListInstancesRequestToModel(req.Msg, s.systemDefaults)
|
||||
@@ -44,6 +53,10 @@ func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instanc
|
||||
}
|
||||
|
||||
func (s *Server) ListCustomDomains(ctx context.Context, req *connect.Request[instance.ListCustomDomainsRequest]) (*connect.Response[instance.ListCustomDomainsResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListCustomDomains(ctx, req)
|
||||
}
|
||||
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,6 +77,10 @@ func (s *Server) ListCustomDomains(ctx context.Context, req *connect.Request[ins
|
||||
}
|
||||
|
||||
func (s *Server) ListTrustedDomains(ctx context.Context, req *connect.Request[instance.ListTrustedDomainsRequest]) (*connect.Response[instance.ListTrustedDomainsResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListTrustedDomains(ctx, req)
|
||||
}
|
||||
|
||||
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,10 +6,16 @@ import (
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) AddCustomDomain(ctx context.Context, req *connect.Request[instance.AddCustomDomainRequest]) (*connect.Response[instance.AddCustomDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.AddCustomDomainBeta(ctx, req)
|
||||
}
|
||||
|
||||
details, err := s.command.AddInstanceDomain(ctx, req.Msg.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -20,16 +26,30 @@ func (s *Server) AddCustomDomain(ctx context.Context, req *connect.Request[insta
|
||||
}
|
||||
|
||||
func (s *Server) RemoveCustomDomain(ctx context.Context, req *connect.Request[instance.RemoveCustomDomainRequest]) (*connect.Response[instance.RemoveCustomDomainResponse], error) {
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Msg.GetDomain())
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.RemoveCustomDomainBeta(ctx, req)
|
||||
}
|
||||
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Msg.GetDomain(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if details != nil {
|
||||
deletionDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&instance.RemoveCustomDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) AddTrustedDomain(ctx context.Context, req *connect.Request[instance.AddTrustedDomainRequest]) (*connect.Response[instance.AddTrustedDomainResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.AddTrustedDomainBeta(ctx, req)
|
||||
}
|
||||
|
||||
details, err := s.command.AddTrustedDomain(ctx, req.Msg.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,12 +60,20 @@ func (s *Server) AddTrustedDomain(ctx context.Context, req *connect.Request[inst
|
||||
}
|
||||
|
||||
func (s *Server) RemoveTrustedDomain(ctx context.Context, req *connect.Request[instance.RemoveTrustedDomainRequest]) (*connect.Response[instance.RemoveTrustedDomainResponse], error) {
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Msg.GetDomain())
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.RemoveTrustedDomainBeta(ctx, req)
|
||||
}
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.Msg.GetDomain(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if details != nil {
|
||||
deletionDate = timestamppb.New(details.EventDate)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&instance.RemoveTrustedDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -13,23 +13,28 @@ import (
|
||||
|
||||
func (s *Server) DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.DeleteInstance(ctx, request)
|
||||
return instancev2.DeleteInstanceBeta(ctx, request)
|
||||
}
|
||||
|
||||
obj, err := s.command.RemoveInstance(ctx, request.Msg.GetInstanceId())
|
||||
obj, err := s.command.RemoveInstance(ctx, request.Msg.GetInstanceId(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if obj != nil {
|
||||
deletionDate = timestamppb.New(obj.EventDate)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&instance.DeleteInstanceResponse{
|
||||
DeletionDate: timestamppb.New(obj.EventDate),
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.UpdateInstance(ctx, request)
|
||||
return instancev2.UpdateInstanceBeta(ctx, request)
|
||||
}
|
||||
|
||||
obj, err := s.command.UpdateInstance(ctx, request.Msg.GetInstanceName())
|
||||
|
||||
@@ -4,16 +4,19 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
@@ -27,79 +30,98 @@ func TestAddCustomDomain(t *testing.T) {
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
|
||||
relationalInst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerRelationalCtx := relationalInst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, relationalInst, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
relationalInst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: relationalInst.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: integration.DomainName(),
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: integration.DomainName(),
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-28nlD)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: inst, ctx: iamOwnerCtx},
|
||||
{testType: "relational", instance: relationalInst, ctx: iamOwnerRelationalCtx},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: integration.DomainName(),
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: integration.DomainName(),
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
instAndCtx.instance.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2Beta.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,84 +129,108 @@ func TestRemoveCustomDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxES := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
|
||||
customDomain := integration.DomainName()
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxRelational := instRelational.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
|
||||
customDomainES := integration.DomainName()
|
||||
customDomainRelational := integration.DomainName()
|
||||
|
||||
_, err := instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: customDomainES})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: customDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: customDomainES})
|
||||
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: customDomainRelational})
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "custom1",
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-39nls)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " " + customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
customDomain string
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: instES, ctx: iamOwnerCtxES, customDomain: customDomainES},
|
||||
{testType: "relational", instance: instRelational, ctx: iamOwnerCtxRelational, customDomain: customDomainRelational},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: "custom1",
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
Domain: " " + instAndCtx.customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2Beta.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,80 +242,100 @@ func TestAddTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1ES := "trusted" + integration.DomainName()
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1Relational := "trusted" + integration.DomainName()
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (COMMA-Stk21)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: d1ES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: d1Relational},
|
||||
}
|
||||
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: stateCase.domain,
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: stateCase.domain,
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2Beta.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,69 +347,95 @@ func TestRemoveTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainES := integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: trustedDomainES})
|
||||
require.Nil(t, err)
|
||||
|
||||
trustedDomain := integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainRelational := integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: trustedDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: trustedDomainES})
|
||||
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: trustedDomainRelational})
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Domain: " " + trustedDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: trustedDomainES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: trustedDomainRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: "trusted1",
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
Domain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2Beta.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,13 @@ func TestDeleteInstace(t *testing.T) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
instances := []*integration.Instance{
|
||||
instEventStore,
|
||||
instRelational,
|
||||
type instanceWithType struct {
|
||||
instType string
|
||||
instance *integration.Instance
|
||||
}
|
||||
instances := []instanceWithType{
|
||||
{instType: "eventstore", instance: instEventStore},
|
||||
{instType: "relational", instance: instRelational},
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
@@ -46,12 +50,13 @@ func TestDeleteInstace(t *testing.T) {
|
||||
|
||||
for _, inst := range instances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.DeleteInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstanceID string
|
||||
testName string
|
||||
inputRequest *instance.DeleteInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstanceID string
|
||||
expectsNullTimestamp bool
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
@@ -63,34 +68,33 @@ func TestDeleteInstace(t *testing.T) {
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid input should return invalid argument error",
|
||||
testName: "when instance not found should return no error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceId: inst.instance.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "not found",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectsNullTimestamp: true,
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceId: inst.instance.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: inst.ID(),
|
||||
expectedInstanceID: inst.instance.ID(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%s - %s", inst.instType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
|
||||
res, err := inst.instance.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
require.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -232,97 +232,126 @@ func TestListCustomDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1, d2 := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1ES, d2ES := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
_, err = instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1Relational, d2Relational := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
|
||||
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
|
||||
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
|
||||
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing"},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2Beta.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,91 +363,118 @@ func TestListTrustedDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1, d2 := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1ES, d2ES := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
_, err = instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1Relational, d2Relational := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
|
||||
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
|
||||
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
|
||||
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
|
||||
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2Beta.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func (s *Server) GetInstance(ctx context.Context, request *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.GetInstance(ctx, request)
|
||||
return instancev2.GetInstanceBeta(ctx, request)
|
||||
}
|
||||
|
||||
inst, err := s.query.Instance(ctx, true)
|
||||
@@ -28,7 +28,7 @@ func (s *Server) GetInstance(ctx context.Context, request *connect.Request[insta
|
||||
|
||||
func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListInstances(ctx, req)
|
||||
return instancev2.ListInstancesBeta(ctx, req)
|
||||
}
|
||||
|
||||
queries, err := ListInstancesRequestToModel(req.Msg, s.systemDefaults)
|
||||
@@ -48,6 +48,10 @@ func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instanc
|
||||
}
|
||||
|
||||
func (s *Server) ListCustomDomains(ctx context.Context, req *connect.Request[instance.ListCustomDomainsRequest]) (*connect.Response[instance.ListCustomDomainsResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListCustomDomainsBeta(ctx, req)
|
||||
}
|
||||
|
||||
queries, err := ListCustomDomainsRequestToModel(req.Msg, s.systemDefaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -65,6 +69,10 @@ func (s *Server) ListCustomDomains(ctx context.Context, req *connect.Request[ins
|
||||
}
|
||||
|
||||
func (s *Server) ListTrustedDomains(ctx context.Context, req *connect.Request[instance.ListTrustedDomainsRequest]) (*connect.Response[instance.ListTrustedDomainsResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return instancev2.ListTrustedDomainsBeta(ctx, req)
|
||||
}
|
||||
|
||||
queries, err := ListTrustedDomainsRequestToModel(req.Msg, s.systemDefaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Server) CreateOrganization(ctx context.Context, request *connect.Reques
|
||||
|
||||
func (s *Server) UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org.UpdateOrganizationRequest]) (*connect.Response[v2beta_org.UpdateOrganizationResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return orgv2.UpdateOrganization(ctx, request)
|
||||
return orgv2.UpdateOrganizationBeta(ctx, request)
|
||||
}
|
||||
|
||||
org, err := s.command.ChangeOrg(ctx, request.Msg.GetId(), request.Msg.GetName())
|
||||
@@ -71,7 +71,7 @@ func (s *Server) ListOrganizations(ctx context.Context, request *connect.Request
|
||||
|
||||
func (s *Server) DeleteOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeleteOrganizationRequest]) (*connect.Response[v2beta_org.DeleteOrganizationResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return orgv2.DeleteOrganization(ctx, request)
|
||||
return orgv2.DeleteOrganizationBeta(ctx, request)
|
||||
}
|
||||
|
||||
details, err := s.command.RemoveOrg(ctx, request.Msg.GetId())
|
||||
@@ -127,7 +127,7 @@ func (s *Server) DeleteOrganizationMetadata(ctx context.Context, request *connec
|
||||
|
||||
func (s *Server) DeactivateOrganization(ctx context.Context, request *connect.Request[org.DeactivateOrganizationRequest]) (*connect.Response[org.DeactivateOrganizationResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return orgv2.DeactivateOrganization(ctx, request)
|
||||
return orgv2.DeactivateOrganizationBeta(ctx, request)
|
||||
}
|
||||
|
||||
objectDetails, err := s.command.DeactivateOrg(ctx, request.Msg.GetId())
|
||||
@@ -141,7 +141,7 @@ func (s *Server) DeactivateOrganization(ctx context.Context, request *connect.Re
|
||||
|
||||
func (s *Server) ActivateOrganization(ctx context.Context, request *connect.Request[org.ActivateOrganizationRequest]) (*connect.Response[org.ActivateOrganizationResponse], error) {
|
||||
if authz.GetFeatures(ctx).EnableRelationalTables {
|
||||
return orgv2.ActivateOrganization(ctx, request)
|
||||
return orgv2.ActivateOrganizationBeta(ctx, request)
|
||||
}
|
||||
|
||||
objectDetails, err := s.command.ReactivateOrg(ctx, request.Msg.GetId())
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *Server) CreateInstance(ctx context.Context, req *system_pb.CreateInstan
|
||||
}
|
||||
|
||||
func (s *Server) RemoveInstance(ctx context.Context, req *system_pb.RemoveInstanceRequest) (*system_pb.RemoveInstanceResponse, error) {
|
||||
details, err := s.command.RemoveInstance(ctx, req.InstanceId)
|
||||
details, err := s.command.RemoveInstance(ctx, req.InstanceId, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest)
|
||||
}
|
||||
|
||||
func (s *Server) RemoveDomain(ctx context.Context, req *system_pb.RemoveDomainRequest) (*system_pb.RemoveDomainResponse, error) {
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Domain)
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.Domain, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -894,17 +894,6 @@ func (c *Commands) prepareSetDefaultOrg(a *instance.Aggregate, orgID string) pre
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggregate, iamWriteModel *InstanceWriteModel, projectID string) (eventstore.Command, error) {
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, iamWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iamWriteModel.ProjectID != "" {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-EGbw2", "Errors.IAM.IAMProjectAlreadySet")
|
||||
}
|
||||
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareSetDefaultLanguage(a *instance.Aggregate, defaultLanguage language.Tag) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if err := domain.LanguageIsDefined(defaultLanguage); err != nil {
|
||||
@@ -959,54 +948,42 @@ func getSystemConfigWriteModel(ctx context.Context, filter preparation.FilterToQ
|
||||
return writeModel, err
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveInstance(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveInstance(ctx context.Context, id string, errorIfNotFound bool) (*domain.ObjectDetails, error) {
|
||||
instID := strings.TrimSpace(id)
|
||||
if instID == "" || len(instID) > 200 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-VeS2zI", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
instanceAgg := instance.NewAggregate(instID)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRemoveInstance(instanceAgg))
|
||||
writeModel, err := c.getInstanceWriteModelByID(ctx, instanceAgg.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.State.Exists() {
|
||||
if errorIfNotFound {
|
||||
return nil, zerrors.ThrowNotFound(err, "COMMA-AE3GS", "Errors.Instance.NotFound")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
milestoneAggregate := milestone.NewInstanceAggregate(instanceAgg.ID)
|
||||
cmds := []eventstore.Command{
|
||||
instance.NewInstanceRemovedEvent(ctx,
|
||||
&instanceAgg.Aggregate,
|
||||
writeModel.Name,
|
||||
writeModel.Domains),
|
||||
milestone.NewReachedEvent(ctx,
|
||||
milestoneAggregate,
|
||||
milestone.InstanceDeleted),
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, cmds...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.caches.milestones.Invalidate(ctx, milestoneIndexInstanceID, id)
|
||||
logging.OnError(err).Error("milestone invalidate")
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreatedAt(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareRemoveInstance(a *instance.Aggregate) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := c.getInstanceWriteModelByID(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowNotFound(err, "COMMA-pax9m3", "Errors.Instance.NotFound")
|
||||
}
|
||||
if !writeModel.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(err, "COMMA-AE3GS", "Errors.Instance.NotFound")
|
||||
}
|
||||
milestoneAggregate := milestone.NewInstanceAggregate(a.ID)
|
||||
return []eventstore.Command{
|
||||
instance.NewInstanceRemovedEvent(ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.Name,
|
||||
writeModel.Domains),
|
||||
milestone.NewReachedEvent(ctx,
|
||||
milestoneAggregate,
|
||||
milestone.InstanceDeleted),
|
||||
},
|
||||
nil
|
||||
}, nil
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) getInstanceWriteModelByID(ctx context.Context, orgID string) (*InstanceWriteModel, error) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
allowDomainRunes = regexp.MustCompile("^[a-zA-Z0-9\\.\\-]+$")
|
||||
allowDomainRunes = regexp.MustCompile(`^[a-zA-Z0-9\.\-]+$`)
|
||||
)
|
||||
|
||||
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
@@ -56,22 +56,35 @@ func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string, errorIfNotFound bool) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := removeInstanceDomain(instanceAgg, instanceDomain)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
//nolint: staticcheck
|
||||
writeModel, err := getInstanceDomainWriteModel(ctx, c.eventstore.Filter, instanceDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
|
||||
if writeModel.State != domain.InstanceDomainStateActive {
|
||||
if errorIfNotFound {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if writeModel.Generated {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
|
||||
}
|
||||
cmds := []eventstore.Command{instance.NewDomainRemovedEvent(ctx, &instanceAgg.Aggregate, instanceDomain)}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, cmds...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreatedAt(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addGeneratedInstanceDomain(ctx context.Context, a *instance.Aggregate, instanceName string) ([]preparation.Validation, error) {
|
||||
@@ -182,27 +195,6 @@ func setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) prep
|
||||
}
|
||||
}
|
||||
|
||||
func removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State != domain.InstanceDomainStateActive {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
|
||||
}
|
||||
if domainWriteModel.Generated {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
|
||||
}
|
||||
return []eventstore.Command{instance.NewDomainRemovedEvent(ctx, &a.Aggregate, instanceDomain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getInstanceDomainWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, domain string) (*InstanceDomainWriteModel, error) {
|
||||
domainWriteModel := NewInstanceDomainWriteModel(ctx, domain)
|
||||
events, err := filter(ctx, domainWriteModel.Query())
|
||||
|
||||
@@ -313,8 +313,9 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
domain string
|
||||
ctx context.Context
|
||||
domain string
|
||||
errorIfNotFound bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -342,7 +343,7 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "domain not exists, precondition error",
|
||||
name: "domain not exists and errorIfNotFound set to TRUE should return not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@@ -350,13 +351,29 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
domain: "domain.ch",
|
||||
ctx: context.Background(),
|
||||
domain: "domain.ch",
|
||||
errorIfNotFound: true,
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "domain not exists and errorIfNotFound set to FALSE should return no error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
domain: "domain.ch",
|
||||
errorIfNotFound: false,
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "remove domain, ok",
|
||||
fields: fields{
|
||||
@@ -420,7 +437,7 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.RemoveInstanceDomain(tt.args.ctx, tt.args.domain)
|
||||
got, err := r.RemoveInstanceDomain(tt.args.ctx, tt.args.domain, tt.args.errorIfNotFound)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1566,8 +1566,9 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
errorIfNotFound bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -1613,13 +1614,28 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
errorIfNotFound: true,
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance not existing, no error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
errorIfNotFound: false,
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "instance removed, not found error",
|
||||
fields: fields{
|
||||
@@ -1643,13 +1659,43 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
errorIfNotFound: true,
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance removed, no error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewInstanceAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"INSTANCE",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
instance.NewInstanceRemovedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"INSTANCE",
|
||||
nil,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
instanceID: "INSTANCE",
|
||||
errorIfNotFound: false,
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "instance remove, ok",
|
||||
fields: fields{
|
||||
@@ -1714,7 +1760,7 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
||||
milestones: noop.NewCache[milestoneIndex, string, *MilestonesReached](),
|
||||
},
|
||||
}
|
||||
got, err := r.RemoveInstance(tt.args.ctx, tt.args.instanceID)
|
||||
got, err := r.RemoveInstance(tt.args.ctx, tt.args.instanceID, tt.args.errorIfNotFound)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (c *Commands) AddTrustedDomain(ctx context.Context, trustedDomain string) (
|
||||
return writeModelToObjectDetails(&model.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string, errorIfNotFound bool) (*domain.ObjectDetails, error) {
|
||||
trustedDomain = strings.TrimSpace(trustedDomain)
|
||||
if trustedDomain == "" || len(trustedDomain) > 253 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-ajAzwu", "Errors.Invalid.Argument")
|
||||
@@ -48,8 +48,12 @@ func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string
|
||||
return nil, err
|
||||
}
|
||||
if !slices.Contains(model.Domains, trustedDomain) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMA-de3z9", "Errors.Instance.Domain.NotFound")
|
||||
if errorIfNotFound {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMA-de3z9", "Errors.Instance.Domain.NotFound")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = c.pushAppendAndReduce(ctx, model, instance.NewTrustedDomainRemovedEvent(ctx, InstanceAggregateFromWriteModel(&model.WriteModel), trustedDomain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -129,8 +129,9 @@ func TestCommands_RemoveTrustedDomain(t *testing.T) {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
trustedDomain string
|
||||
ctx context.Context
|
||||
trustedDomain string
|
||||
errorIfNotFound bool
|
||||
}
|
||||
type want struct {
|
||||
details *domain.ObjectDetails
|
||||
@@ -182,20 +183,35 @@ func TestCommands_RemoveTrustedDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "domain does not exists, error",
|
||||
name: "domain does not exist and flag to error set should return error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
trustedDomain: "domain.com",
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
trustedDomain: "domain.com",
|
||||
errorIfNotFound: true,
|
||||
},
|
||||
want: want{
|
||||
err: zerrors.ThrowNotFound(nil, "COMMA-de3z9", "Errors.Instance.Domain.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "domain does not exist and flag to error not set should return no error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
trustedDomain: "domain.com",
|
||||
errorIfNotFound: false,
|
||||
},
|
||||
want: want{},
|
||||
},
|
||||
{
|
||||
name: "domain remove ok",
|
||||
fields: fields{
|
||||
@@ -228,7 +244,7 @@ func TestCommands_RemoveTrustedDomain(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.RemoveTrustedDomain(tt.args.ctx, tt.args.trustedDomain)
|
||||
got, err := c.RemoveTrustedDomain(tt.args.ctx, tt.args.trustedDomain, tt.args.errorIfNotFound)
|
||||
assert.ErrorIs(t, err, tt.want.err)
|
||||
assertObjectDetails(t, tt.want.details, got)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user