mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:07:31 +00:00
feat: instance requests implementation for resource API (#9830)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved These changes introduce resource-based API endpoints for managing instances and custom domains. There are 4 types of changes: - Endpoint implementation: consisting of the protobuf interface and the implementation of the endpoint. E.g:606439a172
- (Integration) Tests: testing the implemented endpoint. E.g:cdfe1f0372
- Fixes: Bugs found during development that are being fixed. E.g:acbbeedd32
- Miscellaneous: code needed to put everything together or that doesn't fit any of the above categories. E.g:529df92abc
or6802cb5468
# How the Problems Are Solved _Ticked checkboxes indicate that the functionality is complete_ - [x] Instance - [x] Create endpoint - [x] Create endpoint tests - [x] Update endpoint - [x] Update endpoint tests - [x] Get endpoint - [x] Get endpoint tests - [x] Delete endpoint - [x] Delete endpoint tests - [x] Custom Domains - [x] Add custom domain - [x] Add custom domain tests - [x] Remove custom domain - [x] Remove custom domain tests - [x] List custom domains - [x] List custom domains tests - [x] Trusted Domains - [x] Add trusted domain - [x] Add trusted domain tests - [x] Remove trusted domain - [x] Remove trusted domain tests - [x] List trusted domains - [x] List trusted domains tests # Additional Changes When looking for instances (through the `ListInstances` endpoint) matching a given query, if you ask for the results to be order by a specific column, the query will fail due to a syntax error. This is fixed inacbbeedd32
. Further explanation can be found in the commit message # Additional Context - Relates to #9452 - CreateInstance has been excluded: https://github.com/zitadel/zitadel/issues/9930 - Permission checks / instance retrieval (middleware) needs to be changed to allow context based permission checks (https://github.com/zitadel/zitadel/issues/9929), required for ListInstances --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
246
internal/api/grpc/instance/v2beta/converter.go
Normal file
246
internal/api/grpc/instance/v2beta/converter.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func InstancesToPb(instances []*query.Instance) []*instance.Instance {
|
||||
list := []*instance.Instance{}
|
||||
for _, instance := range instances {
|
||||
list = append(list, ToProtoObject(instance))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func ToProtoObject(inst *query.Instance) *instance.Instance {
|
||||
return &instance.Instance{
|
||||
Id: inst.ID,
|
||||
Name: inst.Name,
|
||||
Domains: DomainsToPb(inst.Domains),
|
||||
Version: build.Version(),
|
||||
ChangeDate: timestamppb.New(inst.ChangeDate),
|
||||
CreationDate: timestamppb.New(inst.CreationDate),
|
||||
}
|
||||
}
|
||||
|
||||
func DomainsToPb(domains []*query.InstanceDomain) []*instance.Domain {
|
||||
d := []*instance.Domain{}
|
||||
for _, dm := range domains {
|
||||
pbDomain := DomainToPb(dm)
|
||||
d = append(d, pbDomain)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func DomainToPb(d *query.InstanceDomain) *instance.Domain {
|
||||
return &instance.Domain{
|
||||
Domain: d.Domain,
|
||||
Primary: d.IsPrimary,
|
||||
Generated: d.IsGenerated,
|
||||
InstanceId: d.InstanceID,
|
||||
CreationDate: timestamppb.New(d.CreationDate),
|
||||
}
|
||||
}
|
||||
|
||||
func ListInstancesRequestToModel(req *instance.ListInstancesRequest, sysDefaults systemdefaults.SystemDefaults) (*query.InstanceSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries, err := instanceQueriesToModel(req.GetQueries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &query.InstanceSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToInstanceColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func fieldNameToInstanceColumn(fieldName instance.FieldName) query.Column {
|
||||
switch fieldName {
|
||||
case instance.FieldName_FIELD_NAME_ID:
|
||||
return query.InstanceColumnID
|
||||
case instance.FieldName_FIELD_NAME_NAME:
|
||||
return query.InstanceColumnName
|
||||
case instance.FieldName_FIELD_NAME_CREATION_DATE:
|
||||
return query.InstanceColumnCreationDate
|
||||
case instance.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
||||
|
||||
func instanceQueriesToModel(queries []*instance.Query) (_ []query.SearchQuery, err error) {
|
||||
q := []query.SearchQuery{}
|
||||
for _, query := range queries {
|
||||
model, err := instanceQueryToModel(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q = append(q, model)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func instanceQueryToModel(searchQuery *instance.Query) (query.SearchQuery, error) {
|
||||
switch q := searchQuery.GetQuery().(type) {
|
||||
case *instance.Query_IdQuery:
|
||||
return query.NewInstanceIDsListSearchQuery(q.IdQuery.GetIds()...)
|
||||
case *instance.Query_DomainQuery:
|
||||
return query.NewInstanceDomainsListSearchQuery(q.DomainQuery.GetDomains()...)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func ListCustomDomainsRequestToModel(req *instance.ListCustomDomainsRequest, defaults systemdefaults.SystemDefaults) (*query.InstanceDomainSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(defaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries, err := domainQueriesToModel(req.GetQueries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &query.InstanceDomainSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToInstanceDomainColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fieldNameToInstanceDomainColumn(fieldName instance.DomainFieldName) query.Column {
|
||||
switch fieldName {
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return query.InstanceDomainDomainCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||
return query.InstanceDomainIsGeneratedCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
|
||||
return query.InstanceDomainIsPrimaryCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return query.InstanceDomainCreationDateCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
||||
|
||||
func domainQueriesToModel(queries []*instance.DomainSearchQuery) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = domainQueryToModel(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func domainQueryToModel(searchQuery *instance.DomainSearchQuery) (query.SearchQuery, error) {
|
||||
switch q := searchQuery.GetQuery().(type) {
|
||||
case *instance.DomainSearchQuery_DomainQuery:
|
||||
return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.GetMethod()), q.DomainQuery.GetDomain())
|
||||
case *instance.DomainSearchQuery_GeneratedQuery:
|
||||
return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedQuery.GetGenerated())
|
||||
case *instance.DomainSearchQuery_PrimaryQuery:
|
||||
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.GetPrimary())
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func ListTrustedDomainsRequestToModel(req *instance.ListTrustedDomainsRequest, defaults systemdefaults.SystemDefaults) (*query.InstanceTrustedDomainSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(defaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries, err := trustedDomainQueriesToModel(req.GetQueries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &query.InstanceTrustedDomainSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToInstanceTrustedDomainColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func trustedDomainQueriesToModel(queries []*instance.TrustedDomainSearchQuery) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = trustedDomainQueryToModel(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func trustedDomainQueryToModel(searchQuery *instance.TrustedDomainSearchQuery) (query.SearchQuery, error) {
|
||||
switch q := searchQuery.GetQuery().(type) {
|
||||
case *instance.TrustedDomainSearchQuery_DomainQuery:
|
||||
return query.NewInstanceTrustedDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.GetMethod()), q.DomainQuery.GetDomain())
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func trustedDomainsToPb(domains []*query.InstanceTrustedDomain) []*instance.TrustedDomain {
|
||||
d := make([]*instance.TrustedDomain, len(domains))
|
||||
for i, domain := range domains {
|
||||
d[i] = trustedDomainToPb(domain)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func trustedDomainToPb(d *query.InstanceTrustedDomain) *instance.TrustedDomain {
|
||||
return &instance.TrustedDomain{
|
||||
Domain: d.Domain,
|
||||
InstanceId: d.InstanceID,
|
||||
CreationDate: timestamppb.New(d.CreationDate),
|
||||
}
|
||||
}
|
||||
|
||||
func fieldNameToInstanceTrustedDomainColumn(fieldName instance.TrustedDomainFieldName) query.Column {
|
||||
switch fieldName {
|
||||
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return query.InstanceTrustedDomainDomainCol
|
||||
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return query.InstanceTrustedDomainCreationDateCol
|
||||
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||
fallthrough
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
390
internal/api/grpc/instance/v2beta/converter_test.go
Normal file
390
internal/api/grpc/instance/v2beta/converter_test.go
Normal file
@@ -0,0 +1,390 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
)
|
||||
|
||||
func Test_InstancesToPb(t *testing.T) {
|
||||
instances := []*query.Instance{
|
||||
{
|
||||
ID: "instance1",
|
||||
Name: "Instance One",
|
||||
Domains: []*query.InstanceDomain{
|
||||
{
|
||||
Domain: "example.com",
|
||||
IsPrimary: true,
|
||||
IsGenerated: false,
|
||||
Sequence: 1,
|
||||
CreationDate: time.Unix(123, 0),
|
||||
ChangeDate: time.Unix(124, 0),
|
||||
InstanceID: "instance1",
|
||||
},
|
||||
},
|
||||
Sequence: 1,
|
||||
CreationDate: time.Unix(123, 0),
|
||||
ChangeDate: time.Unix(124, 0),
|
||||
},
|
||||
}
|
||||
|
||||
want := []*instance.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "Instance One",
|
||||
Domains: []*instance.Domain{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
InstanceId: "instance1",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123},
|
||||
},
|
||||
},
|
||||
Version: build.Version(),
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 124},
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123},
|
||||
},
|
||||
}
|
||||
|
||||
got := InstancesToPb(instances)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func Test_ListInstancesRequestToModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1", "instance2")
|
||||
require.Nil(t, err)
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListInstancesRequest
|
||||
maxQueryLimit uint64
|
||||
expectedResult *query.InstanceSearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when query limit exceeds max query limit should return invalid argument error",
|
||||
maxQueryLimit: 1,
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
|
||||
},
|
||||
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return instance search query model",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
|
||||
},
|
||||
expectedResult: &query.InstanceSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Asc: true,
|
||||
SortingColumn: query.InstanceColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{searchInstanceByID},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tc.maxQueryLimit}
|
||||
|
||||
got, err := ListInstancesRequestToModel(tc.inputRequest, sysDefaults)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedResult, got)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fieldNameToInstanceColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fieldName instance.FieldName
|
||||
want query.Column
|
||||
}{
|
||||
{
|
||||
name: "ID field",
|
||||
fieldName: instance.FieldName_FIELD_NAME_ID,
|
||||
want: query.InstanceColumnID,
|
||||
},
|
||||
{
|
||||
name: "Name field",
|
||||
fieldName: instance.FieldName_FIELD_NAME_NAME,
|
||||
want: query.InstanceColumnName,
|
||||
},
|
||||
{
|
||||
name: "Creation Date field",
|
||||
fieldName: instance.FieldName_FIELD_NAME_CREATION_DATE,
|
||||
want: query.InstanceColumnCreationDate,
|
||||
},
|
||||
{
|
||||
name: "Unknown field",
|
||||
fieldName: instance.FieldName(99),
|
||||
want: query.Column{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := fieldNameToInstanceColumn(tt.fieldName)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_instanceQueryToModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1")
|
||||
require.Nil(t, err)
|
||||
|
||||
searchInstanceByDomain, err := query.NewInstanceDomainsListSearchQuery("example.com")
|
||||
require.Nil(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
searchQuery *instance.Query
|
||||
want query.SearchQuery
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ID Query",
|
||||
searchQuery: &instance.Query{
|
||||
Query: &instance.Query_IdQuery{
|
||||
IdQuery: &instance.IdsQuery{
|
||||
Ids: []string{"instance1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: searchInstanceByID,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Domain Query",
|
||||
searchQuery: &instance.Query{
|
||||
Query: &instance.Query_DomainQuery{
|
||||
DomainQuery: &instance.DomainsQuery{
|
||||
Domains: []string{"example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: searchInstanceByDomain,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Query",
|
||||
searchQuery: &instance.Query{
|
||||
Query: nil,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := instanceQueryToModel(tt.searchQuery)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ListCustomDomainsRequestToModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
querySearchRes, err := query.NewInstanceDomainDomainSearchQuery(query.TextEquals, "example.com")
|
||||
require.Nil(t, err)
|
||||
|
||||
queryGeneratedRes, err := query.NewInstanceDomainGeneratedSearchQuery(false)
|
||||
require.Nil(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
maxQueryLimit uint64
|
||||
expectedResult *query.InstanceDomainSearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "when query limit exceeds max query limit should return invalid argument error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
Domain: "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 1,
|
||||
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||
},
|
||||
{
|
||||
name: "when valid request should return domain search query model",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
|
||||
},
|
||||
{
|
||||
Query: &instance.DomainSearchQuery_GeneratedQuery{
|
||||
GeneratedQuery: &instance.DomainGeneratedQuery{Generated: false}},
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 100,
|
||||
expectedResult: &query.InstanceDomainSearchQueries{
|
||||
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceDomainIsPrimaryCol},
|
||||
Queries: []query.SearchQuery{
|
||||
querySearchRes,
|
||||
queryGeneratedRes,
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "when invalid query should return error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
|
||||
Queries: []*instance.DomainSearchQuery{
|
||||
{
|
||||
Query: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 100,
|
||||
expectedResult: nil,
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
|
||||
|
||||
got, err := ListCustomDomainsRequestToModel(tt.inputRequest, sysDefaults)
|
||||
assert.Equal(t, tt.expectedError, err)
|
||||
assert.Equal(t, tt.expectedResult, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ListTrustedDomainsRequestToModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
querySearchRes, err := query.NewInstanceTrustedDomainDomainSearchQuery(query.TextEquals, "example.com")
|
||||
require.Nil(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
maxQueryLimit uint64
|
||||
expectedResult *query.InstanceTrustedDomainSearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "when query limit exceeds max query limit should return invalid argument error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||
Queries: []*instance.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{
|
||||
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||
Domain: "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 1,
|
||||
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||
},
|
||||
{
|
||||
name: "when valid request should return domain search query model",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Queries: []*instance.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 100,
|
||||
expectedResult: &query.InstanceTrustedDomainSearchQueries{
|
||||
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceTrustedDomainCreationDateCol},
|
||||
Queries: []query.SearchQuery{querySearchRes},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "when invalid query should return error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||
Queries: []*instance.TrustedDomainSearchQuery{
|
||||
{
|
||||
Query: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
maxQueryLimit: 100,
|
||||
expectedResult: nil,
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
|
||||
|
||||
got, err := ListTrustedDomainsRequestToModel(tt.inputRequest, sysDefaults)
|
||||
assert.Equal(t, tt.expectedError, err)
|
||||
assert.Equal(t, tt.expectedResult, got)
|
||||
})
|
||||
}
|
||||
}
|
50
internal/api/grpc/instance/v2beta/domain.go
Normal file
50
internal/api/grpc/instance/v2beta/domain.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) AddCustomDomain(ctx context.Context, req *instance.AddCustomDomainRequest) (*instance.AddCustomDomainResponse, error) {
|
||||
details, err := s.command.AddInstanceDomain(ctx, req.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance.AddCustomDomainResponse{
|
||||
CreationDate: timestamppb.New(details.CreationDate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveCustomDomain(ctx context.Context, req *instance.RemoveCustomDomainRequest) (*instance.RemoveCustomDomainResponse, error) {
|
||||
details, err := s.command.RemoveInstanceDomain(ctx, req.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance.RemoveCustomDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddTrustedDomain(ctx context.Context, req *instance.AddTrustedDomainRequest) (*instance.AddTrustedDomainResponse, error) {
|
||||
details, err := s.command.AddTrustedDomain(ctx, req.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance.AddTrustedDomainResponse{
|
||||
CreationDate: timestamppb.New(details.CreationDate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveTrustedDomain(ctx context.Context, req *instance.RemoveTrustedDomainRequest) (*instance.RemoveTrustedDomainResponse, error) {
|
||||
details, err := s.command.RemoveTrustedDomain(ctx, req.GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.RemoveTrustedDomainResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
}, nil
|
||||
}
|
32
internal/api/grpc/instance/v2beta/instance.go
Normal file
32
internal/api/grpc/instance/v2beta/instance.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) DeleteInstance(ctx context.Context, request *instance.DeleteInstanceRequest) (*instance.DeleteInstanceResponse, error) {
|
||||
obj, err := s.command.RemoveInstance(ctx, request.GetInstanceId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.DeleteInstanceResponse{
|
||||
DeletionDate: timestamppb.New(obj.EventDate),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) UpdateInstance(ctx context.Context, request *instance.UpdateInstanceRequest) (*instance.UpdateInstanceResponse, error) {
|
||||
obj, err := s.command.UpdateInstance(ctx, request.GetInstanceName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.UpdateInstanceResponse{
|
||||
ChangeDate: timestamppb.New(obj.EventDate),
|
||||
}, nil
|
||||
}
|
@@ -0,0 +1,350 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"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"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func TestAddCustomDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.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: gofakeit.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: gofakeit.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: " " + gofakeit.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||
}
|
||||
})
|
||||
|
||||
// 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCustomDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||
|
||||
customDomain := gofakeit.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
|
||||
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()})
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.RemoveCustomDomain(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.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddTrustedDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.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: " " + gofakeit.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||
}
|
||||
})
|
||||
|
||||
// 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveTrustedDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
trustedDomain := gofakeit.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
|
||||
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()})
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestDeleteInstace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
})
|
||||
|
||||
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: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid input should return invalid argument error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Instance not found (COMMA-AE3GS)",
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateInstace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
})
|
||||
|
||||
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 unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedNewName: "an-updated-name",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.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, 20*time.Second)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
retrievedInstance, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
|
||||
require.Nil(tt, err)
|
||||
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
369
internal/api/grpc/instance/v2beta/integration_test/query_test.go
Normal file
369
internal/api/grpc/instance/v2beta/integration_test/query_test.go
Normal file
@@ -0,0 +1,369 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestGetInstance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
expectedInstanceID string
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when unauthN context should return unauthN error",
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
|
||||
|
||||
// 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstances(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
instances := make([]*integration.Instance, 2)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instances[0], instances[1] = inst, inst2
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
inst.Client.InstanceV2Beta.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
|
||||
})
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListInstancesRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstances []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
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.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
|
||||
Queries: []*instance.Query{
|
||||
{
|
||||
Query: &instance.Query_IdQuery{
|
||||
IdQuery: &instance.IdsQuery{
|
||||
Ids: []string{inst.ID(), inst2.ID()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstances: []string{inst2.ID(), inst.ID()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.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) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1, d2 := "custom."+gofakeit.DomainName(), "custom."+gofakeit.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
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()})
|
||||
})
|
||||
|
||||
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: 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},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.ListCustomDomains(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 == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(t, domains, tc.expectedDomains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTrustedDomains(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1, d2 := "trusted."+gofakeit.DomainName(), "trusted."+gofakeit.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||
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()})
|
||||
})
|
||||
|
||||
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: 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.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},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2Beta.ListTrustedDomains(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)
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(t, domains, tc.expectedDomains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
70
internal/api/grpc/instance/v2beta/query.go
Normal file
70
internal/api/grpc/instance/v2beta/query.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) GetInstance(ctx context.Context, _ *instance.GetInstanceRequest) (*instance.GetInstanceResponse, error) {
|
||||
inst, err := s.query.Instance(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.GetInstanceResponse{
|
||||
Instance: ToProtoObject(inst),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListInstances(ctx context.Context, req *instance.ListInstancesRequest) (*instance.ListInstancesResponse, error) {
|
||||
queries, err := ListInstancesRequestToModel(req, s.systemDefaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances, err := s.query.SearchInstances(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.ListInstancesResponse{
|
||||
Instances: InstancesToPb(instances.Instances),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, instances.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListCustomDomains(ctx context.Context, req *instance.ListCustomDomainsRequest) (*instance.ListCustomDomainsResponse, error) {
|
||||
queries, err := ListCustomDomainsRequestToModel(req, s.systemDefaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains, err := s.query.SearchInstanceDomains(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.ListCustomDomainsResponse{
|
||||
Domains: DomainsToPb(domains.Domains),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListTrustedDomains(ctx context.Context, req *instance.ListTrustedDomainsRequest) (*instance.ListTrustedDomainsResponse, error) {
|
||||
queries, err := ListTrustedDomainsRequestToModel(req, s.systemDefaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains, err := s.query.SearchInstanceTrustedDomains(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance.ListTrustedDomainsResponse{
|
||||
TrustedDomain: trustedDomainsToPb(domains.Domains),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||
}, nil
|
||||
}
|
60
internal/api/grpc/instance/v2beta/server.go
Normal file
60
internal/api/grpc/instance/v2beta/server.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
var _ instance.InstanceServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
instance.UnimplementedInstanceServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
defaultInstance command.InstanceSetup
|
||||
externalDomain string
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
database string,
|
||||
defaultInstance command.InstanceSetup,
|
||||
externalDomain string,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
defaultInstance: defaultInstance,
|
||||
externalDomain: externalDomain,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
instance.RegisterInstanceServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return instance.InstanceService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return instance.InstanceService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return instance.InstanceService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return instance.RegisterInstanceServiceHandler
|
||||
}
|
Reference in New Issue
Block a user