feat: Instance domains (#3444)

* feat: add domain list

* feat: domain tests

* feat: add redirect url on adding instance domain

* Update internal/command/instance_domain.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* feat: remove unused code

* fix

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2022-04-14 14:19:18 +02:00 committed by GitHub
parent 820a21dce3
commit c25d853820
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 858 additions and 145 deletions

View File

@ -56,6 +56,18 @@ Set the default language
GET: /languages/default GET: /languages/default
### ListInstanceDomains
> **rpc** ListInstanceDomains([ListInstanceDomainsRequest](#listinstancedomainsrequest))
[ListInstanceDomainsResponse](#listinstancedomainsresponse)
Returns the domains of an instance
GET: /domains
### ListSecretGenerators ### ListSecretGenerators
> **rpc** ListSecretGenerators([ListSecretGeneratorsRequest](#listsecretgeneratorsrequest)) > **rpc** ListSecretGenerators([ListSecretGeneratorsRequest](#listsecretgeneratorsrequest))
@ -2629,6 +2641,32 @@ This is an empty request
### ListInstanceDomainsRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| query | zitadel.v1.ListQuery | - | |
| sorting_column | zitadel.instance.v1.DomainFieldName | the field the result is sorted | |
| queries | repeated zitadel.instance.v1.DomainSearchQuery | criterias the client is looking for | |
### ListInstanceDomainsResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ListDetails | - | |
| sorting_column | zitadel.instance.v1.DomainFieldName | - | |
| result | repeated zitadel.instance.v1.Domain | - | |
### ListLoginPolicyIDPsRequest ### ListLoginPolicyIDPsRequest

View File

@ -9,14 +9,63 @@ title: zitadel/instance.proto
## Messages ## Messages
### DomainsQuery ### Domain
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| domains | repeated string | - | string.max_len: 200<br /> | | details | zitadel.v1.ObjectDetails | - | |
| method | zitadel.v1.ListQueryMethod | - | enum.defined_only: true<br /> | | domain | string | - | |
| primary | bool | - | |
| generated | bool | - | |
### DomainGeneratedQuery
DomainGeneratedQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| generated | bool | - | |
### DomainPrimaryQuery
DomainPrimaryQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| primary | bool | - | |
### DomainQuery
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| domain | string | - | string.max_len: 200<br /> |
| method | zitadel.v1.TextQueryMethod | - | enum.defined_only: true<br /> |
### DomainSearchQuery
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.domain_query | DomainQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.generated_query | DomainGeneratedQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.primary_query | DomainPrimaryQuery | - | |
@ -41,8 +90,6 @@ IdQuery is always equals
| id | string | - | | | id | string | - | |
| details | zitadel.v1.ObjectDetails | - | | | details | zitadel.v1.ObjectDetails | - | |
| state | State | - | | | state | State | - | |
| generated_domain | string | - | |
| custom_domains | repeated string | - | |
| name | string | - | | | name | string | - | |
| version | string | - | | | version | string | - | |
@ -56,7 +103,6 @@ IdQuery is always equals
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.id_query | IdQuery | - | | | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.id_query | IdQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.domains_query | DomainsQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | | | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | |
@ -78,6 +124,20 @@ StateQuery is always equals
## Enums ## Enums
### DomainFieldName {#domainfieldname}
| Name | Number | Description |
| ---- | ------ | ----------- |
| 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 | - |
### FieldName {#fieldname} ### FieldName {#fieldname}
@ -85,9 +145,8 @@ StateQuery is always equals
| ---- | ------ | ----------- | | ---- | ------ | ----------- |
| FIELD_NAME_UNSPECIFIED | 0 | - | | FIELD_NAME_UNSPECIFIED | 0 | - |
| FIELD_NAME_ID | 1 | - | | FIELD_NAME_ID | 1 | - |
| FIELD_NAME_GENERATED_DOMAIN | 2 | - | | FIELD_NAME_NAME | 2 | - |
| FIELD_NAME_NAME | 3 | - | | FIELD_NAME_CREATION_DATE | 3 | - |
| FIELD_NAME_CREATION_DATE | 4 | - |

View File

@ -82,40 +82,52 @@ Returns the usage metrics of an instance
GET: /instances/{id}/usage GET: /instances/{id}/usage
### GetGeneratedDomain ### ListDomains
> **rpc** GetGeneratedDomain([GetGeneratedDomainRequest](#getgenerateddomainrequest)) > **rpc** ListDomains([ListDomainsRequest](#listdomainsrequest))
[GetGeneratedDomainResponse](#getgenerateddomainresponse) [ListDomainsResponse](#listdomainsresponse)
Returns the domain of an instance
GET: /instances/{id}/domains/generated
### GetCustomDomains
> **rpc** GetCustomDomains([GetCustomDomainsRequest](#getcustomdomainsrequest))
[GetCustomDomainsResponse](#getcustomdomainsresponse)
Returns the custom domains of an instance Returns the custom domains of an instance
GET: /instances/{id}/domains/custom GET: /instances/{id}/domains
### AddCustomDomain ### AddDomain
> **rpc** AddCustomDomain([AddCustomDomainRequest](#addcustomdomainrequest)) > **rpc** AddDomain([AddDomainRequest](#adddomainrequest))
[AddCustomDomainResponse](#addcustomdomainresponse) [AddDomainResponse](#adddomainresponse)
Returns the domain of an instance Returns the domain of an instance
POST: /instances/{id}/domains/custom POST: /instances/{id}/domains
### RemoveDomain
> **rpc** RemoveDomain([RemoveDomainRequest](#removedomainrequest))
[RemoveDomainResponse](#removedomainresponse)
Returns the domain of an instance
DELETE: /instances/{id}/domains/{domain}
### SetPrimaryDomain
> **rpc** SetPrimaryDomain([SetPrimaryDomainRequest](#setprimarydomainrequest))
[SetPrimaryDomainResponse](#setprimarydomainresponse)
Returns the domain of an instance
POST: /instances/{id}/domains/_set_primary
### ListViews ### ListViews
@ -185,19 +197,19 @@ failed event. You can find out if it worked on the `failure_count`
## Messages ## Messages
### AddCustomDomainRequest ### AddDomainRequest
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| custom_domain | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | domain | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### AddCustomDomainResponse ### AddDomainResponse
@ -297,52 +309,6 @@ This is an empty response
### GetCustomDomainsRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetCustomDomainsResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
| domains | repeated string | - | |
### GetGeneratedDomainRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetGeneratedDomainResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
| domain | string | - | |
### GetInstanceRequest ### GetInstanceRequest
@ -401,6 +367,33 @@ This is an empty response
### ListDomainsRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | list limitations and ordering | string.min_len: 1<br /> string.max_len: 200<br /> |
| query | zitadel.v1.ListQuery | - | |
| sorting_column | zitadel.instance.v1.DomainFieldName | the field the result is sorted | |
| queries | repeated zitadel.instance.v1.DomainSearchQuery | criterias the client is looking for | |
### ListDomainsResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ListDetails | - | |
| sorting_column | zitadel.instance.v1.DomainFieldName | - | |
| result | repeated zitadel.instance.v1.Domain | - | |
### ListFailedEventsRequest ### ListFailedEventsRequest
This is an empty request This is an empty request
@ -461,6 +454,29 @@ This is an empty request
### RemoveDomainRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| domain | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### RemoveDomainResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveFailedEventRequest ### RemoveFailedEventRequest
@ -502,6 +518,29 @@ This is an empty response
### SetPrimaryDomainRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| domain | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### SetPrimaryDomainResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### View ### View

View File

@ -12,6 +12,7 @@ type Instance interface {
InstanceID() string InstanceID() string
ProjectID() string ProjectID() string
ConsoleClientID() string ConsoleClientID() string
ConsoleApplicationID() string
RequestedDomain() string RequestedDomain() string
} }
@ -36,6 +37,10 @@ func (i *instance) ConsoleClientID() string {
return "" return ""
} }
func (i *instance) ConsoleApplicationID() string {
return ""
}
func (i *instance) RequestedDomain() string { func (i *instance) RequestedDomain() string {
return i.Domain return i.Domain
} }

View File

@ -79,6 +79,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleID" return "consoleID"
} }
func (m *mockInstance) ConsoleApplicationID() string {
return "appID"
}
func (m *mockInstance) RequestedDomain() string { func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud" return "zitadel.cloud"
} }

View File

@ -0,0 +1,29 @@
package admin
import (
"context"
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) GetInstanceDomains(ctx context.Context, req *admin_pb.ListInstanceDomainsRequest) (*admin_pb.ListInstanceDomainsResponse, error) {
queries, err := ListInstanceDomainsRequestToModel(req)
if err != nil {
return nil, err
}
domains, err := s.query.SearchInstanceDomains(ctx, queries)
if err != nil {
return nil, err
}
return &admin_pb.ListInstanceDomainsResponse{
Result: instance_grpc.DomainsToPb(domains.Domains),
Details: object.ToListDetails(
domains.Count,
domains.Sequence,
domains.Timestamp,
),
}, nil
}

View File

@ -0,0 +1,25 @@
package admin
import (
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/query"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func ListInstanceDomainsRequestToModel(req *admin_pb.ListInstanceDomainsRequest) (*query.InstanceDomainSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := instance_grpc.DomainQueriesToModel(req.Queries)
if err != nil {
return nil, err
}
return &query.InstanceDomainSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
//SortingColumn: //TODO: sorting
Queries: queries,
}, nil
}

View File

@ -0,0 +1,54 @@
package org
import (
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query"
instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
)
func DomainQueriesToModel(queries []*instance_pb.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_pb.DomainSearchQuery) (query.SearchQuery, error) {
switch q := searchQuery.Query.(type) {
case *instance_pb.DomainSearchQuery_DomainQuery:
return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.Method), q.DomainQuery.Domain)
case *instance_pb.DomainSearchQuery_GeneratedQuery:
return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedQuery.Generated)
case *instance_pb.DomainSearchQuery_PrimaryQuery:
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary)
default:
return nil, errors.ThrowInvalidArgument(nil, "ORG-Ags42", "List.Query.Invalid")
}
}
func DomainsToPb(domains []*query.InstanceDomain) []*instance_pb.Domain {
d := make([]*instance_pb.Domain, len(domains))
for i, domain := range domains {
d[i] = DomainToPb(domain)
}
return d
}
func DomainToPb(d *query.InstanceDomain) *instance_pb.Domain {
return &instance_pb.Domain{
Domain: d.Domain,
Primary: d.IsPrimary,
Generated: d.IsGenerated,
Details: object.ToViewDetailsPb(
d.Sequence,
d.CreationDate,
d.ChangeDate,
d.InstanceID,
),
}
}

View File

@ -173,6 +173,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID" return "consoleClientID"
} }
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string { func (m *mockInstance) RequestedDomain() string {
return "localhost" return "localhost"
} }

View File

@ -257,6 +257,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID" return "consoleClientID"
} }
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string { func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud" return "zitadel.cloud"
} }

View File

@ -356,7 +356,7 @@ func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*
), ),
AddOIDCAppCommand(console, nil), AddOIDCAppCommand(console, nil),
SetIAMConsoleID(instanceAgg, &console.ClientID), SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.Zitadel.consoleAppID),
) )
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...) cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
@ -387,11 +387,11 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
} }
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance //SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
func SetIAMConsoleID(a *instance.Aggregate, clientID *string) preparation.Validation { func SetIAMConsoleID(a *instance.Aggregate, clientID, appID *string) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{ return []eventstore.Command{
instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID), instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID, appID),
}, nil }, nil
}, nil }, nil
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance" "github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/repository/project"
) )
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) { func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
@ -30,6 +31,24 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
}, nil }, nil
} }
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil {
return nil, err
}
events, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
}, nil
}
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) { func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.removeInstanceDomain(instanceAgg, instanceDomain) validation := c.removeInstanceDomain(instanceAgg, instanceDomain)
@ -61,7 +80,46 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
if domainWriteModel.State == domain.InstanceDomainStateActive { if domainWriteModel.State == domain.InstanceDomainStateActive {
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists") return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
} }
return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
if err != nil {
return nil, err
}
redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
ctx,
ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
appWriteModel.AppID,
[]project.OIDCConfigChanges{
project.ChangeRedirectURIs(redirectUrls),
project.ChangePostLogoutRedirectURIs(logoutUrls),
},
)
if err != nil {
return nil, err
}
return []eventstore.Command{
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
consoleChangeEvent,
}, nil
}, nil
}
}
func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
if err != nil {
return nil, err
}
if !domainWriteModel.State.Exists() {
return nil, errors.ThrowNotFound(nil, "INSTANCE-9nkWf", "Errors.Instance.Domain.NotFound")
}
return []eventstore.Command{instance.NewDomainPrimarySetEvent(ctx, &a.Aggregate, instanceDomain)}, nil
}, nil }, nil
} }
} }

