mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:27:32 +00:00
feat: instance requests implementation for resource API (#9830)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved These changes introduce resource-based API endpoints for managing instances and custom domains. There are 4 types of changes: - Endpoint implementation: consisting of the protobuf interface and the implementation of the endpoint. E.g:606439a172
- (Integration) Tests: testing the implemented endpoint. E.g:cdfe1f0372
- Fixes: Bugs found during development that are being fixed. E.g:acbbeedd32
- Miscellaneous: code needed to put everything together or that doesn't fit any of the above categories. E.g:529df92abc
or6802cb5468
# How the Problems Are Solved _Ticked checkboxes indicate that the functionality is complete_ - [x] Instance - [x] Create endpoint - [x] Create endpoint tests - [x] Update endpoint - [x] Update endpoint tests - [x] Get endpoint - [x] Get endpoint tests - [x] Delete endpoint - [x] Delete endpoint tests - [x] Custom Domains - [x] Add custom domain - [x] Add custom domain tests - [x] Remove custom domain - [x] Remove custom domain tests - [x] List custom domains - [x] List custom domains tests - [x] Trusted Domains - [x] Add trusted domain - [x] Add trusted domain tests - [x] Remove trusted domain - [x] Remove trusted domain tests - [x] List trusted domains - [x] List trusted domains tests # Additional Changes When looking for instances (through the `ListInstances` endpoint) matching a given query, if you ask for the results to be order by a specific column, the query will fail due to a syntax error. This is fixed inacbbeedd32
. Further explanation can be found in the commit message # Additional Context - Relates to #9452 - CreateInstance has been excluded: https://github.com/zitadel/zitadel/issues/9930 - Permission checks / instance retrieval (middleware) needs to be changed to allow context based permission checks (https://github.com/zitadel/zitadel/issues/9929), required for ListInstances --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -40,6 +40,7 @@ import (
|
|||||||
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
||||||
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
||||||
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"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||||
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
|
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
|
||||||
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
|
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
|
||||||
@@ -442,6 +443,9 @@ 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 {
|
||||||
|
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 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern
|
|||||||
|
|
||||||
To add a new site to the already existing structure simply save the `md` file into the corresponding folder and append the sites id int the file `sidebars.js`.
|
To add a new site to the already existing structure simply save the `md` file into the corresponding folder and append the sites id int the file `sidebars.js`.
|
||||||
|
|
||||||
|
If you are introducing new APIs (gRPC), you need to add a new entry to `docusaurus.config.js` under the `plugins` section.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install dependencies with
|
Install dependencies with
|
||||||
|
@@ -364,6 +364,14 @@ module.exports = {
|
|||||||
categoryLinkSource: "auto",
|
categoryLinkSource: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
instance_v2: {
|
||||||
|
specPath: ".artifacts/openapi/zitadel/instance/v2beta/instance_service.swagger.json",
|
||||||
|
outputDir: "docs/apis/resources/instance_service_v2",
|
||||||
|
sidebarOptions: {
|
||||||
|
groupPathsBy: "tag",
|
||||||
|
categoryLinkSource: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@@ -13,6 +13,7 @@ const sidebar_api_org_service_v2 = require("./docs/apis/resources/org_service_v2
|
|||||||
const sidebar_api_idp_service_v2 = require("./docs/apis/resources/idp_service_v2/sidebar.ts").default
|
const sidebar_api_idp_service_v2 = require("./docs/apis/resources/idp_service_v2/sidebar.ts").default
|
||||||
const sidebar_api_actions_v2 = require("./docs/apis/resources/action_service_v2/sidebar.ts").default
|
const sidebar_api_actions_v2 = require("./docs/apis/resources/action_service_v2/sidebar.ts").default
|
||||||
const sidebar_api_webkey_service_v2 = require("./docs/apis/resources/webkey_service_v2/sidebar.ts").default
|
const sidebar_api_webkey_service_v2 = require("./docs/apis/resources/webkey_service_v2/sidebar.ts").default
|
||||||
|
const sidebar_api_instance_service_v2 = require("./docs/apis/resources/instance_service_v2/sidebar.ts").default
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
guides: [
|
guides: [
|
||||||
@@ -840,6 +841,24 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
items: sidebar_api_actions_v2,
|
items: sidebar_api_actions_v2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Instance (Beta)",
|
||||||
|
link: {
|
||||||
|
type: "generated-index",
|
||||||
|
title: "Instance Service API (Beta)",
|
||||||
|
slug: "/apis/resources/instance_service_v2",
|
||||||
|
description:
|
||||||
|
"This API is intended to manage instances, custom domains and trusted domains in ZITADEL.\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" +
|
||||||
|
"The whole functionality related to domains (custom and trusted) has been moved under this instance API."
|
||||||
|
,
|
||||||
|
},
|
||||||
|
items: sidebar_api_instance_service_v2,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
246
internal/api/grpc/instance/v2beta/converter.go
Normal file
246
internal/api/grpc/instance/v2beta/converter.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/cmd/build"
|
||||||
|
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InstancesToPb(instances []*query.Instance) []*instance.Instance {
|
||||||
|
list := []*instance.Instance{}
|
||||||
|
for _, instance := range instances {
|
||||||
|
list = append(list, ToProtoObject(instance))
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProtoObject(inst *query.Instance) *instance.Instance {
|
||||||
|
return &instance.Instance{
|
||||||
|
Id: inst.ID,
|
||||||
|
Name: inst.Name,
|
||||||
|
Domains: DomainsToPb(inst.Domains),
|
||||||
|
Version: build.Version(),
|
||||||
|
ChangeDate: timestamppb.New(inst.ChangeDate),
|
||||||
|
CreationDate: timestamppb.New(inst.CreationDate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DomainsToPb(domains []*query.InstanceDomain) []*instance.Domain {
|
||||||
|
d := []*instance.Domain{}
|
||||||
|
for _, dm := range domains {
|
||||||
|
pbDomain := DomainToPb(dm)
|
||||||
|
d = append(d, pbDomain)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func DomainToPb(d *query.InstanceDomain) *instance.Domain {
|
||||||
|
return &instance.Domain{
|
||||||
|
Domain: d.Domain,
|
||||||
|
Primary: d.IsPrimary,
|
||||||
|
Generated: d.IsGenerated,
|
||||||
|
InstanceId: d.InstanceID,
|
||||||
|
CreationDate: timestamppb.New(d.CreationDate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListInstancesRequestToModel(req *instance.ListInstancesRequest, sysDefaults systemdefaults.SystemDefaults) (*query.InstanceSearchQueries, error) {
|
||||||
|
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
queries, err := instanceQueriesToModel(req.GetQueries())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &query.InstanceSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: fieldNameToInstanceColumn(req.GetSortingColumn()),
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldNameToInstanceColumn(fieldName instance.FieldName) query.Column {
|
||||||
|
switch fieldName {
|
||||||
|
case instance.FieldName_FIELD_NAME_ID:
|
||||||
|
return query.InstanceColumnID
|
||||||
|
case instance.FieldName_FIELD_NAME_NAME:
|
||||||
|
return query.InstanceColumnName
|
||||||
|
case instance.FieldName_FIELD_NAME_CREATION_DATE:
|
||||||
|
return query.InstanceColumnCreationDate
|
||||||
|
case instance.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return query.Column{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func instanceQueriesToModel(queries []*instance.Query) (_ []query.SearchQuery, err error) {
|
||||||
|
q := []query.SearchQuery{}
|
||||||
|
for _, query := range queries {
|
||||||
|
model, err := instanceQueryToModel(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
q = append(q, model)
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func instanceQueryToModel(searchQuery *instance.Query) (query.SearchQuery, error) {
|
||||||
|
switch q := searchQuery.GetQuery().(type) {
|
||||||
|
case *instance.Query_IdQuery:
|
||||||
|
return query.NewInstanceIDsListSearchQuery(q.IdQuery.GetIds()...)
|
||||||
|
case *instance.Query_DomainQuery:
|
||||||
|
return query.NewInstanceDomainsListSearchQuery(q.DomainQuery.GetDomains()...)
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListCustomDomainsRequestToModel(req *instance.ListCustomDomainsRequest, defaults systemdefaults.SystemDefaults) (*query.InstanceDomainSearchQueries, error) {
|
||||||
|
offset, limit, asc, err := filter.PaginationPbToQuery(defaults, req.GetPagination())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
queries, err := domainQueriesToModel(req.GetQueries())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &query.InstanceDomainSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: fieldNameToInstanceDomainColumn(req.GetSortingColumn()),
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldNameToInstanceDomainColumn(fieldName instance.DomainFieldName) query.Column {
|
||||||
|
switch fieldName {
|
||||||
|
case instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||||
|
return query.InstanceDomainDomainCol
|
||||||
|
case instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||||
|
return query.InstanceDomainIsGeneratedCol
|
||||||
|
case instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
|
||||||
|
return query.InstanceDomainIsPrimaryCol
|
||||||
|
case instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||||
|
return query.InstanceDomainCreationDateCol
|
||||||
|
case instance.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return query.Column{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainQueriesToModel(queries []*instance.DomainSearchQuery) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(queries))
|
||||||
|
for i, query := range queries {
|
||||||
|
q[i], err = domainQueryToModel(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainQueryToModel(searchQuery *instance.DomainSearchQuery) (query.SearchQuery, error) {
|
||||||
|
switch q := searchQuery.GetQuery().(type) {
|
||||||
|
case *instance.DomainSearchQuery_DomainQuery:
|
||||||
|
return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.GetMethod()), q.DomainQuery.GetDomain())
|
||||||
|
case *instance.DomainSearchQuery_GeneratedQuery:
|
||||||
|
return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedQuery.GetGenerated())
|
||||||
|
case *instance.DomainSearchQuery_PrimaryQuery:
|
||||||
|
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.GetPrimary())
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListTrustedDomainsRequestToModel(req *instance.ListTrustedDomainsRequest, defaults systemdefaults.SystemDefaults) (*query.InstanceTrustedDomainSearchQueries, error) {
|
||||||
|
offset, limit, asc, err := filter.PaginationPbToQuery(defaults, req.GetPagination())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
queries, err := trustedDomainQueriesToModel(req.GetQueries())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &query.InstanceTrustedDomainSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: fieldNameToInstanceTrustedDomainColumn(req.GetSortingColumn()),
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedDomainQueriesToModel(queries []*instance.TrustedDomainSearchQuery) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(queries))
|
||||||
|
for i, query := range queries {
|
||||||
|
q[i], err = trustedDomainQueryToModel(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedDomainQueryToModel(searchQuery *instance.TrustedDomainSearchQuery) (query.SearchQuery, error) {
|
||||||
|
switch q := searchQuery.GetQuery().(type) {
|
||||||
|
case *instance.TrustedDomainSearchQuery_DomainQuery:
|
||||||
|
return query.NewInstanceTrustedDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.GetMethod()), q.DomainQuery.GetDomain())
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedDomainsToPb(domains []*query.InstanceTrustedDomain) []*instance.TrustedDomain {
|
||||||
|
d := make([]*instance.TrustedDomain, len(domains))
|
||||||
|
for i, domain := range domains {
|
||||||
|
d[i] = trustedDomainToPb(domain)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedDomainToPb(d *query.InstanceTrustedDomain) *instance.TrustedDomain {
|
||||||
|
return &instance.TrustedDomain{
|
||||||
|
Domain: d.Domain,
|
||||||
|
InstanceId: d.InstanceID,
|
||||||
|
CreationDate: timestamppb.New(d.CreationDate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldNameToInstanceTrustedDomainColumn(fieldName instance.TrustedDomainFieldName) query.Column {
|
||||||
|
switch fieldName {
|
||||||
|
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN:
|
||||||
|
return query.InstanceTrustedDomainDomainCol
|
||||||
|
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||||
|
return query.InstanceTrustedDomainCreationDateCol
|
||||||
|
case instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return query.Column{}
|
||||||
|
}
|
||||||
|
}
|
390
internal/api/grpc/instance/v2beta/converter_test.go
Normal file
390
internal/api/grpc/instance/v2beta/converter_test.go
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/cmd/build"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_InstancesToPb(t *testing.T) {
|
||||||
|
instances := []*query.Instance{
|
||||||
|
{
|
||||||
|
ID: "instance1",
|
||||||
|
Name: "Instance One",
|
||||||
|
Domains: []*query.InstanceDomain{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
IsPrimary: true,
|
||||||
|
IsGenerated: false,
|
||||||
|
Sequence: 1,
|
||||||
|
CreationDate: time.Unix(123, 0),
|
||||||
|
ChangeDate: time.Unix(124, 0),
|
||||||
|
InstanceID: "instance1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sequence: 1,
|
||||||
|
CreationDate: time.Unix(123, 0),
|
||||||
|
ChangeDate: time.Unix(124, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []*instance.Instance{
|
||||||
|
{
|
||||||
|
Id: "instance1",
|
||||||
|
Name: "Instance One",
|
||||||
|
Domains: []*instance.Domain{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Primary: true,
|
||||||
|
Generated: false,
|
||||||
|
InstanceId: "instance1",
|
||||||
|
CreationDate: ×tamppb.Timestamp{Seconds: 123},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Version: build.Version(),
|
||||||
|
ChangeDate: ×tamppb.Timestamp{Seconds: 124},
|
||||||
|
CreationDate: ×tamppb.Timestamp{Seconds: 123},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := InstancesToPb(instances)
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ListInstancesRequestToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1", "instance2")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.ListInstancesRequest
|
||||||
|
maxQueryLimit uint64
|
||||||
|
expectedResult *query.InstanceSearchQueries
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when query limit exceeds max query limit should return invalid argument error",
|
||||||
|
maxQueryLimit: 1,
|
||||||
|
inputRequest: &instance.ListInstancesRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||||
|
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
|
||||||
|
},
|
||||||
|
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request should return instance search query model",
|
||||||
|
inputRequest: &instance.ListInstancesRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
|
||||||
|
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
|
||||||
|
},
|
||||||
|
expectedResult: &query.InstanceSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 10,
|
||||||
|
Asc: true,
|
||||||
|
SortingColumn: query.InstanceColumnID,
|
||||||
|
},
|
||||||
|
Queries: []query.SearchQuery{searchInstanceByID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tc.maxQueryLimit}
|
||||||
|
|
||||||
|
got, err := ListInstancesRequestToModel(tc.inputRequest, sysDefaults)
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedResult, got)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_fieldNameToInstanceColumn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fieldName instance.FieldName
|
||||||
|
want query.Column
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ID field",
|
||||||
|
fieldName: instance.FieldName_FIELD_NAME_ID,
|
||||||
|
want: query.InstanceColumnID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Name field",
|
||||||
|
fieldName: instance.FieldName_FIELD_NAME_NAME,
|
||||||
|
want: query.InstanceColumnName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Creation Date field",
|
||||||
|
fieldName: instance.FieldName_FIELD_NAME_CREATION_DATE,
|
||||||
|
want: query.InstanceColumnCreationDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown field",
|
||||||
|
fieldName: instance.FieldName(99),
|
||||||
|
want: query.Column{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := fieldNameToInstanceColumn(tt.fieldName)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_instanceQueryToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
searchInstanceByDomain, err := query.NewInstanceDomainsListSearchQuery("example.com")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
searchQuery *instance.Query
|
||||||
|
want query.SearchQuery
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ID Query",
|
||||||
|
searchQuery: &instance.Query{
|
||||||
|
Query: &instance.Query_IdQuery{
|
||||||
|
IdQuery: &instance.IdsQuery{
|
||||||
|
Ids: []string{"instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: searchInstanceByID,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Domain Query",
|
||||||
|
searchQuery: &instance.Query{
|
||||||
|
Query: &instance.Query_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainsQuery{
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: searchInstanceByDomain,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Query",
|
||||||
|
searchQuery: &instance.Query{
|
||||||
|
Query: nil,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got, err := instanceQueryToModel(tt.searchQuery)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ListCustomDomainsRequestToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
querySearchRes, err := query.NewInstanceDomainDomainSearchQuery(query.TextEquals, "example.com")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
queryGeneratedRes, err := query.NewInstanceDomainGeneratedSearchQuery(false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputRequest *instance.ListCustomDomainsRequest
|
||||||
|
maxQueryLimit uint64
|
||||||
|
expectedResult *query.InstanceDomainSearchQueries
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "when query limit exceeds max query limit should return invalid argument error",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
|
||||||
|
Queries: []*instance.DomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{
|
||||||
|
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 1,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when valid request should return domain search query model",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||||
|
Queries: []*instance.DomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Query: &instance.DomainSearchQuery_GeneratedQuery{
|
||||||
|
GeneratedQuery: &instance.DomainGeneratedQuery{Generated: false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 100,
|
||||||
|
expectedResult: &query.InstanceDomainSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceDomainIsPrimaryCol},
|
||||||
|
Queries: []query.SearchQuery{
|
||||||
|
querySearchRes,
|
||||||
|
queryGeneratedRes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when invalid query should return error",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
|
||||||
|
Queries: []*instance.DomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 100,
|
||||||
|
expectedResult: nil,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
|
||||||
|
|
||||||
|
got, err := ListCustomDomainsRequestToModel(tt.inputRequest, sysDefaults)
|
||||||
|
assert.Equal(t, tt.expectedError, err)
|
||||||
|
assert.Equal(t, tt.expectedResult, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ListTrustedDomainsRequestToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
querySearchRes, err := query.NewInstanceTrustedDomainDomainSearchQuery(query.TextEquals, "example.com")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputRequest *instance.ListTrustedDomainsRequest
|
||||||
|
maxQueryLimit uint64
|
||||||
|
expectedResult *query.InstanceTrustedDomainSearchQueries
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "when query limit exceeds max query limit should return invalid argument error",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
|
||||||
|
Queries: []*instance.TrustedDomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{
|
||||||
|
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 1,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when valid request should return domain search query model",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||||
|
Queries: []*instance.TrustedDomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 100,
|
||||||
|
expectedResult: &query.InstanceTrustedDomainSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceTrustedDomainCreationDateCol},
|
||||||
|
Queries: []query.SearchQuery{querySearchRes},
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when invalid query should return error",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
|
||||||
|
Queries: []*instance.TrustedDomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxQueryLimit: 100,
|
||||||
|
expectedResult: nil,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
|
||||||
|
|
||||||
|
got, err := ListTrustedDomainsRequestToModel(tt.inputRequest, sysDefaults)
|
||||||
|
assert.Equal(t, tt.expectedError, err)
|
||||||
|
assert.Equal(t, tt.expectedResult, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
50
internal/api/grpc/instance/v2beta/domain.go
Normal file
50
internal/api/grpc/instance/v2beta/domain.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) AddCustomDomain(ctx context.Context, req *instance.AddCustomDomainRequest) (*instance.AddCustomDomainResponse, error) {
|
||||||
|
details, err := s.command.AddInstanceDomain(ctx, req.GetDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &instance.AddCustomDomainResponse{
|
||||||
|
CreationDate: timestamppb.New(details.CreationDate),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveCustomDomain(ctx context.Context, req *instance.RemoveCustomDomainRequest) (*instance.RemoveCustomDomainResponse, error) {
|
||||||
|
details, err := s.command.RemoveInstanceDomain(ctx, req.GetDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &instance.RemoveCustomDomainResponse{
|
||||||
|
DeletionDate: timestamppb.New(details.EventDate),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddTrustedDomain(ctx context.Context, req *instance.AddTrustedDomainRequest) (*instance.AddTrustedDomainResponse, error) {
|
||||||
|
details, err := s.command.AddTrustedDomain(ctx, req.GetDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &instance.AddTrustedDomainResponse{
|
||||||
|
CreationDate: timestamppb.New(details.CreationDate),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveTrustedDomain(ctx context.Context, req *instance.RemoveTrustedDomainRequest) (*instance.RemoveTrustedDomainResponse, error) {
|
||||||
|
details, err := s.command.RemoveTrustedDomain(ctx, req.GetDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.RemoveTrustedDomainResponse{
|
||||||
|
DeletionDate: timestamppb.New(details.EventDate),
|
||||||
|
}, nil
|
||||||
|
}
|
32
internal/api/grpc/instance/v2beta/instance.go
Normal file
32
internal/api/grpc/instance/v2beta/instance.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) DeleteInstance(ctx context.Context, request *instance.DeleteInstanceRequest) (*instance.DeleteInstanceResponse, error) {
|
||||||
|
obj, err := s.command.RemoveInstance(ctx, request.GetInstanceId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.DeleteInstanceResponse{
|
||||||
|
DeletionDate: timestamppb.New(obj.EventDate),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateInstance(ctx context.Context, request *instance.UpdateInstanceRequest) (*instance.UpdateInstanceResponse, error) {
|
||||||
|
obj, err := s.command.UpdateInstance(ctx, request.GetInstanceName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.UpdateInstanceResponse{
|
||||||
|
ChangeDate: timestamppb.New(obj.EventDate),
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -0,0 +1,350 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddCustomDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputContext context.Context
|
||||||
|
inputRequest *instance.AddCustomDomainRequest
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.AddCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: gofakeit.DomainName(),
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.AddCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: gofakeit.DomainName(),
|
||||||
|
},
|
||||||
|
inputContext: iamOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when invalid domain should return invalid argument error",
|
||||||
|
inputRequest: &instance.AddCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " ",
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedErrorCode: codes.InvalidArgument,
|
||||||
|
expectedErrorMsg: "Errors.Invalid.Argument (INST-28nlD)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request should return successful response",
|
||||||
|
inputRequest: &instance.AddCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " " + gofakeit.DomainName(),
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotEmpty(t, res.GetCreationDate())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveCustomDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
iamOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
|
customDomain := gofakeit.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputContext context.Context
|
||||||
|
inputRequest *instance.RemoveCustomDomainRequest
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "custom1",
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "custom1",
|
||||||
|
},
|
||||||
|
inputContext: iamOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when invalid domain should return invalid argument error",
|
||||||
|
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " ",
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedErrorCode: codes.InvalidArgument,
|
||||||
|
expectedErrorMsg: "Errors.Invalid.Argument (INST-39nls)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request should return successful response",
|
||||||
|
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " " + customDomain,
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotEmpty(t, res.GetDeletionDate())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTrustedDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputContext context.Context
|
||||||
|
inputRequest *instance.AddTrustedDomainRequest
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.AddTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "trusted1",
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.AddTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "trusted1",
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when invalid domain should return invalid argument error",
|
||||||
|
inputRequest: &instance.AddTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " ",
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedErrorCode: codes.InvalidArgument,
|
||||||
|
expectedErrorMsg: "Errors.Invalid.Argument (COMMA-Stk21)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request should return successful response",
|
||||||
|
inputRequest: &instance.AddTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " " + gofakeit.DomainName(),
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotEmpty(t, res.GetCreationDate())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveTrustedDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
trustedDomain := gofakeit.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputContext context.Context
|
||||||
|
inputRequest *instance.RemoveTrustedDomainRequest
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "trusted1",
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: "trusted1",
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request should return successful response",
|
||||||
|
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Domain: " " + trustedDomain,
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.GetDeletionDate())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,162 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeleteInstace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.DeleteInstanceRequest
|
||||||
|
inputContext context.Context
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
expectedInstanceID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.DeleteInstanceRequest{
|
||||||
|
InstanceId: " ",
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when invalid input should return invalid argument error",
|
||||||
|
inputRequest: &instance.DeleteInstanceRequest{
|
||||||
|
InstanceId: inst.ID() + "invalid",
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedErrorCode: codes.NotFound,
|
||||||
|
expectedErrorMsg: "Instance not found (COMMA-AE3GS)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when delete succeeds should return deletion date",
|
||||||
|
inputRequest: &instance.DeleteInstanceRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedInstanceID: inst.ID(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.GetDeletionDate())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateInstace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.UpdateInstanceRequest
|
||||||
|
inputContext context.Context
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
expectedNewName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.UpdateInstanceRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
InstanceName: " ",
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.UpdateInstanceRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
InstanceName: " ",
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when update succeeds should change instance name",
|
||||||
|
inputRequest: &instance.UpdateInstanceRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
InstanceName: "an-updated-name",
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedNewName: "an-updated-name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.UpdateInstance(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
|
||||||
|
require.NotNil(t, res)
|
||||||
|
assert.NotEmpty(t, res.GetChangeDate())
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, 20*time.Second)
|
||||||
|
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||||
|
retrievedInstance, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
require.Nil(tt, err)
|
||||||
|
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||||
|
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
369
internal/api/grpc/instance/v2beta/integration_test/query_test.go
Normal file
369
internal/api/grpc/instance/v2beta/integration_test/query_test.go
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetInstance(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputContext context.Context
|
||||||
|
expectedInstanceID string
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when unauthN context should return unauthN error",
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when request succeeds should return matching instance",
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedInstanceID: inst.ID(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListInstances(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
|
||||||
|
instances := make([]*integration.Instance, 2)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
instances[0], instances[1] = inst, inst2
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort in descending order
|
||||||
|
slices.SortFunc(instances, func(i1, i2 *integration.Instance) int {
|
||||||
|
res := i1.Instance.Details.CreationDate.AsTime().Compare(i2.Instance.Details.CreationDate.AsTime())
|
||||||
|
if res == 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return -res
|
||||||
|
})
|
||||||
|
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.ListInstancesRequest
|
||||||
|
inputContext context.Context
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
expectedInstances []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.ListInstancesRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.ListInstancesRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request with filter should return paginated response",
|
||||||
|
inputRequest: &instance.ListInstancesRequest{
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
|
||||||
|
Queries: []*instance.Query{
|
||||||
|
{
|
||||||
|
Query: &instance.Query_IdQuery{
|
||||||
|
IdQuery: &instance.IdsQuery{
|
||||||
|
Ids: []string{inst.ID(), inst2.ID()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedInstances: []string{inst2.ID(), inst.ID()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
require.NotNil(t, res)
|
||||||
|
|
||||||
|
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
|
||||||
|
|
||||||
|
for i, ins := range res.GetInstances() {
|
||||||
|
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListCustomDomains(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
d1, d2 := "custom."+gofakeit.DomainName(), "custom."+gofakeit.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, err = inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||||
|
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.ListCustomDomainsRequest
|
||||||
|
inputContext context.Context
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
expectedDomains []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing"},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||||
|
Queries: []*instance.DomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request with filter should return paginated response",
|
||||||
|
inputRequest: &instance.ListCustomDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||||
|
Queries: []*instance.DomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.DomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedDomains: []string{d1, d2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
domains := []string{}
|
||||||
|
for _, d := range res.GetDomains() {
|
||||||
|
domains = append(domains, d.GetDomain())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Subset(t, domains, tc.expectedDomains)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListTrustedDomains(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Given
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||||
|
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||||
|
|
||||||
|
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
|
||||||
|
d1, d2 := "trusted."+gofakeit.DomainName(), "trusted."+gofakeit.DomainName()
|
||||||
|
|
||||||
|
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, err = inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
|
||||||
|
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
|
||||||
|
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *instance.ListTrustedDomainsRequest
|
||||||
|
inputContext context.Context
|
||||||
|
expectedErrorMsg string
|
||||||
|
expectedErrorCode codes.Code
|
||||||
|
expectedDomains []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when invalid context should return unauthN error",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
},
|
||||||
|
inputContext: context.Background(),
|
||||||
|
expectedErrorCode: codes.Unauthenticated,
|
||||||
|
expectedErrorMsg: "auth header missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when unauthZ context should return unauthZ error",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
},
|
||||||
|
inputContext: orgOwnerCtx,
|
||||||
|
expectedErrorCode: codes.PermissionDenied,
|
||||||
|
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when valid request with filter should return paginated response",
|
||||||
|
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||||
|
InstanceId: inst.ID(),
|
||||||
|
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||||
|
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||||
|
Queries: []*instance.TrustedDomainSearchQuery{
|
||||||
|
{
|
||||||
|
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
|
||||||
|
DomainQuery: &instance.DomainQuery{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputContext: ctxWithSysAuthZ,
|
||||||
|
expectedDomains: []string{d1, d2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
// Test
|
||||||
|
res, err := inst.Client.InstanceV2Beta.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||||
|
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||||
|
|
||||||
|
if tc.expectedErrorMsg == "" {
|
||||||
|
require.NotNil(t, res)
|
||||||
|
|
||||||
|
domains := []string{}
|
||||||
|
for _, d := range res.GetTrustedDomain() {
|
||||||
|
domains = append(domains, d.GetDomain())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Subset(t, domains, tc.expectedDomains)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
70
internal/api/grpc/instance/v2beta/query.go
Normal file
70
internal/api/grpc/instance/v2beta/query.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) GetInstance(ctx context.Context, _ *instance.GetInstanceRequest) (*instance.GetInstanceResponse, error) {
|
||||||
|
inst, err := s.query.Instance(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.GetInstanceResponse{
|
||||||
|
Instance: ToProtoObject(inst),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListInstances(ctx context.Context, req *instance.ListInstancesRequest) (*instance.ListInstancesResponse, error) {
|
||||||
|
queries, err := ListInstancesRequestToModel(req, s.systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
instances, err := s.query.SearchInstances(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.ListInstancesResponse{
|
||||||
|
Instances: InstancesToPb(instances.Instances),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, instances.SearchResponse),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListCustomDomains(ctx context.Context, req *instance.ListCustomDomainsRequest) (*instance.ListCustomDomainsResponse, error) {
|
||||||
|
queries, err := ListCustomDomainsRequestToModel(req, s.systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains, err := s.query.SearchInstanceDomains(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.ListCustomDomainsResponse{
|
||||||
|
Domains: DomainsToPb(domains.Domains),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListTrustedDomains(ctx context.Context, req *instance.ListTrustedDomainsRequest) (*instance.ListTrustedDomainsResponse, error) {
|
||||||
|
queries, err := ListTrustedDomainsRequestToModel(req, s.systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains, err := s.query.SearchInstanceTrustedDomains(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instance.ListTrustedDomainsResponse{
|
||||||
|
TrustedDomain: trustedDomainsToPb(domains.Domains),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, domains.SearchResponse),
|
||||||
|
}, nil
|
||||||
|
}
|
60
internal/api/grpc/instance/v2beta/server.go
Normal file
60
internal/api/grpc/instance/v2beta/server.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ instance.InstanceServiceServer = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
instance.UnimplementedInstanceServiceServer
|
||||||
|
command *command.Commands
|
||||||
|
query *query.Queries
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
defaultInstance command.InstanceSetup
|
||||||
|
externalDomain string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
func CreateServer(
|
||||||
|
command *command.Commands,
|
||||||
|
query *query.Queries,
|
||||||
|
database string,
|
||||||
|
defaultInstance command.InstanceSetup,
|
||||||
|
externalDomain string,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
command: command,
|
||||||
|
query: query,
|
||||||
|
defaultInstance: defaultInstance,
|
||||||
|
externalDomain: externalDomain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||||
|
instance.RegisterInstanceServiceServer(grpcServer, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AppName() string {
|
||||||
|
return instance.InstanceService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) MethodPrefix() string {
|
||||||
|
return instance.InstanceService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||||
|
return instance.InstanceService_AuthMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||||
|
return instance.RegisterInstanceServiceHandler
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
@@ -666,7 +667,7 @@ 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) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
validation := c.prepareUpdateInstance(instanceAgg, name)
|
validation := c.prepareUpdateInstance(instanceAgg, strings.TrimSpace(name))
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -885,7 +886,12 @@ func getSystemConfigWriteModel(ctx context.Context, filter preparation.FilterToQ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) RemoveInstance(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
func (c *Commands) RemoveInstance(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(id)
|
instID := strings.TrimSpace(id)
|
||||||
|
if instID == "" || len(instID) > 200 {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-VeS2zI", "Errors.Invalid.Argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceAgg := instance.NewAggregate(instID)
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRemoveInstance(instanceAgg))
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRemoveInstance(instanceAgg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -1257,7 +1257,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) {
|
|||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
name: "",
|
name: " ",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@@ -1404,6 +1404,32 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
|||||||
args args
|
args args
|
||||||
res res
|
res res
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "instance empty, invalid argument error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: func(t *testing.T) *eventstore.Eventstore { return &eventstore.Eventstore{} },
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), " "),
|
||||||
|
instanceID: " ",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "instance too long, invalid argument error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: func(t *testing.T) *eventstore.Eventstore { return &eventstore.Eventstore{} },
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "averylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonginstance"),
|
||||||
|
instanceID: "averylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonginstance",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "instance not existing, not found error",
|
name: "instance not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
|
@@ -34,6 +34,14 @@ func (c *Commands) AddTrustedDomain(ctx context.Context, trustedDomain string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string) (*domain.ObjectDetails, error) {
|
func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string) (*domain.ObjectDetails, error) {
|
||||||
|
trustedDomain = strings.TrimSpace(trustedDomain)
|
||||||
|
if trustedDomain == "" || len(trustedDomain) > 253 {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-ajAzwu", "Errors.Invalid.Argument")
|
||||||
|
}
|
||||||
|
if !allowDomainRunes.MatchString(trustedDomain) {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-lfs3Te", "Errors.Instance.Domain.InvalidCharacter")
|
||||||
|
}
|
||||||
|
|
||||||
model := NewInstanceTrustedDomainsWriteModel(ctx)
|
model := NewInstanceTrustedDomainsWriteModel(ctx)
|
||||||
err := c.eventstore.FilterToQueryReducer(ctx, model)
|
err := c.eventstore.FilterToQueryReducer(ctx, model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -142,6 +142,45 @@ func TestCommands_RemoveTrustedDomain(t *testing.T) {
|
|||||||
args args
|
args args
|
||||||
want want
|
want want
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "domain empty string, error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: func(t *testing.T) *eventstore.Eventstore { return &eventstore.Eventstore{} },
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||||
|
trustedDomain: " ",
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
err: zerrors.ThrowInvalidArgument(nil, "COMMA-ajAzwu", "Errors.Invalid.Argument"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain invalid character, error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: func(t *testing.T) *eventstore.Eventstore { return &eventstore.Eventstore{} },
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||||
|
trustedDomain: "? ",
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
err: zerrors.ThrowInvalidArgument(nil, "COMMA-lfs3Te", "Errors.Instance.Domain.InvalidCharacter"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain length exceeded, error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: func(t *testing.T) *eventstore.Eventstore { return &eventstore.Eventstore{} },
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||||
|
trustedDomain: "averylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongdomain",
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
err: zerrors.ThrowInvalidArgument(nil, "COMMA-ajAzwu", "Errors.Invalid.Argument"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "domain does not exists, error",
|
name: "domain does not exists, error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
|
@@ -26,6 +26,7 @@ import (
|
|||||||
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
||||||
"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"
|
||||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
@@ -70,6 +71,11 @@ type Client struct {
|
|||||||
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
||||||
SAMLv2 saml_pb.SAMLServiceClient
|
SAMLv2 saml_pb.SAMLServiceClient
|
||||||
SCIM *scim.Client
|
SCIM *scim.Client
|
||||||
|
InstanceV2Beta instance.InstanceServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultClient(ctx context.Context) (*Client, error) {
|
||||||
|
return newClient(ctx, loadedConfig.Host())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(ctx context.Context, target string) (*Client, error) {
|
func newClient(ctx context.Context, target string) (*Client, error) {
|
||||||
@@ -103,6 +109,7 @@ func newClient(ctx context.Context, target string) (*Client, error) {
|
|||||||
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
||||||
SAMLv2: saml_pb.NewSAMLServiceClient(cc),
|
SAMLv2: saml_pb.NewSAMLServiceClient(cc),
|
||||||
SCIM: scim.NewScimClient(target),
|
SCIM: scim.NewScimClient(target),
|
||||||
|
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
||||||
}
|
}
|
||||||
return client, client.pollHealth(ctx)
|
return client, client.pollHealth(ctx)
|
||||||
}
|
}
|
||||||
|
@@ -150,7 +150,7 @@ func (q *Queries) SearchInstances(ctx context.Context, queries *InstanceSearchQu
|
|||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
filter, query, scan := prepareInstancesQuery()
|
filter, query, scan := prepareInstancesQuery(queries.SortingColumn, queries.Asc)
|
||||||
stmt, args, err := query(queries.toQuery(filter)).ToSql()
|
stmt, args, err := query(queries.toQuery(filter)).ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-M9fow", "Errors.Query.SQLStatement")
|
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-M9fow", "Errors.Query.SQLStatement")
|
||||||
@@ -260,17 +260,20 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
|
|||||||
return instance.DefaultLang
|
return instance.DefaultLang
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareInstancesQuery() (sq.SelectBuilder, func(sq.SelectBuilder) sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
func prepareInstancesQuery(sortBy Column, isAscedingSort bool) (sq.SelectBuilder, func(sq.SelectBuilder) sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
||||||
instanceFilterTable := instanceTable.setAlias(InstancesFilterTableAlias)
|
instanceFilterTable := instanceTable.setAlias(InstancesFilterTableAlias)
|
||||||
instanceFilterIDColumn := InstanceColumnID.setTable(instanceFilterTable)
|
instanceFilterIDColumn := InstanceColumnID.setTable(instanceFilterTable)
|
||||||
instanceFilterCountColumn := InstancesFilterTableAlias + ".count"
|
instanceFilterCountColumn := InstancesFilterTableAlias + ".count"
|
||||||
return sq.Select(
|
|
||||||
InstanceColumnID.identifier(),
|
selector := sq.Select(InstanceColumnID.identifier(), countColumn.identifier())
|
||||||
countColumn.identifier(),
|
if !sortBy.isZero() {
|
||||||
).Distinct().From(instanceTable.identifier()).
|
selector = sq.Select(InstanceColumnID.identifier(), countColumn.identifier(), sortBy.identifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
return selector.Distinct().From(instanceTable.identifier()).
|
||||||
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)),
|
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)),
|
||||||
func(builder sq.SelectBuilder) sq.SelectBuilder {
|
func(builder sq.SelectBuilder) sq.SelectBuilder {
|
||||||
return sq.Select(
|
outerQuery := sq.Select(
|
||||||
instanceFilterCountColumn,
|
instanceFilterCountColumn,
|
||||||
instanceFilterIDColumn.identifier(),
|
instanceFilterIDColumn.identifier(),
|
||||||
InstanceColumnCreationDate.identifier(),
|
InstanceColumnCreationDate.identifier(),
|
||||||
@@ -292,6 +295,16 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(sq.SelectBuilder) sq.Select
|
|||||||
LeftJoin(join(InstanceColumnID, instanceFilterIDColumn)).
|
LeftJoin(join(InstanceColumnID, instanceFilterIDColumn)).
|
||||||
LeftJoin(join(InstanceDomainInstanceIDCol, instanceFilterIDColumn)).
|
LeftJoin(join(InstanceDomainInstanceIDCol, instanceFilterIDColumn)).
|
||||||
PlaceholderFormat(sq.Dollar)
|
PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
|
if !sortBy.isZero() {
|
||||||
|
sorting := sortBy.identifier()
|
||||||
|
if !isAscedingSort {
|
||||||
|
sorting += " DESC"
|
||||||
|
}
|
||||||
|
return outerQuery.OrderBy(sorting)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outerQuery
|
||||||
},
|
},
|
||||||
func(rows *sql.Rows) (*Instances, error) {
|
func(rows *sql.Rows) (*Instances, error) {
|
||||||
instances := make([]*Instance, 0)
|
instances := make([]*Instance, 0)
|
||||||
|
@@ -70,7 +70,7 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "prepareInstancesQuery no result",
|
name: "prepareInstancesQuery no result",
|
||||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
||||||
filter, query, scan := prepareInstancesQuery()
|
filter, query, scan := prepareInstancesQuery(Column{}, true)
|
||||||
return query(filter), scan
|
return query(filter), scan
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
@@ -85,7 +85,7 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "prepareInstancesQuery one result",
|
name: "prepareInstancesQuery one result",
|
||||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
||||||
filter, query, scan := prepareInstancesQuery()
|
filter, query, scan := prepareInstancesQuery(Column{}, true)
|
||||||
return query(filter), scan
|
return query(filter), scan
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
@@ -149,7 +149,7 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "prepareInstancesQuery multiple results",
|
name: "prepareInstancesQuery multiple results",
|
||||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
||||||
filter, query, scan := prepareInstancesQuery()
|
filter, query, scan := prepareInstancesQuery(Column{}, true)
|
||||||
return query(filter), scan
|
return query(filter), scan
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
@@ -253,7 +253,8 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
IsPrimary: true,
|
IsPrimary: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
ID: "id2",
|
ID: "id2",
|
||||||
CreationDate: testNow,
|
CreationDate: testNow,
|
||||||
ChangeDate: testNow,
|
ChangeDate: testNow,
|
||||||
@@ -282,7 +283,7 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "prepareInstancesQuery sql err",
|
name: "prepareInstancesQuery sql err",
|
||||||
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
prepare: func() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
|
||||||
filter, query, scan := prepareInstancesQuery()
|
filter, query, scan := prepareInstancesQuery(Column{}, true)
|
||||||
return query(filter), scan
|
return query(filter), scan
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
|
@@ -307,6 +307,7 @@ service AdminService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [ListCustomDomains](apis/resources/instance_service_v2/instance-service-list-custom-domains.api.mdx) instead to list custom domains
|
||||||
rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
|
rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/domains/_search";
|
post: "/domains/_search";
|
||||||
@@ -319,10 +320,12 @@ service AdminService {
|
|||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "Instance";
|
tags: "Instance";
|
||||||
summary: "List Instance Domains";
|
summary: "List Instance Domains";
|
||||||
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are the URLs where ZITADEL is running."
|
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are the URLs where ZITADEL is running.";
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [ListTrustedDomains](apis/resources/instance_service_v2/instance-service-list-trusted-domains.api.mdx) instead to list trusted domains
|
||||||
rpc ListInstanceTrustedDomains(ListInstanceTrustedDomainsRequest) returns (ListInstanceTrustedDomainsResponse) {
|
rpc ListInstanceTrustedDomains(ListInstanceTrustedDomainsRequest) returns (ListInstanceTrustedDomainsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/trusted_domains/_search";
|
post: "/trusted_domains/_search";
|
||||||
@@ -335,10 +338,12 @@ service AdminService {
|
|||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "Instance";
|
tags: "Instance";
|
||||||
summary: "List Instance Trusted Domains";
|
summary: "List Instance Trusted Domains";
|
||||||
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts."
|
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts.";
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [AddTrustedDomain](apis/resources/instance_service_v2/instance-service-add-trusted-domain.api.mdx) instead to add a trusted domain
|
||||||
rpc AddInstanceTrustedDomain(AddInstanceTrustedDomainRequest) returns (AddInstanceTrustedDomainResponse) {
|
rpc AddInstanceTrustedDomain(AddInstanceTrustedDomainRequest) returns (AddInstanceTrustedDomainResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/trusted_domains";
|
post: "/trusted_domains";
|
||||||
@@ -352,10 +357,12 @@ service AdminService {
|
|||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "Instance";
|
tags: "Instance";
|
||||||
summary: "Add an Instance Trusted Domain";
|
summary: "Add an Instance Trusted Domain";
|
||||||
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts."
|
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts.";
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [RemoveTrustedDomain](apis/resources/instance_service_v2/instance-service-remove-trusted-domain.api.mdx) instead to remove a trusted domain
|
||||||
rpc RemoveInstanceTrustedDomain(RemoveInstanceTrustedDomainRequest) returns (RemoveInstanceTrustedDomainResponse) {
|
rpc RemoveInstanceTrustedDomain(RemoveInstanceTrustedDomainRequest) returns (RemoveInstanceTrustedDomainResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/trusted_domains/{domain}";
|
delete: "/trusted_domains/{domain}";
|
||||||
@@ -368,7 +375,8 @@ service AdminService {
|
|||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "Instance";
|
tags: "Instance";
|
||||||
summary: "Remove an Instance Trusted Domain";
|
summary: "Remove an Instance Trusted Domain";
|
||||||
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts."
|
description: "Returns a list of domains that are configured for this ZITADEL instance. These domains are trusted to be used as public hosts.";
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
192
proto/zitadel/instance/v2beta/instance.proto
Normal file
192
proto/zitadel/instance/v2beta/instance.proto
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "zitadel/object/v2/object.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
package zitadel.instance.v2beta;
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta;instance";
|
||||||
|
|
||||||
|
message Instance {
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// change_date is the timestamp when the object was changed
|
||||||
|
//
|
||||||
|
// on read: the timestamp of the last event reduced by the projection
|
||||||
|
//
|
||||||
|
// on manipulation: the timestamp of the event(s) added by the manipulation
|
||||||
|
google.protobuf.Timestamp change_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
google.protobuf.Timestamp creation_date = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
State state = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "current state of the instance";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"ZITADEL\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string version = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"1.0.0\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated Domain domains = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
STATE_UNSPECIFIED = 0;
|
||||||
|
STATE_CREATING = 1;
|
||||||
|
STATE_RUNNING = 2;
|
||||||
|
STATE_STOPPING = 3;
|
||||||
|
STATE_STOPPED = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Domain {
|
||||||
|
string instance_id = 1;
|
||||||
|
|
||||||
|
google.protobuf.Timestamp creation_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
string domain = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"zitadel.com\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool primary = 4;
|
||||||
|
bool generated = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FieldName {
|
||||||
|
FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
FIELD_NAME_ID = 1;
|
||||||
|
FIELD_NAME_NAME = 2;
|
||||||
|
FIELD_NAME_CREATION_DATE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Query {
|
||||||
|
oneof query {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
IdsQuery id_query = 1;
|
||||||
|
DomainsQuery domain_query = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message IdsQuery {
|
||||||
|
repeated string ids = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Instance ID";
|
||||||
|
example: "[\"4820840938402429\",\"4820840938402422\"]"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DomainsQuery {
|
||||||
|
repeated string domains = 1 [
|
||||||
|
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
max_items: 20;
|
||||||
|
example: "[\"my-instace.zitadel.cloud\", \"auth.custom.com\"]";
|
||||||
|
description: "Return the instances that have the requested domains";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
message DomainSearchQuery {
|
||||||
|
oneof query {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
DomainQuery domain_query = 1;
|
||||||
|
DomainGeneratedQuery generated_query = 2;
|
||||||
|
DomainPrimaryQuery primary_query = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DomainQuery {
|
||||||
|
string domain = 1 [
|
||||||
|
(validate.rules).string = {max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"zitadel.com\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.object.v2.TextQueryMethod method = 2 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Defines which text equality method is used";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DomainGeneratedQuery {
|
||||||
|
bool generated = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Generated domains";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DomainPrimaryQuery {
|
||||||
|
bool primary = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Primary domains";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
string instance_id = 1;
|
||||||
|
|
||||||
|
google.protobuf.Timestamp creation_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
string domain = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"zitadel.com\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrustedDomainSearchQuery {
|
||||||
|
oneof query {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
DomainQuery domain_query = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TrustedDomainFieldName {
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_DOMAIN = 1;
|
||||||
|
TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE = 2;
|
||||||
|
}
|
648
proto/zitadel/instance/v2beta/instance_service.proto
Normal file
648
proto/zitadel/instance/v2beta/instance_service.proto
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.instance.v2beta;
|
||||||
|
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "zitadel/object/v2/object.proto";
|
||||||
|
import "zitadel/instance/v2beta/instance.proto";
|
||||||
|
import "zitadel/filter/v2beta/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/v2beta;instance";
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||||
|
info: {
|
||||||
|
title: "Instance Service";
|
||||||
|
version: "2.0-beta";
|
||||||
|
description: "This API is intended to manage instances in ZITADEL.";
|
||||||
|
contact:{
|
||||||
|
name: "ZITADEL"
|
||||||
|
url: "https://zitadel.com"
|
||||||
|
email: "hi@zitadel.com"
|
||||||
|
}
|
||||||
|
license: {
|
||||||
|
name: "AGPL-3.0-only",
|
||||||
|
url: "https://github.com/zitadel/zitadel/blob/main/LICENSING.md";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
schemes: HTTPS;
|
||||||
|
schemes: HTTP;
|
||||||
|
|
||||||
|
consumes: "application/json";
|
||||||
|
consumes: "application/grpc";
|
||||||
|
|
||||||
|
produces: "application/json";
|
||||||
|
produces: "application/grpc";
|
||||||
|
|
||||||
|
consumes: "application/grpc-web+proto";
|
||||||
|
produces: "application/grpc-web+proto";
|
||||||
|
|
||||||
|
host: "$CUSTOM-DOMAIN";
|
||||||
|
base_path: "/";
|
||||||
|
|
||||||
|
external_docs: {
|
||||||
|
description: "Detailed information about ZITADEL",
|
||||||
|
url: "https://zitadel.com/docs"
|
||||||
|
}
|
||||||
|
security_definitions: {
|
||||||
|
security: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
type: TYPE_OAUTH2;
|
||||||
|
flow: FLOW_ACCESS_CODE;
|
||||||
|
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
|
||||||
|
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
|
||||||
|
scopes: {
|
||||||
|
scope: {
|
||||||
|
key: "openid";
|
||||||
|
value: "openid";
|
||||||
|
}
|
||||||
|
scope: {
|
||||||
|
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
security: {
|
||||||
|
security_requirement: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
scope: "openid";
|
||||||
|
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "403";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the user does not have permission to access the resource.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "404";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the resource does not exist.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Service to manage instances and their domains.
|
||||||
|
// The service provides methods to create, update, delete and list instances and their domains.
|
||||||
|
service InstanceService {
|
||||||
|
|
||||||
|
// Delete Instance
|
||||||
|
//
|
||||||
|
// Deletes an instance with the given ID.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.instance.delete`
|
||||||
|
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The deleted instance.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/v2beta/instances/{instance_id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.instance.delete"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Instance
|
||||||
|
//
|
||||||
|
// Returns the instance in the current context.
|
||||||
|
//
|
||||||
|
// The instace_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The instance of the context.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v2beta/instances/{instance_id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.read"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Instance
|
||||||
|
//
|
||||||
|
// Updates instance in context with the given name.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The instance was successfully updated.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
put: "/v2beta/instances/{instance_id}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.instance.read`
|
||||||
|
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The list of instances.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/instances/search"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.instance.read"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Custom Domain
|
||||||
|
//
|
||||||
|
// Adds a custom domain to the instance in context.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.domain.write`
|
||||||
|
rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The added custom domain.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/instances/{instance_id}/custom-domains"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.domain.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Custom Domain
|
||||||
|
//
|
||||||
|
// Removes a custom domain from the instance.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `system.domain.write`
|
||||||
|
rpc RemoveCustomDomain(RemoveCustomDomainRequest) returns (RemoveCustomDomainResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The removed custom domain.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/v2beta/instances/{instance_id}/custom-domains/{domain}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "system.domain.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Custom Domains
|
||||||
|
//
|
||||||
|
// Lists custom domains of the instance.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
rpc ListCustomDomains(ListCustomDomainsRequest) returns (ListCustomDomainsResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The list of custom domains.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/instances/{instance_id}/custom-domains/search"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.read"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Trusted Domain
|
||||||
|
//
|
||||||
|
// Adds a trusted domain to the instance.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
rpc AddTrustedDomain(AddTrustedDomainRequest) returns (AddTrustedDomainResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The added trusted domain.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/instances/{instance_id}/trusted-domains"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Trusted Domain
|
||||||
|
//
|
||||||
|
// Removes a trusted domain from the instance.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.write`
|
||||||
|
rpc RemoveTrustedDomain(RemoveTrustedDomainRequest) returns (RemoveTrustedDomainResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The removed trusted domain.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/v2beta/instances/{instance_id}/trusted-domains/{domain}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// List Trusted Domains
|
||||||
|
//
|
||||||
|
// Lists trusted domains of the instance.
|
||||||
|
//
|
||||||
|
// The instance_id in the input message will be used in the future.
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - `iam.read`
|
||||||
|
rpc ListTrustedDomains(ListTrustedDomainsRequest) returns (ListTrustedDomainsResponse) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The list of trusted domains.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/instances/{instance_id}/trusted-domains/search"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "iam.read"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteInstanceRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteInstanceResponse {
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInstanceRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInstanceResponse {
|
||||||
|
zitadel.instance.v2beta.Instance instance = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateInstanceRequest {
|
||||||
|
// used only to identify the instance to change.
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
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;
|
||||||
|
description: "\"name of the instance to update\"";
|
||||||
|
example: "\"my instance\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateInstanceResponse {
|
||||||
|
google.protobuf.Timestamp change_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListInstancesRequest {
|
||||||
|
// Criterias the client is looking for.
|
||||||
|
repeated Query queries = 1;
|
||||||
|
|
||||||
|
// Pagination and sorting.
|
||||||
|
zitadel.filter.v2beta.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
optional FieldName sorting_column = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListInstancesResponse {
|
||||||
|
// The list of instances.
|
||||||
|
repeated Instance instances = 1;
|
||||||
|
|
||||||
|
// Contains the total number of instances matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2beta.PaginationResponse pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddCustomDomainRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string 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;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddCustomDomainResponse {
|
||||||
|
google.protobuf.Timestamp creation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveCustomDomainRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string 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;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveCustomDomainResponse {
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCustomDomainsRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Pagination and sorting.
|
||||||
|
zitadel.filter.v2beta.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
DomainFieldName sorting_column = 3;
|
||||||
|
|
||||||
|
// Criterias the client is looking for.
|
||||||
|
repeated DomainSearchQuery queries = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCustomDomainsResponse {
|
||||||
|
repeated Domain domains = 1;
|
||||||
|
|
||||||
|
// Contains the total number of domains matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2beta.PaginationResponse pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddTrustedDomainRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string 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 {
|
||||||
|
google.protobuf.Timestamp creation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveTrustedDomainRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string 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 {
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTrustedDomainsRequest {
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Pagination and sorting.
|
||||||
|
zitadel.filter.v2beta.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
// The field the result is sorted by.
|
||||||
|
TrustedDomainFieldName sorting_column = 3;
|
||||||
|
|
||||||
|
// Criterias the client is looking for.
|
||||||
|
repeated TrustedDomainSearchQuery queries = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTrustedDomainsResponse {
|
||||||
|
repeated TrustedDomain trusted_domain = 1;
|
||||||
|
|
||||||
|
// Contains the total number of domains matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2beta.PaginationResponse pagination = 2;
|
||||||
|
}
|
@@ -117,6 +117,8 @@ service SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of ZITADEL instances
|
// Returns a list of ZITADEL instances
|
||||||
|
//
|
||||||
|
// Deprecated: Use [ListInstances](apis/resources/instance_service_v2/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"
|
||||||
@@ -126,9 +128,15 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.instance.read";
|
permission: "system.instance.read";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the detail of an instance
|
// Returns the detail of an instance
|
||||||
|
//
|
||||||
|
// Deprecated: Use [GetInstance](apis/resources/instance_service_v2/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}";
|
||||||
@@ -137,6 +145,10 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.instance.read";
|
permission: "system.instance.read";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use CreateInstance instead
|
// Deprecated: Use CreateInstance instead
|
||||||
@@ -151,9 +163,15 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.instance.write";
|
permission: "system.instance.write";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates name of an existing instance
|
// Updates name of an existing instance
|
||||||
|
//
|
||||||
|
// Deprecated: Use [UpdateInstance](apis/resources/instance_service_v2/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}"
|
||||||
@@ -163,6 +181,10 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.instance.write";
|
permission: "system.instance.write";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new instance with all needed setup data
|
// Creates a new instance with all needed setup data
|
||||||
@@ -180,6 +202,8 @@ 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/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}"
|
||||||
@@ -188,6 +212,10 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.instance.delete";
|
permission: "system.instance.delete";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//Returns all instance members matching the request
|
//Returns all instance members matching the request
|
||||||
@@ -204,7 +232,9 @@ service SystemService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//Checks if a domain exists
|
// Checks if a domain exists
|
||||||
|
//
|
||||||
|
// Deprecated: Use [ListCustomDomains](apis/resources/instance_service_v2/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";
|
||||||
@@ -214,10 +244,14 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.domain.read";
|
permission: "system.domain.read";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the custom domains of an instance
|
// Returns the custom domains of an instance
|
||||||
//Checks if a domain exists
|
// Checks if a domain exists
|
||||||
// Deprecated: Use the Admin APIs ListInstanceDomains on the admin API instead
|
// Deprecated: Use the Admin APIs ListInstanceDomains on the admin API instead
|
||||||
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
|
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@@ -228,9 +262,15 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.domain.read";
|
permission: "system.domain.read";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a domain to an instance
|
// Adds a domain to an instance
|
||||||
|
//
|
||||||
|
// Deprecated: Use [AddCustomDomain](apis/resources/instance_service_v2/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";
|
||||||
@@ -240,9 +280,15 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.domain.write";
|
permission: "system.domain.write";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the domain of an instance
|
// Removes the domain of an instance
|
||||||
|
//
|
||||||
|
// Deprecated: Use [RemoveDomain](apis/resources/instance_service_v2/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}";
|
||||||
@@ -251,6 +297,10 @@ service SystemService {
|
|||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "system.domain.delete";
|
permission: "system.domain.delete";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the primary domain of an instance
|
// Sets the primary domain of an instance
|
||||||
|
Reference in New Issue
Block a user