mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-02 14:12:27 +00:00
feat(api): move instance service to v2 (#10919)
# Which Problems Are Solved
As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.
# How the Problems Are Solved
- This PR moves instance v2beta service and its endpoints to a
corresponding v2 version. The v2beta service and endpoints are
deprecated.
- The docs are moved to the new GA service and its endpoints. The v2beta
is not displayed anymore.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- All required fields have been marked with (google.api.field_behavior)
= REQUIRED and validation rules have been added where missing
- `Domain` has been renamed to `CustomDomain` to align with naming
conventions
- `..Query` has been renamed to `..Filter` to align with other services
- The `instance_id` parameter can now passed on all endpoints and is
properly used, but requires `system` permissions. It can be omitted to
use the own instance (identified by context as any other service).
- The following endpoints are affected:
- GetInstance
- UpdateInstance
- ListCustomDomains
- AddTrustedDomain
- RemoveTrustedDomain
- ListTrustedDomains
- InstanceService has been added the InstanceInterceptor's
`explicitInstanceIdServices` to allow passing the id
- If the instance is not found by id, the error is not directly returned
to prevent enumeration.
- Permissions are checked in the API instead of the interceptor for
these calls.
- Setting the same instance name in the update no longer returns an
error, but the previous change date.
# Additional Changes
none
# Additional Context
- part of https://github.com/zitadel/zitadel/issues/10772
- requires backport to v4.x
This commit is contained in:
@@ -49,7 +49,8 @@ import (
|
|||||||
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
||||||
group_v2 "github.com/zitadel/zitadel/internal/api/grpc/group/v2"
|
group_v2 "github.com/zitadel/zitadel/internal/api/grpc/group/v2"
|
||||||
idp_v2 "github.com/zitadel/zitadel/internal/api/grpc/idp/v2"
|
idp_v2 "github.com/zitadel/zitadel/internal/api/grpc/idp/v2"
|
||||||
instance "github.com/zitadel/zitadel/internal/api/grpc/instance/v2beta"
|
instance_v2 "github.com/zitadel/zitadel/internal/api/grpc/instance/v2"
|
||||||
|
instance_v2beta "github.com/zitadel/zitadel/internal/api/grpc/instance/v2beta"
|
||||||
internal_permission_v2 "github.com/zitadel/zitadel/internal/api/grpc/internal_permission/v2"
|
internal_permission_v2 "github.com/zitadel/zitadel/internal/api/grpc/internal_permission/v2"
|
||||||
internal_permission_v2beta "github.com/zitadel/zitadel/internal/api/grpc/internal_permission/v2beta"
|
internal_permission_v2beta "github.com/zitadel/zitadel/internal/api/grpc/internal_permission/v2beta"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||||
@@ -488,7 +489,10 @@ func startAPIs(
|
|||||||
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain), tlsConfig); err != nil {
|
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain), tlsConfig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := apis.RegisterService(ctx, instance.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain)); err != nil {
|
if err := apis.RegisterService(ctx, instance_v2beta.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := apis.RegisterService(ctx, instance_v2.CreateServer(commands, queries, config.DefaultInstance, config.ExternalDomain, permissionCheck)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, keys.User, config.AuditLogRetention), tlsConfig); err != nil {
|
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, keys.User, config.AuditLogRetention), tlsConfig); err != nil {
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
instance_v2: {
|
instance_v2: {
|
||||||
specPath:
|
specPath:
|
||||||
".artifacts/openapi3/zitadel/instance/v2beta/instance_service.openapi.yaml",
|
".artifacts/openapi3/zitadel/instance/v2/instance_service.openapi.yaml",
|
||||||
outputDir: "docs/apis/resources/instance_service_v2",
|
outputDir: "docs/apis/resources/instance_service_v2",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
|
|||||||
@@ -842,16 +842,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "Instance (Beta)",
|
label: "Instance",
|
||||||
link: {
|
link: {
|
||||||
type: "generated-index",
|
type: "generated-index",
|
||||||
title: "Instance Service API (Beta)",
|
title: "Instance Service API",
|
||||||
slug: "/apis/resources/instance_service_v2",
|
slug: "/apis/resources/instance_service_v2",
|
||||||
description:
|
description:
|
||||||
"This API is intended to manage instances, custom domains and trusted domains in ZITADEL.\n" +
|
"This API is intended to manage instances, custom domains and trusted domains in ZITADEL.\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"This service is in beta state. It can AND will continue breaking until a stable version is released.\n" +
|
|
||||||
"\n" +
|
|
||||||
"This v2 of the API provides the same functionalities as the v1, but organised on a per resource basis.\n" +
|
"This v2 of the API provides the same functionalities as the v1, but organised on a per resource basis.\n" +
|
||||||
"The whole functionality related to domains (custom and trusted) has been moved under this instance API."
|
"The whole functionality related to domains (custom and trusted) has been moved under this instance API."
|
||||||
,
|
,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
instance_pb "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@ func (a *API) registerConnectServer(service server.ConnectServer) {
|
|||||||
connect_middleware.CallDurationHandler(),
|
connect_middleware.CallDurationHandler(),
|
||||||
connect_middleware.MetricsHandler(metricTypes, grpc_api.Probes...),
|
connect_middleware.MetricsHandler(metricTypes, grpc_api.Probes...),
|
||||||
connect_middleware.NoCacheInterceptor(),
|
connect_middleware.NoCacheInterceptor(),
|
||||||
connect_middleware.InstanceInterceptor(a.queries, a.externalDomain, a.translator, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName),
|
connect_middleware.InstanceInterceptor(a.queries, a.externalDomain, a.translator, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName, instance_pb.InstanceService_ServiceDesc.ServiceName),
|
||||||
connect_middleware.AccessStorageInterceptor(a.accessInterceptor.AccessService()),
|
connect_middleware.AccessStorageInterceptor(a.accessInterceptor.AccessService()),
|
||||||
connect_middleware.ErrorHandler(),
|
connect_middleware.ErrorHandler(),
|
||||||
connect_middleware.LimitsInterceptor(system_pb.SystemService_ServiceDesc.ServiceName),
|
connect_middleware.LimitsInterceptor(system_pb.SystemService_ServiceDesc.ServiceName),
|
||||||
|
|||||||
246
internal/api/grpc/instance/v2/converter.go
Normal file
246
internal/api/grpc/instance/v2/converter.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/cmd/build"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||||
|
"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"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
CustomDomains: DomainsToPb(inst.Domains),
|
||||||
|
Version: build.Version(),
|
||||||
|
ChangeDate: timestamppb.New(inst.ChangeDate),
|
||||||
|
CreationDate: timestamppb.New(inst.CreationDate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DomainsToPb(domains []*query.InstanceDomain) []*instance.CustomDomain {
|
||||||
|
d := []*instance.CustomDomain{}
|
||||||
|
for _, dm := range domains {
|
||||||
|
pbDomain := DomainToPb(dm)
|
||||||
|
d = append(d, pbDomain)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func DomainToPb(d *query.InstanceDomain) *instance.CustomDomain {
|
||||||
|
return &instance.CustomDomain{
|
||||||
|
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 := filtersToQueries(req.GetFilters())
|
||||||
|
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 filtersToQueries(filters []*instance.Filter) (_ []query.SearchQuery, err error) {
|
||||||
|
q := []query.SearchQuery{}
|
||||||
|
for _, filter := range filters {
|
||||||
|
model, err := instanceFilterToQuery(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
q = append(q, model)
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func instanceFilterToQuery(filter *instance.Filter) (query.SearchQuery, error) {
|
||||||
|
switch q := filter.GetFilter().(type) {
|
||||||
|
case *instance.Filter_InIdsFilter:
|
||||||
|
return query.NewInstanceIDsListSearchQuery(q.InIdsFilter.GetIds()...)
|
||||||
|
case *instance.Filter_CustomDomainsFilter:
|
||||||
|
return query.NewInstanceDomainsListSearchQuery(q.CustomDomainsFilter.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 := customDomainFiltersToQueries(req.GetFilters())
|
||||||
|
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 customDomainFiltersToQueries(filters []*instance.CustomDomainFilter) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(filters))
|
||||||
|
for i, filter := range filters {
|
||||||
|
q[i], err = customDomainFilterToQuery(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func customDomainFilterToQuery(filter *instance.CustomDomainFilter) (query.SearchQuery, error) {
|
||||||
|
switch q := filter.GetFilter().(type) {
|
||||||
|
case *instance.CustomDomainFilter_DomainFilter:
|
||||||
|
return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainFilter.GetMethod()), q.DomainFilter.GetDomain())
|
||||||
|
case *instance.CustomDomainFilter_GeneratedFilter:
|
||||||
|
return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedFilter)
|
||||||
|
case *instance.CustomDomainFilter_PrimaryFilter:
|
||||||
|
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryFilter)
|
||||||
|
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 := trustedDomainFiltersToQueries(req.GetFilters())
|
||||||
|
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 trustedDomainFiltersToQueries(filters []*instance.TrustedDomainFilter) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(filters))
|
||||||
|
for i, filter := range filters {
|
||||||
|
q[i], err = trustedDomainQueryToModel(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedDomainQueryToModel(filter *instance.TrustedDomainFilter) (query.SearchQuery, error) {
|
||||||
|
switch q := filter.GetFilter().(type) {
|
||||||
|
case *instance.TrustedDomainFilter_DomainFilter:
|
||||||
|
return query.NewInstanceTrustedDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainFilter.GetMethod()), q.DomainFilter.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{}
|
||||||
|
}
|
||||||
|
}
|
||||||
391
internal/api/grpc/instance/v2/converter_test.go
Normal file
391
internal/api/grpc/instance/v2/converter_test.go
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
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"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
"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",
|
||||||
|
CustomDomains: []*instance.CustomDomain{
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
Filters: []*instance.Filter{{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{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,
|
||||||
|
Filters: []*instance.Filter{{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{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.Filter
|
||||||
|
want query.SearchQuery
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ID Query",
|
||||||
|
searchQuery: &instance.Filter{
|
||||||
|
Filter: &instance.Filter_InIdsFilter{
|
||||||
|
InIdsFilter: &filter.InIDsFilter{
|
||||||
|
Ids: []string{"instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: searchInstanceByID,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Domain Query",
|
||||||
|
searchQuery: &instance.Filter{
|
||||||
|
Filter: &instance.Filter_CustomDomainsFilter{
|
||||||
|
CustomDomainsFilter: &instance.CustomDomainsFilter{
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: searchInstanceByDomain,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Query",
|
||||||
|
searchQuery: &instance.Filter{
|
||||||
|
Filter: nil,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got, err := instanceFilterToQuery(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,
|
||||||
|
Filters: []*instance.CustomDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||||
|
DomainFilter: &instance.DomainFilter{
|
||||||
|
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,
|
||||||
|
Filters: []*instance.CustomDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||||
|
DomainFilter: &instance.DomainFilter{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Filter: &instance.CustomDomainFilter_GeneratedFilter{
|
||||||
|
GeneratedFilter: 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,
|
||||||
|
Filters: []*instance.CustomDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: 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,
|
||||||
|
Filters: []*instance.TrustedDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||||
|
DomainFilter: &instance.DomainFilter{
|
||||||
|
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,
|
||||||
|
Filters: []*instance.TrustedDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||||
|
DomainFilter: &instance.DomainFilter{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},
|
||||||
|
Filters: []*instance.TrustedDomainFilter{
|
||||||
|
{
|
||||||
|
Filter: 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
62
internal/api/grpc/instance/v2/domain.go
Normal file
62
internal/api/grpc/instance/v2/domain.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
// 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())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&instance.AddCustomDomainResponse{
|
||||||
|
CreationDate: timestamppb.New(details.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveCustomDomain(ctx context.Context, req *connect.Request[instance.RemoveCustomDomainRequest]) (*connect.Response[instance.RemoveCustomDomainResponse], error) {
|
||||||
|
// 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())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&instance.RemoveCustomDomainResponse{
|
||||||
|
DeletionDate: timestamppb.New(details.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddTrustedDomain(ctx context.Context, req *connect.Request[instance.AddTrustedDomainRequest]) (*connect.Response[instance.AddTrustedDomainResponse], error) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
details, err := s.command.AddTrustedDomain(ctx, req.Msg.GetTrustedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&instance.AddTrustedDomainResponse{
|
||||||
|
CreationDate: timestamppb.New(details.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveTrustedDomain(ctx context.Context, req *connect.Request[instance.RemoveTrustedDomainRequest]) (*connect.Response[instance.RemoveTrustedDomainResponse], error) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
details, err := s.command.RemoveTrustedDomain(ctx, req.Msg.GetTrustedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.RemoveTrustedDomainResponse{
|
||||||
|
DeletionDate: timestamppb.New(details.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
39
internal/api/grpc/instance/v2/instance.go
Normal file
39
internal/api/grpc/instance/v2/instance.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.DeleteInstanceResponse{
|
||||||
|
DeletionDate: timestamppb.New(obj.EventDate),
|
||||||
|
}), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceWrite, domain.PermissionInstanceWrite); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
obj, err := s.command.UpdateInstance(ctx, request.Msg.GetInstanceName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.UpdateInstanceResponse{
|
||||||
|
ChangeDate: timestamppb.New(obj.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
349
internal/api/grpc/instance/v2/integration_test/domain_test.go
Normal file
349
internal/api/grpc/instance/v2/integration_test/domain_test.go
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/instance/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2.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(),
|
||||||
|
CustomDomain: 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(),
|
||||||
|
CustomDomain: 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(),
|
||||||
|
CustomDomain: " ",
|
||||||
|
},
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
|
customDomain := integration.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
|
||||||
|
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()})
|
||||||
|
})
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2.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.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2.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(),
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
trustedDomain := integration.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
|
||||||
|
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()})
|
||||||
|
})
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := 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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
252
internal/api/grpc/instance/v2/integration_test/instance_test.go
Normal file
252
internal/api/grpc/instance/v2/integration_test/instance_test.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/instance/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeleteInstance(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)
|
||||||
|
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2.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: 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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateInstance(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)
|
||||||
|
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2.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 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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.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 := 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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
427
internal/api/grpc/instance/v2/integration_test/query_test.go
Normal file
427
internal/api/grpc/instance/v2/integration_test/query_test.go
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/filter/v2"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
organizationOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.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)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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})
|
||||||
|
|
||||||
|
// 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.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
|
||||||
|
})
|
||||||
|
|
||||||
|
orgOwnerCtx := inst.WithAuthorizationToken(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.Unauthenticated,
|
||||||
|
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
Filters: []*instance.Filter{
|
||||||
|
{
|
||||||
|
Filter: &instance.Filter_InIdsFilter{
|
||||||
|
InIdsFilter: &filter.InIDsFilter{
|
||||||
|
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.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) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
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})
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, err = inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
|
||||||
|
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()})
|
||||||
|
})
|
||||||
|
|
||||||
|
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,
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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})
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, err = inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
|
||||||
|
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()})
|
||||||
|
})
|
||||||
|
|
||||||
|
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.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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
domains := []string{}
|
||||||
|
for _, d := range res.GetTrustedDomain() {
|
||||||
|
domains = append(domains, d.GetDomain())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Subset(collect, domains, tc.expectedDomains)
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
84
internal/api/grpc/instance/v2/query.go
Normal file
84
internal/api/grpc/instance/v2/query.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inst, err := s.query.Instance(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.GetInstanceResponse{
|
||||||
|
Instance: ToProtoObject(inst),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
instances, err := s.query.SearchInstances(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.ListInstancesResponse{
|
||||||
|
Instances: InstancesToPb(instances.Instances),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, instances.SearchResponse),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListCustomDomains(ctx context.Context, req *connect.Request[instance.ListCustomDomainsRequest]) (*connect.Response[instance.ListCustomDomainsResponse], error) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries, err := ListCustomDomainsRequestToModel(req.Msg, s.systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains, err := s.query.SearchInstanceDomains(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.ListCustomDomainsResponse{
|
||||||
|
Domains: DomainsToPb(domains.Domains),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListTrustedDomains(ctx context.Context, req *connect.Request[instance.ListTrustedDomainsRequest]) (*connect.Response[instance.ListTrustedDomainsResponse], error) {
|
||||||
|
if err := s.checkPermission(ctx, domain.PermissionSystemInstanceRead, domain.PermissionInstanceRead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries, err := ListTrustedDomainsRequestToModel(req.Msg, s.systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains, err := s.query.SearchInstanceTrustedDomains(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect.NewResponse(&instance.ListTrustedDomainsResponse{
|
||||||
|
TrustedDomain: trustedDomainsToPb(domains.Domains),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
74
internal/api/grpc/instance/v2/server.go
Normal file
74
internal/api/grpc/instance/v2/server.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/instance/v2/instanceconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ instanceconnect.InstanceServiceHandler = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
command *command.Commands
|
||||||
|
query *query.Queries
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
defaultInstance command.InstanceSetup
|
||||||
|
externalDomain string
|
||||||
|
permissionCheck domain.PermissionCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateServer(
|
||||||
|
command *command.Commands,
|
||||||
|
query *query.Queries,
|
||||||
|
defaultInstance command.InstanceSetup,
|
||||||
|
externalDomain string,
|
||||||
|
check domain.PermissionCheck,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
command: command,
|
||||||
|
query: query,
|
||||||
|
defaultInstance: defaultInstance,
|
||||||
|
externalDomain: externalDomain,
|
||||||
|
permissionCheck: check,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||||
|
return instanceconnect.NewInstanceServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||||
|
return instance.File_zitadel_instance_v2_instance_service_proto
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPermission checks if either the system-wide or the instance-specific permission is granted.
|
||||||
|
func (s *Server) checkPermission(ctx context.Context, systemPermission, instancePermission string) error {
|
||||||
|
// Let's first check the system permission since it's already resolved into the context.
|
||||||
|
// If that succeeds, we don't need to resolve the instance permission.
|
||||||
|
if err := s.permissionCheck(ctx, systemPermission, "", ""); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.permissionCheck(ctx, instancePermission, "", "")
|
||||||
|
}
|
||||||
@@ -40,7 +40,10 @@ func setInstance(ctx context.Context, req connect.AnyRequest, handler connect.Un
|
|||||||
if !ok {
|
if !ok {
|
||||||
return handler(ctx, req)
|
return handler(ctx, req)
|
||||||
}
|
}
|
||||||
return addInstanceByID(interceptorCtx, req, handler, verifier, translator, withInstanceIDProperty.GetInstanceId())
|
instanceID := withInstanceIDProperty.GetInstanceId()
|
||||||
|
if instanceID != "" {
|
||||||
|
return addInstanceByID(interceptorCtx, req, handler, verifier, translator, instanceID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
explicitInstanceRequest, ok := req.Any().(interface {
|
explicitInstanceRequest, ok := req.Any().(interface {
|
||||||
@@ -61,11 +64,12 @@ func setInstance(ctx context.Context, req connect.AnyRequest, handler connect.Un
|
|||||||
func addInstanceByID(ctx context.Context, req connect.AnyRequest, handler connect.UnaryFunc, verifier authz.InstanceVerifier, translator *i18n.Translator, id string) (connect.AnyResponse, error) {
|
func addInstanceByID(ctx context.Context, req connect.AnyRequest, handler connect.UnaryFunc, verifier authz.InstanceVerifier, translator *i18n.Translator, id string) (connect.AnyResponse, error) {
|
||||||
instance, err := verifier.InstanceByID(ctx, id)
|
instance, err := verifier.InstanceByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFoundErr := new(zerrors.ZitadelError)
|
// We do not want to expose whether the instance id was invalid or not
|
||||||
if errors.As(err, ¬FoundErr) {
|
// to prevent leaking information about existing instances.
|
||||||
notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil)
|
// In case the user has permission to access the instance, but the instance does not exist,
|
||||||
}
|
// the error will be returned by the business logic later on.
|
||||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("unable to set instance using id %s: %w", id, notFoundErr))
|
logging.WithFields("instanceID", id).WithError(err).Error("unable to set instance by id")
|
||||||
|
return handler(ctx, req)
|
||||||
}
|
}
|
||||||
return handler(authz.WithInstance(ctx, instance), req)
|
return handler(authz.WithInstance(ctx, instance), req)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -753,17 +753,25 @@ func setupMessageTexts(validations *[]preparation.Validation, setupMessageTexts
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) UpdateInstance(ctx context.Context, name string) (*domain.ObjectDetails, error) {
|
func (c *Commands) UpdateInstance(ctx context.Context, name string) (*domain.ObjectDetails, error) {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == "" {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "INST-092mid", "Errors.Invalid.Argument")
|
||||||
|
}
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
validation := c.prepareUpdateInstance(instanceAgg, strings.TrimSpace(name))
|
writeModel, err := getInstanceWriteModel(ctx, c.eventstore.Filter) //nolint:staticcheck
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
events, err := c.eventstore.Push(ctx, cmds...)
|
if !writeModel.State.Exists() {
|
||||||
if err != nil {
|
return nil, zerrors.ThrowNotFound(nil, "INST-nuso2m", "Errors.Instance.NotFound")
|
||||||
|
}
|
||||||
|
if writeModel.Name == name {
|
||||||
|
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||||
|
}
|
||||||
|
if err := c.pushAppendAndReduce(ctx, writeModel, instance.NewInstanceChangedEvent(ctx, &instanceAgg.Aggregate, name)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return pushedEventsToObjectDetails(events), nil
|
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) SetDefaultLanguage(ctx context.Context, defaultLanguage language.Tag) (*domain.ObjectDetails, error) {
|
func (c *Commands) SetDefaultLanguage(ctx context.Context, defaultLanguage language.Tag) (*domain.ObjectDetails, error) {
|
||||||
@@ -897,27 +905,6 @@ func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggrega
|
|||||||
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
|
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) prepareUpdateInstance(a *instance.Aggregate, name string) preparation.Validation {
|
|
||||||
return func() (preparation.CreateCommands, error) {
|
|
||||||
if name == "" {
|
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-092mid", "Errors.Invalid.Argument")
|
|
||||||
}
|
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
|
||||||
writeModel, err := getInstanceWriteModel(ctx, filter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !writeModel.State.Exists() {
|
|
||||||
return nil, zerrors.ThrowNotFound(nil, "INST-nuso2m", "Errors.Instance.NotFound")
|
|
||||||
}
|
|
||||||
if writeModel.Name == name {
|
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INST-alpxism", "Errors.Instance.NotChanged")
|
|
||||||
}
|
|
||||||
return []eventstore.Command{instance.NewInstanceChangedEvent(ctx, &a.Aggregate, name)}, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) prepareSetDefaultLanguage(a *instance.Aggregate, defaultLanguage language.Tag) preparation.Validation {
|
func (c *Commands) prepareSetDefaultLanguage(a *instance.Aggregate, defaultLanguage language.Tag) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if err := domain.LanguageIsDefined(defaultLanguage); err != nil {
|
if err := domain.LanguageIsDefined(defaultLanguage); err != nil {
|
||||||
|
|||||||
@@ -1411,7 +1411,7 @@ func TestCommandSide_setUpInstance(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommandSide_UpdateInstance(t *testing.T) {
|
func TestCommandSide_UpdateInstance(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -1430,9 +1430,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty name, invalid error",
|
name: "empty name, invalid error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
@@ -1445,8 +1443,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "instance not existing, not found error",
|
name: "instance not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -1461,8 +1458,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "instance removed, not found error",
|
name: "instance removed, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
instance.NewInstanceAddedEvent(
|
instance.NewInstanceAddedEvent(
|
||||||
@@ -1492,8 +1488,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "no changes, precondition error",
|
name: "no changes, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
instance.NewInstanceAddedEvent(
|
instance.NewInstanceAddedEvent(
|
||||||
@@ -1510,14 +1505,15 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
name: "INSTANCE",
|
name: "INSTANCE",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: zerrors.IsPreconditionFailed,
|
want: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "INSTANCE",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "instance change, ok",
|
name: "instance change, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusherWithInstanceID(
|
eventFromEventPusherWithInstanceID(
|
||||||
"INSTANCE",
|
"INSTANCE",
|
||||||
@@ -1549,7 +1545,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
got, err := r.UpdateInstance(tt.args.ctx, tt.args.name)
|
got, err := r.UpdateInstance(tt.args.ctx, tt.args.name)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ const (
|
|||||||
PermissionIAMPolicyWrite = "iam.policy.write"
|
PermissionIAMPolicyWrite = "iam.policy.write"
|
||||||
PermissionIAMPolicyDelete = "iam.policy.delete"
|
PermissionIAMPolicyDelete = "iam.policy.delete"
|
||||||
PermissionPolicyRead = "policy.read"
|
PermissionPolicyRead = "policy.read"
|
||||||
|
PermissionInstanceRead = "iam.read"
|
||||||
|
PermissionInstanceWrite = "iam.write"
|
||||||
|
PermissionSystemInstanceRead = "system.instance.read"
|
||||||
|
PermissionSystemInstanceWrite = "system.instance.write"
|
||||||
PermissionGroupCreate = "group.create"
|
PermissionGroupCreate = "group.create"
|
||||||
PermissionGroupWrite = "group.write"
|
PermissionGroupWrite = "group.write"
|
||||||
PermissionGroupRead = "group.read"
|
PermissionGroupRead = "group.read"
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import (
|
|||||||
group_v2 "github.com/zitadel/zitadel/pkg/grpc/group/v2"
|
group_v2 "github.com/zitadel/zitadel/pkg/grpc/group/v2"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/idp"
|
"github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
|
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
|
||||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
instance_v2 "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||||
|
instance_v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
internal_permission_v2 "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2"
|
internal_permission_v2 "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2"
|
||||||
internal_permission_v2beta "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
internal_permission_v2beta "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
||||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||||
@@ -88,7 +89,8 @@ type Client struct {
|
|||||||
SCIM *scim.Client
|
SCIM *scim.Client
|
||||||
Projectv2Beta project_v2beta.ProjectServiceClient
|
Projectv2Beta project_v2beta.ProjectServiceClient
|
||||||
ProjectV2 project_v2.ProjectServiceClient
|
ProjectV2 project_v2.ProjectServiceClient
|
||||||
InstanceV2Beta instance.InstanceServiceClient
|
InstanceV2Beta instance_v2beta.InstanceServiceClient //nolint:staticcheck // deprecated but still used in tests
|
||||||
|
InstanceV2 instance_v2.InstanceServiceClient
|
||||||
AppV2Beta app_v2beta.AppServiceClient
|
AppV2Beta app_v2beta.AppServiceClient
|
||||||
ApplicationV2 application.ApplicationServiceClient
|
ApplicationV2 application.ApplicationServiceClient
|
||||||
InternalPermissionv2Beta internal_permission_v2beta.InternalPermissionServiceClient
|
InternalPermissionv2Beta internal_permission_v2beta.InternalPermissionServiceClient
|
||||||
@@ -137,7 +139,8 @@ func newClient(ctx context.Context, target string) (*Client, error) {
|
|||||||
SCIM: scim.NewScimClient(target),
|
SCIM: scim.NewScimClient(target),
|
||||||
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
||||||
ProjectV2: project_v2.NewProjectServiceClient(cc),
|
ProjectV2: project_v2.NewProjectServiceClient(cc),
|
||||||
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
InstanceV2Beta: instance_v2beta.NewInstanceServiceClient(cc),
|
||||||
|
InstanceV2: instance_v2.NewInstanceServiceClient(cc),
|
||||||
AppV2Beta: app_v2beta.NewAppServiceClient(cc),
|
AppV2Beta: app_v2beta.NewAppServiceClient(cc),
|
||||||
ApplicationV2: application.NewApplicationServiceClient(cc),
|
ApplicationV2: application.NewApplicationServiceClient(cc),
|
||||||
InternalPermissionv2Beta: internal_permission_v2beta.NewInternalPermissionServiceClient(cc),
|
InternalPermissionv2Beta: internal_permission_v2beta.NewInternalPermissionServiceClient(cc),
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ service AdminService {
|
|||||||
|
|
||||||
// Get My Instance
|
// Get My Instance
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-get-instance.api.mdx) instead.
|
// Deprecated: use [instance service v2 GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-get-instance.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Returns the details about the current instance such as the name, version, domains, etc.
|
// Returns the details about the current instance such as the name, version, domains, etc.
|
||||||
rpc GetMyInstance(GetMyInstanceRequest) returns (GetMyInstanceResponse) {
|
rpc GetMyInstance(GetMyInstanceRequest) returns (GetMyInstanceResponse) {
|
||||||
@@ -313,7 +313,7 @@ service AdminService {
|
|||||||
|
|
||||||
// List Instance Domains
|
// List Instance Domains
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-get-instance.api.mdx) instead.
|
// Deprecated: use [instance service v2 GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-get-instance.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Returns a list of domains that are configured for this ZITADEL instance. These domains are the URLs where ZITADEL is running.
|
// Returns a list of domains that are configured for this ZITADEL instance. These domains are the URLs where ZITADEL is running.
|
||||||
rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
|
rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
|
||||||
@@ -333,7 +333,7 @@ service AdminService {
|
|||||||
|
|
||||||
// List Instance Trusted Domains
|
// List Instance Trusted Domains
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-list-trusted-domains.api.mdx) instead.
|
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-list-trusted-domains.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
// Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
||||||
rpc ListInstanceTrustedDomains(ListInstanceTrustedDomainsRequest) returns (ListInstanceTrustedDomainsResponse) {
|
rpc ListInstanceTrustedDomains(ListInstanceTrustedDomainsRequest) returns (ListInstanceTrustedDomainsResponse) {
|
||||||
@@ -353,7 +353,7 @@ service AdminService {
|
|||||||
|
|
||||||
// Add an Instance Trusted Domain
|
// Add an Instance Trusted Domain
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-add-trusted-domain.api.mdx) instead.
|
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-add-trusted-domain.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Add a domain to the list configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
// Add a domain to the list configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
||||||
rpc AddInstanceTrustedDomain(AddInstanceTrustedDomainRequest) returns (AddInstanceTrustedDomainResponse) {
|
rpc AddInstanceTrustedDomain(AddInstanceTrustedDomainRequest) returns (AddInstanceTrustedDomainResponse) {
|
||||||
@@ -374,7 +374,7 @@ service AdminService {
|
|||||||
|
|
||||||
// Remove an Instance Trusted Domain
|
// Remove an Instance Trusted Domain
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-remove-trusted-domain.api.mdx) instead.
|
// Deprecated: use [instance service v2 ListTrustedDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-remove-trusted-domain.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Removes a domain from the list configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
// Removes a domain from the list configured for this ZITADEL instance. These domains are trusted to be used as public hosts.
|
||||||
rpc RemoveInstanceTrustedDomain(RemoveInstanceTrustedDomainRequest) returns (RemoveInstanceTrustedDomainResponse) {
|
rpc RemoveInstanceTrustedDomain(RemoveInstanceTrustedDomainRequest) returns (RemoveInstanceTrustedDomainResponse) {
|
||||||
|
|||||||
205
proto/zitadel/instance/v2/instance.proto
Normal file
205
proto/zitadel/instance/v2/instance.proto
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "zitadel/filter/v2/filter.proto";
|
||||||
|
import "zitadel/object/v2/object.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
package zitadel.instance.v2;
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/instance/v2;instance";
|
||||||
|
|
||||||
|
message Instance {
|
||||||
|
// ID is the unique identifier of the instance.
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ChangeDate is the timestamp when the instance was last changed.
|
||||||
|
google.protobuf.Timestamp change_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// CreationDate is the timestamp when the instance was created.
|
||||||
|
google.protobuf.Timestamp creation_date = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// State is the current state of the instance.
|
||||||
|
State state = 4;
|
||||||
|
|
||||||
|
// Name is the display name of the instance.
|
||||||
|
// This can be changed by the instance administrator.
|
||||||
|
string name = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"ZITADEL\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Version of the system the instance is running on.
|
||||||
|
// This is managed by the system and cannot be changed by the instance administrator.
|
||||||
|
string version = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"1.0.0\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// CustomDomains are the domains that are assigned to the instance.
|
||||||
|
// The list includes auto-generated and manually added domains.
|
||||||
|
// They are all unique across all instances in the system.
|
||||||
|
// They're used to route requests to the correct instance.
|
||||||
|
repeated CustomDomain custom_domains = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
STATE_UNSPECIFIED = 0;
|
||||||
|
STATE_CREATING = 1;
|
||||||
|
STATE_RUNNING = 2;
|
||||||
|
STATE_STOPPING = 3;
|
||||||
|
STATE_STOPPED = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CustomDomain {
|
||||||
|
// InstanceID is the unique identifier of the instance the domain belongs to.
|
||||||
|
string instance_id = 1;
|
||||||
|
|
||||||
|
// CreationDate is the timestamp when the domain was created.
|
||||||
|
google.protobuf.Timestamp creation_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Domain is the fully qualified domain name.
|
||||||
|
// It must be unique across all instances in the system.
|
||||||
|
string domain = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"zitadel.com\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Primary states whether this domain is the primary domain of the instance.
|
||||||
|
// Each instance must have exactly one primary domain.
|
||||||
|
// The primary domain is used for various purposes and acts as fallback
|
||||||
|
// in those cases, e.g if no explicit domain is specified.
|
||||||
|
bool primary = 4;
|
||||||
|
|
||||||
|
// Generate states whether this domain was auto-generated by the system.
|
||||||
|
// Auto-generated domains follow a specific pattern and are created
|
||||||
|
// when a new instance is created.
|
||||||
|
// They cannot be deleted, but the primary domain can be changed
|
||||||
|
// to a manually added domain.
|
||||||
|
bool generated = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FieldName {
|
||||||
|
FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
FIELD_NAME_ID = 1;
|
||||||
|
FIELD_NAME_NAME = 2;
|
||||||
|
FIELD_NAME_CREATION_DATE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Filter {
|
||||||
|
oneof filter {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
// Filter for one or more specific instance IDs.
|
||||||
|
zitadel.filter.v2.InIDsFilter in_ids_filter = 1;
|
||||||
|
|
||||||
|
// Filter for instances that have at least one of the specified custom domains.
|
||||||
|
CustomDomainsFilter custom_domains_filter = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CustomDomainsFilter {
|
||||||
|
// The domains to query for. All instances that have at least one of the
|
||||||
|
// specified domains will be returned.
|
||||||
|
// A maximum of 20 domains can be specified.
|
||||||
|
repeated string domains = 1 [
|
||||||
|
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 253}}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
max_items: 20;
|
||||||
|
example: "[\"my-instace.zitadel.cloud\", \"auth.custom.com\"]";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
message CustomDomainFilter {
|
||||||
|
oneof filter {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
// Filter for a specific custom domain.
|
||||||
|
DomainFilter domain_filter = 1;
|
||||||
|
|
||||||
|
// Filter whether the domain is auto-generated.
|
||||||
|
bool generated_filter = 2;
|
||||||
|
|
||||||
|
// Filter whether the domain is the primary domain of the instance.
|
||||||
|
bool primary_filter = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DomainFilter {
|
||||||
|
// The domain to filter for.
|
||||||
|
string domain = 1 [
|
||||||
|
(validate.rules).string = {max_len: 253},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
max_length: 253;
|
||||||
|
example: "\"zitadel.com\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// The method to use for text comparison.
|
||||||
|
// If not specified, EQUALS is used.
|
||||||
|
zitadel.object.v2.TextQueryMethod method = 2 [
|
||||||
|
(validate.rules).enum.defined_only = true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DomainFieldName {
|
||||||
|
DOMAIN_FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
DOMAIN_FIELD_NAME_DOMAIN = 1;
|
||||||
|
DOMAIN_FIELD_NAME_PRIMARY = 2;
|
||||||
|
DOMAIN_FIELD_NAME_GENERATED = 3;
|
||||||
|
DOMAIN_FIELD_NAME_CREATION_DATE = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrustedDomain {
|
||||||
|
// InstanceID is the unique identifier of the instance the domain belongs to.
|
||||||
|
string instance_id = 1;
|
||||||
|
|
||||||
|
// The timestamp when the domain was created.
|
||||||
|
google.protobuf.Timestamp creation_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Domain is the fully qualified domain name.
|
||||||
|
string domain = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"zitadel.com\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrustedDomainFilter {
|
||||||
|
oneof filter {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
// Filter for a specific trusted domain.
|
||||||
|
DomainFilter domain_filter = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TrustedDomainFieldName {
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_DOMAIN = 1;
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE = 2;
|
||||||
|
}
|
||||||
508
proto/zitadel/instance/v2/instance_service.proto
Normal file
508
proto/zitadel/instance/v2/instance_service.proto
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.instance.v2;
|
||||||
|
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "zitadel/object/v2/object.proto";
|
||||||
|
import "zitadel/instance/v2/instance.proto";
|
||||||
|
import "zitadel/filter/v2/filter.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/instance/v2;instance";
|
||||||
|
|
||||||
|
// Service to manage instances and their domains.
|
||||||
|
// The service provides methods to create, update, delete and list instances and their domains.
|
||||||
|
//
|
||||||
|
// Contrary to most services in ZITADEL, the instance service allows accessing data not only from
|
||||||
|
// the current instance, but also from other instances.
|
||||||
|
service InstanceService {
|
||||||
|
|
||||||
|
// Delete Instance
|
||||||
|
//
|
||||||
|
// Deletes an instance with the given ID.
|
||||||
|
// This method requires system level permissions and cannot be called from an instance context.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.instance.delete`
|
||||||
|
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.instance.delete"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Instance
|
||||||
|
//
|
||||||
|
// Returns the instance in the current context or by its ID.
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to retrieve a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
// - `system.instance.read` (if InstanceID is set)
|
||||||
|
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Instance
|
||||||
|
//
|
||||||
|
// Updates instance's name in the current context or by its ID.
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to update a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
// - `system.instance.write` (if InstanceID is set)
|
||||||
|
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Instances
|
||||||
|
//
|
||||||
|
// Lists instances matching the given query.
|
||||||
|
// The query can be used to filter either by instance ID or domain.
|
||||||
|
// The request is paginated and returns 100 results by default.
|
||||||
|
// This method requires system level permissions and cannot be called from an instance context.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.instance.read`
|
||||||
|
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.instance.read"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Custom Domain
|
||||||
|
//
|
||||||
|
// Adds a custom domain to the instance.
|
||||||
|
// The custom domain must be unique across all instances.
|
||||||
|
// Once the domain is added, it will be used to route requests to this instance.
|
||||||
|
// This method requires system level permissions and cannot be called from an instance context.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.domain.write`
|
||||||
|
rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.domain.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Custom Domain
|
||||||
|
//
|
||||||
|
// Removes a custom domain from the instance.
|
||||||
|
// Be aware that this will stop routing requests from this domain to the instance and
|
||||||
|
// might break existing setups or integrations.
|
||||||
|
// This method requires system level permissions and cannot be called from an instance context.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.domain.write`
|
||||||
|
rpc RemoveCustomDomain(RemoveCustomDomainRequest) returns (RemoveCustomDomainResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.domain.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Custom Domains
|
||||||
|
//
|
||||||
|
// Lists custom domains of the instance.
|
||||||
|
//
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to list the domains of a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
// - `system.instance.read` (if InstanceID is set)
|
||||||
|
rpc ListCustomDomains(ListCustomDomainsRequest) returns (ListCustomDomainsResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Trusted Domain
|
||||||
|
//
|
||||||
|
// Adds a trusted domain to the instance.
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to list the domains of a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// It must be a valid domain name.
|
||||||
|
// Once the domain is added, it can be used in API responses like OIDC discovery,
|
||||||
|
// email templates, and more.
|
||||||
|
// This can be used in cases where the API is accessed through a different domain
|
||||||
|
// than the instance domain, e.g. proxy setups and custom login UIs.
|
||||||
|
// Unlike custom domain, trusted domains are not used to route requests to this instance
|
||||||
|
// and therefore do not need to be uniquely assigned to an instance.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
// - `system.instance.write` (if InstanceID is set)
|
||||||
|
rpc AddTrustedDomain(AddTrustedDomainRequest) returns (AddTrustedDomainResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Trusted Domain
|
||||||
|
//
|
||||||
|
// Removes a trusted domain from the instance.
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to list the domains of a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
// - `system.instance.write` (if InstanceID is set)
|
||||||
|
rpc RemoveTrustedDomain(RemoveTrustedDomainRequest) returns (RemoveTrustedDomainResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// List Trusted Domains
|
||||||
|
//
|
||||||
|
// Lists trusted domains of the instance.
|
||||||
|
// By default the instance will be determined by the context of the request,
|
||||||
|
// e.g. the host header.
|
||||||
|
// You can optionally pass an InstanceID to list the domains of a specific instance.
|
||||||
|
// This requires additional permissions.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
// - `system.instance.read` (if InstanceID is set)
|
||||||
|
rpc ListTrustedDomains(ListTrustedDomainsRequest) returns (ListTrustedDomainsResponse) {
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteInstanceRequest {
|
||||||
|
// InstanceID is the unique ID of the instance to be deleted.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteInstanceResponse {
|
||||||
|
// DeletionDate is the timestamp when the instance was deleted.
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInstanceRequest {
|
||||||
|
// InstanceID is the unique ID of the instance to be retrieved.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be returned.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInstanceResponse {
|
||||||
|
// The instance matching the instance ID
|
||||||
|
zitadel.instance.v2.Instance instance = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateInstanceRequest {
|
||||||
|
// InstanceID is the unique ID of the instance to be updated.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be changed.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// InstanceName is the new name of the instance to be set.
|
||||||
|
string instance_name = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"my instance\"";
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateInstanceResponse {
|
||||||
|
// ChangeDate is the timestamp when the instance was last changed.
|
||||||
|
// In case the instance was not changed during the call, the previous change date is returned.
|
||||||
|
google.protobuf.Timestamp change_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListInstancesRequest {
|
||||||
|
// Paginate through the results using a limit, offset and sorting.
|
||||||
|
zitadel.filter.v2.PaginationRequest pagination = 1;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
FieldName sorting_column = 2 [
|
||||||
|
(validate.rules).enum = {defined_only: true}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter the instances to be returned.
|
||||||
|
repeated Filter filters = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListInstancesResponse {
|
||||||
|
// The instances matching the query.
|
||||||
|
repeated Instance instances = 1;
|
||||||
|
|
||||||
|
// Contains the total number of instances matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddCustomDomainRequest {
|
||||||
|
// InstanceID is the unique ID of the instance to which the domain will be added.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
|
||||||
|
// Custom domain to add to the instance.
|
||||||
|
// Must be a valid domain name.
|
||||||
|
// Once the domain is added, it will be used to route requests to this instance.
|
||||||
|
string custom_domain = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 253},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 253;
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddCustomDomainResponse {
|
||||||
|
// CreationDate is the timestamp when the domain was added.
|
||||||
|
google.protobuf.Timestamp creation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveCustomDomainRequest {
|
||||||
|
// InstanceID is the unique ID of the instance from which the domain will be removed.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
|
||||||
|
// CustomDomain is the the domain to remove from the instance.
|
||||||
|
string custom_domain = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 253},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 253;
|
||||||
|
},
|
||||||
|
(google.api.field_behavior) = REQUIRED
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveCustomDomainResponse {
|
||||||
|
// DeletionDate is the timestamp when the domain was removed.
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCustomDomainsRequest {
|
||||||
|
// InstanceID is the unique ID of the instance whose domains will be listed.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be used.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Paginate and sort the results using a limit, offset and sorting.
|
||||||
|
zitadel.filter.v2.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
DomainFieldName sorting_column = 3;
|
||||||
|
|
||||||
|
// Filter the domains to be returned.
|
||||||
|
repeated CustomDomainFilter filters = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCustomDomainsResponse {
|
||||||
|
// The list of custom domains matching the query.
|
||||||
|
repeated CustomDomain domains = 1;
|
||||||
|
|
||||||
|
// Contains the total number of domains matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddTrustedDomainRequest {
|
||||||
|
// InstanceID is the unique ID of the instance to which the trusted domain will be added.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be used.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Trusted domain to be added to the instance.
|
||||||
|
// Must be a valid domain name.
|
||||||
|
// Once the domain is added, it can be used in API responses like OIDC discovery,
|
||||||
|
// email templates, and more.
|
||||||
|
// This can be used in cases where the API is accessed through a different domain
|
||||||
|
// than the instance domain, e.g. proxy setups and custom login UIs.
|
||||||
|
// Unlike custom domains, trusted domains are not used to route requests to this instance
|
||||||
|
// and therefore do not need to be uniquely assigned to an instance.
|
||||||
|
string trusted_domain = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 253},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"login.example.com\"";
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 253;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddTrustedDomainResponse {
|
||||||
|
// CreationDate is the timestamp when the trusted domain was added.
|
||||||
|
google.protobuf.Timestamp creation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveTrustedDomainRequest {
|
||||||
|
// InstanceID is the unique ID of the instance from which the trusted domain will be removed.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be used.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// The trusted domain to remove from the instance.
|
||||||
|
string trusted_domain = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 253},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"login.example.com\"";
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 253;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveTrustedDomainResponse {
|
||||||
|
// DeletionDate is the timestamp when the trusted domain was removed.
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTrustedDomainsRequest {
|
||||||
|
// InstanceID is the unique ID of the instance whose trusted domains will be listed.
|
||||||
|
// If not set, the instance in the current context (e.g. identified by the host header) will be used.
|
||||||
|
// If an ID is set, the caller must have additional permissions.
|
||||||
|
string instance_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"222430354126975533\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Paginate and sort the results using a limit, offset and sorting.
|
||||||
|
zitadel.filter.v2.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
TrustedDomainFieldName sorting_column = 3;
|
||||||
|
|
||||||
|
// Filter the domains to be returned.
|
||||||
|
repeated TrustedDomainFilter filters = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTrustedDomainsResponse {
|
||||||
|
// The list of trusted domains matching the query.
|
||||||
|
repeated TrustedDomain trusted_domain = 1;
|
||||||
|
|
||||||
|
// Contains the total number of domains matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||||
|
}
|
||||||
@@ -105,16 +105,21 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
|||||||
|
|
||||||
// Service to manage instances and their domains.
|
// Service to manage instances and their domains.
|
||||||
// The service provides methods to create, update, delete and list instances and their domains.
|
// The service provides methods to create, update, delete and list instances and their domains.
|
||||||
|
//
|
||||||
|
// Deprecated: use instance service v2 instead. This service will be removed in the next major version of ZITADEL.
|
||||||
service InstanceService {
|
service InstanceService {
|
||||||
|
|
||||||
// Delete Instance
|
// Delete Instance
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Deletes an instance with the given ID.
|
// Deletes an instance with the given ID.
|
||||||
//
|
//
|
||||||
// Required permissions:
|
// Required permissions:
|
||||||
// - `system.instance.delete`
|
// - `system.instance.delete`
|
||||||
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse) {
|
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -136,6 +141,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Get Instance
|
// Get Instance
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Returns the instance in the current context.
|
// Returns the instance in the current context.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -144,6 +151,7 @@ service InstanceService {
|
|||||||
// - `iam.read`
|
// - `iam.read`
|
||||||
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -165,6 +173,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Update Instance
|
// Update Instance
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Updates instance in context with the given name.
|
// Updates instance in context with the given name.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -173,6 +183,7 @@ service InstanceService {
|
|||||||
// - `iam.write`
|
// - `iam.write`
|
||||||
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -195,6 +206,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// List Instances
|
// List Instances
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Lists instances matching the given query.
|
// Lists instances matching the given query.
|
||||||
// The query can be used to filter either by instance ID or domain.
|
// The query can be used to filter either by instance ID or domain.
|
||||||
// The request is paginated and returns 100 results by default.
|
// The request is paginated and returns 100 results by default.
|
||||||
@@ -203,6 +216,7 @@ service InstanceService {
|
|||||||
// - `system.instance.read`
|
// - `system.instance.read`
|
||||||
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -225,6 +239,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Add Custom Domain
|
// Add Custom Domain
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Adds a custom domain to the instance in context.
|
// Adds a custom domain to the instance in context.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future
|
// The instance_id in the input message will be used in the future
|
||||||
@@ -233,6 +249,7 @@ service InstanceService {
|
|||||||
// - `system.domain.write`
|
// - `system.domain.write`
|
||||||
rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) {
|
rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -255,6 +272,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Remove Custom Domain
|
// Remove Custom Domain
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Removes a custom domain from the instance.
|
// Removes a custom domain from the instance.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -263,6 +282,7 @@ service InstanceService {
|
|||||||
// - `system.domain.write`
|
// - `system.domain.write`
|
||||||
rpc RemoveCustomDomain(RemoveCustomDomainRequest) returns (RemoveCustomDomainResponse) {
|
rpc RemoveCustomDomain(RemoveCustomDomainRequest) returns (RemoveCustomDomainResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -284,6 +304,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// List Custom Domains
|
// List Custom Domains
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Lists custom domains of the instance.
|
// Lists custom domains of the instance.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -292,6 +314,7 @@ service InstanceService {
|
|||||||
// - `iam.read`
|
// - `iam.read`
|
||||||
rpc ListCustomDomains(ListCustomDomainsRequest) returns (ListCustomDomainsResponse) {
|
rpc ListCustomDomains(ListCustomDomainsRequest) returns (ListCustomDomainsResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -314,6 +337,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Add Trusted Domain
|
// Add Trusted Domain
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Adds a trusted domain to the instance.
|
// Adds a trusted domain to the instance.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -322,6 +347,7 @@ service InstanceService {
|
|||||||
// - `iam.write`
|
// - `iam.write`
|
||||||
rpc AddTrustedDomain(AddTrustedDomainRequest) returns (AddTrustedDomainResponse) {
|
rpc AddTrustedDomain(AddTrustedDomainRequest) returns (AddTrustedDomainResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -344,6 +370,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// Remove Trusted Domain
|
// Remove Trusted Domain
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Removes a trusted domain from the instance.
|
// Removes a trusted domain from the instance.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -352,6 +380,7 @@ service InstanceService {
|
|||||||
// - `iam.write`
|
// - `iam.write`
|
||||||
rpc RemoveTrustedDomain(RemoveTrustedDomainRequest) returns (RemoveTrustedDomainResponse) {
|
rpc RemoveTrustedDomain(RemoveTrustedDomainRequest) returns (RemoveTrustedDomainResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
@@ -374,6 +403,8 @@ service InstanceService {
|
|||||||
|
|
||||||
// List Trusted Domains
|
// List Trusted Domains
|
||||||
//
|
//
|
||||||
|
// Deprecated: please move to the corresponding endpoint under instance service v2. This endpoint will be removed with the next major version of ZITADEL.
|
||||||
|
//
|
||||||
// Lists trusted domains of the instance.
|
// Lists trusted domains of the instance.
|
||||||
//
|
//
|
||||||
// The instance_id in the input message will be used in the future.
|
// The instance_id in the input message will be used in the future.
|
||||||
@@ -382,6 +413,7 @@ service InstanceService {
|
|||||||
// - `iam.read`
|
// - `iam.read`
|
||||||
rpc ListTrustedDomains(ListTrustedDomainsRequest) returns (ListTrustedDomainsResponse) {
|
rpc ListTrustedDomains(ListTrustedDomainsRequest) returns (ListTrustedDomainsResponse) {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
responses: {
|
responses: {
|
||||||
key: "200";
|
key: "200";
|
||||||
value: {
|
value: {
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Returns a list of ZITADEL instances
|
// Returns a list of ZITADEL instances
|
||||||
//
|
//
|
||||||
// Deprecated: Use [ListInstances](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-list-instances.api.mdx) instead to list instances
|
// Deprecated: Use [ListInstances](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-list-instances.api.mdx) instead to list instances
|
||||||
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/instances/_search"
|
post: "/instances/_search"
|
||||||
@@ -136,7 +136,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Returns the detail of an instance
|
// Returns the detail of an instance
|
||||||
//
|
//
|
||||||
// Deprecated: Use [GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-get-instance.api.mdx) instead to get the details of the instance in context
|
// Deprecated: Use [GetInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-get-instance.api.mdx) instead to get the details of the instance in context
|
||||||
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/instances/{instance_id}";
|
get: "/instances/{instance_id}";
|
||||||
@@ -171,7 +171,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Updates name of an existing instance
|
// Updates name of an existing instance
|
||||||
//
|
//
|
||||||
// Deprecated: Use [UpdateInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-update-instance.api.mdx) instead to update the name of the instance in context
|
// Deprecated: Use [UpdateInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-update-instance.api.mdx) instead to update the name of the instance in context
|
||||||
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
put: "/instances/{instance_id}"
|
put: "/instances/{instance_id}"
|
||||||
@@ -203,7 +203,7 @@ service SystemService {
|
|||||||
// Removes an instance
|
// Removes an instance
|
||||||
// This might take some time
|
// This might take some time
|
||||||
//
|
//
|
||||||
// Deprecated: Use [DeleteInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-delete-instance.api.mdx) instead to delete an instance
|
// Deprecated: Use [DeleteInstance](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-delete-instance.api.mdx) instead to delete an instance
|
||||||
rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
|
rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/instances/{instance_id}"
|
delete: "/instances/{instance_id}"
|
||||||
@@ -234,7 +234,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Checks if a domain exists
|
// Checks if a domain exists
|
||||||
//
|
//
|
||||||
// Deprecated: Use [ListCustomDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-list-custom-domains.api.mdx) instead to check existence of an instance
|
// Deprecated: Use [ListCustomDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-list-custom-domains.api.mdx) instead to check existence of an instance
|
||||||
rpc ExistsDomain(ExistsDomainRequest) returns (ExistsDomainResponse) {
|
rpc ExistsDomain(ExistsDomainRequest) returns (ExistsDomainResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/domains/{domain}/_exists";
|
post: "/domains/{domain}/_exists";
|
||||||
@@ -252,7 +252,7 @@ service SystemService {
|
|||||||
|
|
||||||
// List Domains
|
// List Domains
|
||||||
//
|
//
|
||||||
// Deprecated: use [instance service v2 ListCustomDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-list-custom-domains.api.mdx) instead.
|
// Deprecated: use [instance service v2 ListCustomDomains](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-list-custom-domains.api.mdx) instead.
|
||||||
//
|
//
|
||||||
// Returns the custom domains of an instance.
|
// Returns the custom domains of an instance.
|
||||||
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
|
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
|
||||||
@@ -272,7 +272,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Adds a domain to an instance
|
// Adds a domain to an instance
|
||||||
//
|
//
|
||||||
// Deprecated: Use [AddCustomDomain](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-add-custom-domain.api.mdx) instead to add a custom domain to the instance in context
|
// Deprecated: Use [AddCustomDomain](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-add-custom-domain.api.mdx) instead to add a custom domain to the instance in context
|
||||||
rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
|
rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/instances/{instance_id}/domains";
|
post: "/instances/{instance_id}/domains";
|
||||||
@@ -290,7 +290,7 @@ service SystemService {
|
|||||||
|
|
||||||
// Removes the domain of an instance
|
// Removes the domain of an instance
|
||||||
//
|
//
|
||||||
// Deprecated: Use [RemoveDomain](apis/resources/instance_service_v2/zitadel-instance-v-2-beta-instance-service-remove-custom-domain.api.mdx) instead to remove a custom domain from the instance in context
|
// Deprecated: Use [RemoveDomain](apis/resources/instance_service_v2/zitadel-instance-v-2-instance-service-remove-custom-domain.api.mdx) instead to remove a custom domain from the instance in context
|
||||||
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
|
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/instances/{instance_id}/domains/{domain}";
|
delete: "/instances/{instance_id}/domains/{domain}";
|
||||||
|
|||||||
Reference in New Issue
Block a user