View File

@ -3,8 +3,11 @@ package command
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
@ -77,6 +80,43 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
eventstore: eventstoreExpect( eventstore: eventstoreExpect(
t, t,
expectFilter(), expectFilter(),
expectFilter(
eventFromEventPusherWithInstanceID(
"INSTANCE",
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"consoleApplicationID",
"app",
),
),
eventFromEventPusherWithInstanceID(
"INSTANCE",
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("projectID", "org1").Aggregate,
domain.OIDCVersionV1,
"consoleApplicationID",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1,
[]string{"https://sub.test.ch"}),
),
),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusherWithInstanceID( eventFromEventPusherWithInstanceID(
@ -86,11 +126,121 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
"domain.ch", "domain.ch",
false, false,
)), )),
eventFromEventPusherWithInstanceID(
"INSTANCE",
newOIDCAppChangedEventInstanceDomain(context.Background(), "consoleApplicationID", "projectID", "org1"),
),
}, },
uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddInstanceDomainUniqueConstraint("domain.ch")), uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddInstanceDomainUniqueConstraint("domain.ch")),
), ),
), ),
}, },
args: args{
ctx: authz.WithInstance(context.Background(), new(mockInstance)),
domain: "domain.ch",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_SetPrimaryInstanceDomain(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
domain string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid domain, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
domain: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "domain not exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
domain: "domain.ch",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "set primary domain, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewDomainPrimarySetEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
)),
},
),
),
},
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
domain: "domain.ch", domain: "domain.ch",
@ -107,7 +257,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
} }
got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain) got, err := r.SetPrimaryInstanceDomain(tt.args.ctx, tt.args.domain)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -251,3 +401,16 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
}) })
} }
} }
func newOIDCAppChangedEventInstanceDomain(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
changes := []project.OIDCConfigChanges{
project.ChangeRedirectURIs([]string{"https://test.ch", "domain.ch/ui/console/auth/callback"}),
project.ChangePostLogoutRedirectURIs([]string{"https://test.ch/logout", "domain.ch/ui/console/signedout"}),
}
event, _ := project.NewOIDCConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
changes,
)
return event
}

View File

@ -256,3 +256,25 @@ func (v *testVerifier) CheckOrgFeatures(ctx context.Context, orgID string, requi
} }
return nil return nil
} }
type mockInstance struct{}
func (m *mockInstance) InstanceID() string {
return "INSTANCE"
}
func (m *mockInstance) ProjectID() string {
return "projectID"
}
func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@ -309,7 +309,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err return err
} }
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) _, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed") logging.New().OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
} }

View File

@ -18,6 +18,10 @@ func (f InstanceDomainState) Valid() bool {
return f >= 0 && f < instanceDomainStateCount return f >= 0 && f < instanceDomainStateCount
} }
func (f InstanceDomainState) Exists() bool {
return f == InstanceDomainStateActive
}
func NewGeneratedInstanceDomain(instanceName, iamDomain string) string { func NewGeneratedInstanceDomain(instanceName, iamDomain string) string {
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain) return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain)
} }

View File

@ -43,6 +43,10 @@ var (
name: projection.InstanceColumnConsoleID, name: projection.InstanceColumnConsoleID,
table: instanceTable, table: instanceTable,
} }
InstanceColumnConsoleAppID = Column{
name: projection.InstanceColumnConsoleAppID,
table: instanceTable,
}
InstanceColumnSetupStarted = Column{ InstanceColumnSetupStarted = Column{
name: projection.InstanceColumnSetUpStarted, name: projection.InstanceColumnSetUpStarted,
table: instanceTable, table: instanceTable,
@ -65,6 +69,7 @@ type Instance struct {
GlobalOrgID string GlobalOrgID string
IAMProjectID string IAMProjectID string
ConsoleID string ConsoleID string
ConsoleAppID string
DefaultLanguage language.Tag DefaultLanguage language.Tag
SetupStarted domain.Step SetupStarted domain.Step
SetupDone domain.Step SetupDone domain.Step
@ -83,6 +88,10 @@ func (i *Instance) ConsoleClientID() string {
return i.ConsoleID return i.ConsoleID
} }
func (i *Instance) ConsoleApplicationID() string {
return i.ConsoleAppID
}
func (i *Instance) RequestedDomain() string { func (i *Instance) RequestedDomain() string {
return i.Host return i.Host
} }
@ -142,6 +151,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
InstanceColumnGlobalOrgID.identifier(), InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(), InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(), InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(), InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(), InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(), InstanceColumnDefaultLanguage.identifier(),
@ -157,6 +167,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
&instance.GlobalOrgID, &instance.GlobalOrgID,
&instance.IAMProjectID, &instance.IAMProjectID,
&instance.ConsoleID, &instance.ConsoleID,
&instance.ConsoleAppID,
&instance.SetupStarted, &instance.SetupStarted,
&instance.SetupDone, &instance.SetupDone,
&lang, &lang,

View File

@ -19,6 +19,7 @@ type InstanceDomain struct {
Domain string Domain string
InstanceID string InstanceID string
IsGenerated bool IsGenerated bool
IsPrimary bool
} }
type InstanceDomains struct { type InstanceDomains struct {
@ -47,8 +48,12 @@ func NewInstanceDomainInstanceIDSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(InstanceDomainInstanceIDCol, value, TextEquals) return NewTextQuery(InstanceDomainInstanceIDCol, value, TextEquals)
} }
func NewInstanceDomainGeneratedSearchQuery(verified bool) (SearchQuery, error) { func NewInstanceDomainGeneratedSearchQuery(generated bool) (SearchQuery, error) {
return NewBoolQuery(InstanceDomainIsGeneratedCol, verified) return NewBoolQuery(InstanceDomainIsGeneratedCol, generated)
}
func NewInstanceDomainPrimarySearchQuery(primary bool) (SearchQuery, error) {
return NewBoolQuery(InstanceDomainIsPrimaryCol, primary)
} }
func (q *Queries) SearchInstanceDomains(ctx context.Context, queries *InstanceDomainSearchQueries) (domains *InstanceDomains, err error) { func (q *Queries) SearchInstanceDomains(ctx context.Context, queries *InstanceDomainSearchQueries) (domains *InstanceDomains, err error) {
@ -81,6 +86,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
InstanceDomainDomainCol.identifier(), InstanceDomainDomainCol.identifier(),
InstanceDomainInstanceIDCol.identifier(), InstanceDomainInstanceIDCol.identifier(),
InstanceDomainIsGeneratedCol.identifier(), InstanceDomainIsGeneratedCol.identifier(),
InstanceDomainIsPrimaryCol.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(instanceDomainsTable.identifier()).PlaceholderFormat(sq.Dollar), ).From(instanceDomainsTable.identifier()).PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*InstanceDomains, error) { func(rows *sql.Rows) (*InstanceDomains, error) {
@ -95,6 +101,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
&domain.Domain, &domain.Domain,
&domain.InstanceID, &domain.InstanceID,
&domain.IsGenerated, &domain.IsGenerated,
&domain.IsPrimary,
&count, &count,
) )
if err != nil { if err != nil {
@ -145,4 +152,8 @@ var (
name: projection.InstanceDomainIsGeneratedCol, name: projection.InstanceDomainIsGeneratedCol,
table: instanceDomainsTable, table: instanceDomainsTable,
} }
InstanceDomainIsPrimaryCol = Column{
name: projection.InstanceDomainIsPrimaryCol,
table: instanceDomainsTable,
}
) )

View File

@ -31,6 +31,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+ ` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+ ` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+ ` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+ ` COUNT(*) OVER ()`+
` FROM projections.instance_domains`), ` FROM projections.instance_domains`),
nil, nil,
@ -50,6 +51,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+ ` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+ ` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+ ` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+ ` COUNT(*) OVER ()`+
` FROM projections.instance_domains`), ` FROM projections.instance_domains`),
[]string{ []string{
@ -59,6 +61,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain", "domain",
"instance_id", "instance_id",
"is_generated", "is_generated",
"is_primary",
"count", "count",
}, },
[][]driver.Value{ [][]driver.Value{
@ -69,6 +72,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch", "zitadel.ch",
"inst-id", "inst-id",
true, true,
true,
}, },
}, },
), ),
@ -85,6 +89,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch", Domain: "zitadel.ch",
InstanceID: "inst-id", InstanceID: "inst-id",
IsGenerated: true, IsGenerated: true,
IsPrimary: true,
}, },
}, },
}, },
@ -100,6 +105,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+ ` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+ ` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+ ` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+ ` COUNT(*) OVER ()`+
` FROM projections.instance_domains`), ` FROM projections.instance_domains`),
[]string{ []string{
@ -109,6 +115,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain", "domain",
"instance_id", "instance_id",
"is_generated", "is_generated",
"is_primary",
"count", "count",
}, },
[][]driver.Value{ [][]driver.Value{
@ -119,6 +126,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch", "zitadel.ch",
"inst-id", "inst-id",
true, true,
true,
}, },
{ {
testNow, testNow,
@ -127,6 +135,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.com", "zitadel.com",
"inst-id", "inst-id",
false, false,
false,
}, },
}, },
), ),
@ -143,6 +152,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch", Domain: "zitadel.ch",
InstanceID: "inst-id", InstanceID: "inst-id",
IsGenerated: true, IsGenerated: true,
IsPrimary: true,
}, },
{ {
CreationDate: testNow, CreationDate: testNow,
@ -151,6 +161,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.com", Domain: "zitadel.com",
InstanceID: "inst-id", InstanceID: "inst-id",
IsGenerated: false, IsGenerated: false,
IsPrimary: false,
}, },
}, },
}, },
@ -166,6 +177,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+ ` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+ ` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+ ` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+ ` COUNT(*) OVER ()`+
` FROM projections.instance_domains`), ` FROM projections.instance_domains`),
sql.ErrConnDone, sql.ErrConnDone,

View File

@ -39,6 +39,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+ ` projections.instances.setup_started,`+
` projections.instances.setup_done,`+ ` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+
@ -68,6 +69,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+ ` projections.instances.setup_started,`+
` projections.instances.setup_done,`+ ` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+
@ -79,6 +81,7 @@ func Test_InstancePrepares(t *testing.T) {
"global_org_id", "global_org_id",
"iam_project_id", "iam_project_id",
"console_client_id", "console_client_id",
"console_app_id",
"setup_started", "setup_started",
"setup_done", "setup_done",
"default_language", "default_language",
@ -90,6 +93,7 @@ func Test_InstancePrepares(t *testing.T) {
"global-org-id", "global-org-id",
"project-id", "project-id",
"client-id", "client-id",
"app-id",
domain.Step2, domain.Step2,
domain.Step1, domain.Step1,
"en", "en",
@ -103,6 +107,7 @@ func Test_InstancePrepares(t *testing.T) {
GlobalOrgID: "global-org-id", GlobalOrgID: "global-org-id",
IAMProjectID: "project-id", IAMProjectID: "project-id",
ConsoleID: "client-id", ConsoleID: "client-id",
ConsoleAppID: "app-id",
SetupStarted: domain.Step2, SetupStarted: domain.Step2,
SetupDone: domain.Step1, SetupDone: domain.Step1,
DefaultLanguage: language.English, DefaultLanguage: language.English,
@ -121,6 +126,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+ ` projections.instances.setup_started,`+
` projections.instances.setup_done,`+ ` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+

View File

@ -18,6 +18,7 @@ const (
InstanceColumnGlobalOrgID = "global_org_id" InstanceColumnGlobalOrgID = "global_org_id"
InstanceColumnProjectID = "iam_project_id" InstanceColumnProjectID = "iam_project_id"
InstanceColumnConsoleID = "console_client_id" InstanceColumnConsoleID = "console_client_id"
InstanceColumnConsoleAppID = "console_app_id"
InstanceColumnSequence = "sequence" InstanceColumnSequence = "sequence"
InstanceColumnSetUpStarted = "setup_started" InstanceColumnSetUpStarted = "setup_started"
InstanceColumnSetUpDone = "setup_done" InstanceColumnSetUpDone = "setup_done"
@ -129,6 +130,7 @@ func (p *InstanceProjection) reduceConsoleSet(event eventstore.Event) (*handler.
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()), handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()), handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnConsoleID, e.ClientID), handler.NewCol(InstanceColumnConsoleID, e.ClientID),
handler.NewCol(InstanceColumnConsoleAppID, e.AppID),
}, },
), nil ), nil
} }

View File

@ -19,6 +19,7 @@ const (
InstanceDomainSequenceCol = "sequence" InstanceDomainSequenceCol = "sequence"
InstanceDomainDomainCol = "domain" InstanceDomainDomainCol = "domain"
InstanceDomainIsGeneratedCol = "is_generated" InstanceDomainIsGeneratedCol = "is_generated"
InstanceDomainIsPrimaryCol = "is_primary"
) )
type InstanceDomainProjection struct { type InstanceDomainProjection struct {
@ -37,6 +38,7 @@ func NewInstanceDomainProjection(ctx context.Context, config crdb.StatementHandl
crdb.NewColumn(InstanceDomainSequenceCol, crdb.ColumnTypeInt64), crdb.NewColumn(InstanceDomainSequenceCol, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceDomainDomainCol, crdb.ColumnTypeText), crdb.NewColumn(InstanceDomainDomainCol, crdb.ColumnTypeText),
crdb.NewColumn(InstanceDomainIsGeneratedCol, crdb.ColumnTypeBool), crdb.NewColumn(InstanceDomainIsGeneratedCol, crdb.ColumnTypeBool),
crdb.NewColumn(InstanceDomainIsPrimaryCol, crdb.ColumnTypeBool),
}, },
crdb.NewPrimaryKey(InstanceDomainInstanceIDCol, InstanceDomainDomainCol), crdb.NewPrimaryKey(InstanceDomainInstanceIDCol, InstanceDomainDomainCol),
), ),
@ -54,6 +56,10 @@ func (p *InstanceDomainProjection) reducers() []handler.AggregateReducer {
Event: instance.InstanceDomainAddedEventType, Event: instance.InstanceDomainAddedEventType,
Reduce: p.reduceDomainAdded, Reduce: p.reduceDomainAdded,
}, },
{
Event: instance.InstanceDomainAddedEventType,
Reduce: p.reduceDomainPrimarySet,
},
{ {
Event: instance.InstanceDomainRemovedEventType, Event: instance.InstanceDomainRemovedEventType,
Reduce: p.reduceDomainRemoved, Reduce: p.reduceDomainRemoved,
@ -77,10 +83,43 @@ func (p *InstanceDomainProjection) reduceDomainAdded(event eventstore.Event) (*h
handler.NewCol(InstanceDomainDomainCol, e.Domain), handler.NewCol(InstanceDomainDomainCol, e.Domain),
handler.NewCol(InstanceDomainInstanceIDCol, e.Aggregate().ID), handler.NewCol(InstanceDomainInstanceIDCol, e.Aggregate().ID),
handler.NewCol(InstanceDomainIsGeneratedCol, e.Generated), handler.NewCol(InstanceDomainIsGeneratedCol, e.Generated),
handler.NewCol(InstanceDomainIsPrimaryCol, false),
}, },
), nil ), nil
} }
func (p *InstanceDomainProjection) reduceDomainPrimarySet(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.DomainPrimarySetEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-f8nlw", "reduce.wrong.event.type %s", instance.InstanceDomainPrimarySetEventType)
}
return crdb.NewMultiStatement(
e,
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
handler.NewCol(InstanceDomainIsPrimaryCol, false),
},
[]handler.Condition{
handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(InstanceDomainIsPrimaryCol, true),
},
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
handler.NewCol(InstanceDomainIsPrimaryCol, true),
},
[]handler.Condition{
handler.NewCond(InstanceDomainDomainCol, e.Domain),
handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().ID),
},
),
), nil
}
func (p *InstanceDomainProjection) reduceDomainRemoved(event eventstore.Event) (*handler.Statement, error) { func (p *InstanceDomainProjection) reduceDomainRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.DomainRemovedEvent) e, ok := event.(*instance.DomainRemovedEvent)
if !ok { if !ok {

View File

@ -38,7 +38,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated) VALUES ($1, $2, $3, $4, $5, $6)", expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated, is_primary) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -46,6 +46,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
"domain.new", "domain.new",
"agg-id", "agg-id",
true, true,
false,
}, },
}, },
}, },

View File

@ -11,10 +11,11 @@ import (
) )
const ( const (
UniqueInstanceDomain = "instance_domain" UniqueInstanceDomain = "instance_domain"
domainEventPrefix = instanceEventTypePrefix + "domain." domainEventPrefix = instanceEventTypePrefix + "domain."
InstanceDomainAddedEventType = domainEventPrefix + "added" InstanceDomainAddedEventType = domainEventPrefix + "added"
InstanceDomainRemovedEventType = domainEventPrefix + "removed" InstanceDomainPrimarySetEventType = domainEventPrefix + "primary.set"
InstanceDomainRemovedEventType = domainEventPrefix + "removed"
) )
func NewAddInstanceDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint { func NewAddInstanceDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint {
@ -69,6 +70,43 @@ func DomainAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
return orgDomainAdded, nil return orgDomainAdded, nil
} }
type DomainPrimarySetEvent struct {
eventstore.BaseEvent `json:"-"`
Domain string `json:"domain,omitempty"`
}
func (e *DomainPrimarySetEvent) Data() interface{} {
return e
}
func (e *DomainPrimarySetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewDomainPrimarySetEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainPrimarySetEvent {
return &DomainPrimarySetEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
InstanceDomainPrimarySetEventType,
),
Domain: domain,
}
}
func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) {
orgDomainAdded := &DomainAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, orgDomainAdded)
if err != nil {
return nil, errors.ThrowInternal(err, "INSTANCE-29jöF", "unable to unmarshal instance domain added")
}
return orgDomainAdded, nil
}
type DomainRemovedEvent struct { type DomainRemovedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`

View File

@ -60,6 +60,7 @@ type ConsoleSetEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
ClientID string `json:"clientId"` ClientID string `json:"clientId"`
AppID string `json:"appId"`
} }
func (e *ConsoleSetEvent) Data() interface{} { func (e *ConsoleSetEvent) Data() interface{} {
@ -73,7 +74,8 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
func NewIAMConsoleSetEvent( func NewIAMConsoleSetEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
clientID *string, clientID,
appID *string,
) *ConsoleSetEvent { ) *ConsoleSetEvent {
return &ConsoleSetEvent{ return &ConsoleSetEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
@ -82,6 +84,7 @@ func NewIAMConsoleSetEvent(
ConsoleSetEventType, ConsoleSetEventType,
), ),
ClientID: *clientID, ClientID: *clientID,
AppID: *appID,
} }
} }

View File

@ -87,6 +87,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper). RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper).
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper). RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
RegisterFilterEventMapper(InstanceDomainAddedEventType, DomainAddedEventMapper). RegisterFilterEventMapper(InstanceDomainAddedEventType, DomainAddedEventMapper).
RegisterFilterEventMapper(InstanceDomainPrimarySetEventType, DomainPrimarySetEventMapper).
RegisterFilterEventMapper(InstanceDomainRemovedEventType, DomainRemovedEventMapper). RegisterFilterEventMapper(InstanceDomainRemovedEventType, DomainRemovedEventMapper).
RegisterFilterEventMapper(InstanceAddedEventType, InstanceAddedEventMapper). RegisterFilterEventMapper(InstanceAddedEventType, InstanceAddedEventMapper).
RegisterFilterEventMapper(InstanceChangedEventType, InstanceChangedEventMapper). RegisterFilterEventMapper(InstanceChangedEventType, InstanceChangedEventMapper).

View File

@ -1,6 +1,7 @@
syntax = "proto3"; syntax = "proto3";
import "zitadel/idp.proto"; import "zitadel/idp.proto";
import "zitadel/instance.proto";
import "zitadel/user.proto"; import "zitadel/user.proto";
import "zitadel/object.proto"; import "zitadel/object.proto";
import "zitadel/options.proto"; import "zitadel/options.proto";
@ -184,6 +185,13 @@ service AdminService {
}; };
} }
// Returns the domains of an instance
rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
option (google.api.http) = {
get: "/domains";
};
}
// Set the default language // Set the default language
rpc ListSecretGenerators(ListSecretGeneratorsRequest) returns (ListSecretGeneratorsResponse) { rpc ListSecretGenerators(ListSecretGeneratorsRequest) returns (ListSecretGeneratorsResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -2642,6 +2650,20 @@ message GetDefaultLanguageResponse {
string language = 1; string language = 1;
} }
message ListInstanceDomainsRequest {
zitadel.v1.ListQuery query = 1;
// the field the result is sorted
zitadel.instance.v1.DomainFieldName sorting_column = 2;
//criterias the client is looking for
repeated zitadel.instance.v1.DomainSearchQuery queries = 3;
}
message ListInstanceDomainsResponse {
zitadel.v1.ListDetails details = 1;
zitadel.instance.v1.DomainFieldName sorting_column = 2;
repeated zitadel.instance.v1.Domain result = 3;
}
message ListSecretGeneratorsRequest { message ListSecretGeneratorsRequest {
//list limitations and ordering //list limitations and ordering
zitadel.v1.ListQuery query = 1; zitadel.v1.ListQuery query = 1;

View File

@ -20,22 +20,12 @@ message Instance {
description: "current state of the instance"; description: "current state of the instance";
} }
]; ];
string generated_domain = 4 [ string name = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"organization.zitadel.com\"";
}
];
repeated string custom_domains = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"zitadel.com\", \"zitadel.cloud\", \"zitadel.ch\"]";
}
];
string name = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ZITADEL\""; example: "\"ZITADEL\"";
} }
]; ];
string version = 7 [ string version = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"v1.0.0\""; example: "\"v1.0.0\"";
} }
@ -55,8 +45,7 @@ message Query {
option (validate.required) = true; option (validate.required) = true;
IdQuery id_query = 1; IdQuery id_query = 1;
DomainsQuery domains_query = 2; StateQuery state_query = 2;
StateQuery state_query = 3;
} }
} }
@ -70,21 +59,6 @@ message IdQuery {
]; ];
} }
message DomainsQuery {
repeated string domains = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"caos.ch\"";
}
];
zitadel.v1.ListQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which list equality method is used";
}
];
}
//StateQuery is always equals //StateQuery is always equals
message StateQuery { message StateQuery {
State state = 1 [ State state = 1 [
@ -98,7 +72,68 @@ message StateQuery {
enum FieldName { enum FieldName {
FIELD_NAME_UNSPECIFIED = 0; FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1; FIELD_NAME_ID = 1;
FIELD_NAME_GENERATED_DOMAIN = 2; FIELD_NAME_NAME = 2;
FIELD_NAME_NAME = 3; FIELD_NAME_CREATION_DATE = 3;
FIELD_NAME_CREATION_DATE = 4; }
message Domain {
zitadel.v1.ObjectDetails details = 1;
string domain = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"zitadel.com\""
}
];
bool primary = 3;
bool generated = 4;
}
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) = {
description: "zitadel.com";
}
];
zitadel.v1.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";
}
];
}
//DomainGeneratedQuery is always equals
message DomainGeneratedQuery {
bool generated = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "generated domains";
}
];
}
//DomainPrimaryQuery is always equals
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;
} }

View File

@ -141,28 +141,37 @@ service SystemService {
}; };
} }
// Returns the domain of an instance
rpc GetGeneratedDomain(GetGeneratedDomainRequest) returns (GetGeneratedDomainResponse) {
option (google.api.http) = {
get: "/instances/{id}/domains/generated";
};
}
// Returns the custom domains of an instance // Returns the custom domains of an instance
rpc GetCustomDomains(GetCustomDomainsRequest) returns (GetCustomDomainsResponse) { rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
option (google.api.http) = { option (google.api.http) = {
get: "/instances/{id}/domains/custom"; get: "/instances/{id}/domains";
}; };
} }
// Returns the domain of an instance // Returns the domain of an instance
rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) { rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/instances/{id}/domains/custom"; post: "/instances/{id}/domains";
body: "*" body: "*"
}; };
} }
// Returns the domain of an instance
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
option (google.api.http) = {
delete: "/instances/{id}/domains/{domain}";
};
}
// Returns the domain of an instance
rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
option (google.api.http) = {
post: "/instances/{id}/domains/_set_primary";
body: "*"
};
}
//Returns all stored read models of ZITADEL //Returns all stored read models of ZITADEL
// views are used for search optimisation and optimise request latencies // views are used for search optimisation and optimise request latencies
// they represent the delta of the event happend on the objects // they represent the delta of the event happend on the objects
@ -346,30 +355,45 @@ message GetUsageResponse {
uint64 executed_action_mins = 3; uint64 executed_action_mins = 3;
} }
message GetGeneratedDomainRequest { message ListDomainsRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];//list limitations and ordering
zitadel.v1.ListQuery query = 2;
// the field the result is sorted
zitadel.instance.v1.DomainFieldName sorting_column = 3;
//criterias the client is looking for
repeated zitadel.instance.v1.DomainSearchQuery queries = 4;
} }
message GetGeneratedDomainResponse { message ListDomainsResponse {
zitadel.v1.ListDetails details = 1;
zitadel.instance.v1.DomainFieldName sorting_column = 2;
repeated zitadel.instance.v1.Domain result = 3;
}
message AddDomainRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message AddDomainResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
string domain = 2;
} }
message GetCustomDomainsRequest { message RemoveDomainRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
} }
message GetCustomDomainsResponse { message RemoveDomainResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
repeated string domains = 2;
} }
message AddCustomDomainRequest { message SetPrimaryDomainRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string custom_domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
} }
message AddCustomDomainResponse { message SetPrimaryDomainResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
} }