feat: System api (#3461)

* feat: start system api

* feat: remove auth

* feat: change gitignore

* feat: run system api

* feat: remove clear view form admin api

* feat: search instances

* feat: add instance

* fix: set primary domain

* Update .gitignore

* fix: add instance

* fix: add instance

* fix: handle errors

* fix: handle instance name

* fix: test

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2022-04-21 12:37:39 +02:00 committed by GitHub
parent a7816a43b1
commit 3d5891eb11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1216 additions and 485 deletions

2
.gitignore vendored
View File

@ -58,7 +58,7 @@ openapi/**/*.json
/internal/api/ui/console/static/* /internal/api/ui/console/static/*
# local # local
build/local/cloud.env build/local/*.env
migrations/cockroach/migrate_cloud.go migrations/cockroach/migrate_cloud.go
.notifications .notifications
.artifacts .artifacts

View File

@ -24,6 +24,8 @@ type DefaultInstance struct {
domain string domain string
defaults systemdefaults.SystemDefaults defaults systemdefaults.SystemDefaults
zitadelRoles []authz.RoleMapping zitadelRoles []authz.RoleMapping
baseURL string
externalSecure bool
} }
func (mig *DefaultInstance) Execute(ctx context.Context) error { func (mig *DefaultInstance) Execute(ctx context.Context) error {
@ -45,7 +47,8 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
mig.zitadelRoles, mig.zitadelRoles,
nil, nil,
nil, nil,
webauthn_helper.Config{}, //TODO: Livio will fix this, but it ZITADEL doesn't run without this
webauthn_helper.Config{DisplayName: "HELLO LIVIO", ID: "RPID"},
nil, nil,
nil, nil,
nil, nil,
@ -54,8 +57,12 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
nil, nil,
nil) nil)
if err != nil {
return err
}
ctx = authz.WithRequestedDomain(ctx, mig.domain) ctx = authz.WithRequestedDomain(ctx, mig.domain)
_, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup)
_, _, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup, mig.externalSecure, mig.baseURL)
return err return err
} }

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/command"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -15,20 +16,27 @@ import (
) )
type Config struct { type Config struct {
Database database.Config Database database.Config
SystemDefaults systemdefaults.SystemDefaults SystemDefaults systemdefaults.SystemDefaults
InternalAuthZ authz.Config InternalAuthZ authz.Config
ExternalPort uint16 ExternalPort uint16
ExternalDomain string ExternalDomain string
ExternalSecure bool ExternalSecure bool
Log *logging.Config Log *logging.Config
EncryptionKeys *encryptionKeyConfig EncryptionKeys *encryptionKeyConfig
DefaultInstance command.InstanceSetup
} }
func MustNewConfig(v *viper.Viper) *Config { func MustNewConfig(v *viper.Viper) *Config {
config := new(Config) config := new(Config)
err := v.Unmarshal(config) err := v.Unmarshal(config,
logging.OnError(err).Fatal("unable to read config") viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
)),
)
err = config.Log.SetLogger() err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger") logging.OnError(err).Fatal("unable to set logger")

View File

@ -51,6 +51,12 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient} steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
steps.s2AssetsTable = &AssetTable{dbClient: dbClient} steps.s2AssetsTable = &AssetTable{dbClient: dbClient}
instanceSetup := config.DefaultInstance
instanceSetup.InstanceName = steps.S3DefaultInstance.InstanceSetup.InstanceName
instanceSetup.CustomDomain = steps.S3DefaultInstance.InstanceSetup.CustomDomain
instanceSetup.Org = steps.S3DefaultInstance.InstanceSetup.Org
steps.S3DefaultInstance.InstanceSetup = instanceSetup
steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = strings.TrimSpace(steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address) steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = strings.TrimSpace(steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address)
if steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address == "" { if steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address == "" {
steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = "admin@" + config.ExternalDomain steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = "admin@" + config.ExternalDomain
@ -63,13 +69,14 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.S3DefaultInstance.domain = config.ExternalDomain steps.S3DefaultInstance.domain = config.ExternalDomain
steps.S3DefaultInstance.zitadelRoles = config.InternalAuthZ.RolePermissionMappings steps.S3DefaultInstance.zitadelRoles = config.InternalAuthZ.RolePermissionMappings
steps.S3DefaultInstance.userEncryptionKey = config.EncryptionKeys.User steps.S3DefaultInstance.userEncryptionKey = config.EncryptionKeys.User
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure steps.S3DefaultInstance.externalSecure = config.ExternalSecure
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) steps.S3DefaultInstance.baseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
ctx := context.Background() ctx := context.Background()
migration.Migrate(ctx, eventstoreClient, steps.s1ProjectionTable) err = migration.Migrate(ctx, eventstoreClient, steps.s1ProjectionTable)
migration.Migrate(ctx, eventstoreClient, steps.s2AssetsTable) logging.OnError(err).Fatal("unable to migrate step 1")
migration.Migrate(ctx, eventstoreClient, steps.S3DefaultInstance) err = migration.Migrate(ctx, eventstoreClient, steps.s2AssetsTable)
logging.OnError(err).Fatal("unable to migrate step 3")
err = migration.Migrate(ctx, eventstoreClient, steps.S3DefaultInstance)
logging.OnError(err).Fatal("unable to migrate step 4")
} }

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,9 @@ package start
import ( import (
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/hook"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper" "github.com/spf13/viper"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing" admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
@ -42,14 +45,20 @@ type Config struct {
InternalAuthZ internal_authz.Config InternalAuthZ internal_authz.Config
SystemDefaults systemdefaults.SystemDefaults SystemDefaults systemdefaults.SystemDefaults
EncryptionKeys *encryptionKeyConfig EncryptionKeys *encryptionKeyConfig
DefaultInstance command.InstanceSetup
} }
func MustNewConfig(v *viper.Viper) *Config { func MustNewConfig(v *viper.Viper) *Config {
config := new(Config) config := new(Config)
err := v.Unmarshal(config) err := v.Unmarshal(config,
logging.OnError(err).Fatal("unable to read config") viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
)),
)
err = config.Log.SetLogger() err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger") logging.OnError(err).Fatal("unable to set logger")

View File

@ -14,6 +14,7 @@ import (
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/api/grpc/system"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -142,7 +143,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
} }
verifier := internal_authz.Start(repo) verifier := internal_authz.Start(repo)
apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader) authenticatedAPIs := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader)
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.OIDC, keys.User) authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.OIDC, keys.User)
if err != nil { if err != nil {
return fmt.Errorf("error starting auth repo: %w", err) return fmt.Errorf("error starting auth repo: %w", err)
@ -151,18 +152,21 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil { if err != nil {
return fmt.Errorf("error starting admin repo: %w", err) return fmt.Errorf("error starting admin repo: %w", err)
} }
if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, keys.User)); err != nil { if err := authenticatedAPIs.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.DefaultInstance, config.ExternalPort, config.ExternalDomain, config.ExternalSecure)); err != nil {
return err return err
} }
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil { if err := authenticatedAPIs.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, keys.User)); err != nil {
return err return err
} }
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil { if err := authenticatedAPIs.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
return err
}
if err := authenticatedAPIs.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
return err return err
} }
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader) instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler)) authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure) userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure)
if err != nil { if err != nil {
@ -174,26 +178,26 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil { if err != nil {
return fmt.Errorf("unable to start oidc provider: %w", err) return fmt.Errorf("unable to start oidc provider: %w", err)
} }
apis.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler()) authenticatedAPIs.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
openAPIHandler, err := openapi.Start() openAPIHandler, err := openapi.Start()
if err != nil { if err != nil {
return fmt.Errorf("unable to start openapi handler: %w", err) return fmt.Errorf("unable to start openapi handler: %w", err)
} }
apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler) authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, instanceInterceptor.Handler) c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, instanceInterceptor.Handler)
if err != nil { if err != nil {
return fmt.Errorf("unable to start console: %w", err) return fmt.Errorf("unable to start console: %w", err)
} }
apis.RegisterHandler(console.HandlerPrefix, c) authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey) l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
if err != nil { if err != nil {
return fmt.Errorf("unable to start login: %w", err) return fmt.Errorf("unable to start login: %w", err)
} }
apis.RegisterHandler(login.HandlerPrefix, l.Handler()) authenticatedAPIs.RegisterHandler(login.HandlerPrefix, l.Handler())
return nil return nil
} }

File diff suppressed because one or more lines are too long

View File

@ -1427,21 +1427,6 @@ they represent the delta of the event happend on the objects
POST: /views/_search POST: /views/_search
### ClearView
> **rpc** ClearView([ClearViewRequest](#clearviewrequest))
[ClearViewResponse](#clearviewresponse)
Truncates the delta of the change stream
be carefull with this function because ZITADEL has to
recompute the deltas after they got cleared.
Search requests will return wrong results until all deltas are recomputed
POST: /views/{database}/{view_name}
### ListFailedEvents ### ListFailedEvents
> **rpc** ListFailedEvents([ListFailedEventsRequest](#listfailedeventsrequest)) > **rpc** ListFailedEvents([ListFailedEventsRequest](#listfailedeventsrequest))
@ -1718,24 +1703,6 @@ This is an empty request
### ClearViewRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| database | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| view_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ClearViewResponse
This is an empty response
### DeactivateIDPRequest ### DeactivateIDPRequest

View File

@ -70,13 +70,13 @@ DomainPrimaryQuery is always equals
### IdQuery ### IdsQuery
IdQuery is always equals IdQuery is always equals
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| id | string | - | string.max_len: 200<br /> | | ids | repeated string | - | |
@ -91,7 +91,6 @@ IdQuery is always equals
| details | zitadel.v1.ObjectDetails | - | | | details | zitadel.v1.ObjectDetails | - | |
| state | State | - | | | state | State | - | |
| name | string | - | | | name | string | - | |
| version | string | - | |
@ -102,19 +101,7 @@ 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 | IdsQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | |
### StateQuery
StateQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| state | State | - | enum.defined_only: true<br /> |

View File

@ -29,7 +29,7 @@ Returns a list of ZITADEL instances/tenants
POST: /instances POST: /instances/_search
### GetInstance ### GetInstance
@ -70,18 +70,6 @@ This might take some time
DELETE: /instances/{id} DELETE: /instances/{id}
### GetUsage
> **rpc** GetUsage([GetUsageRequest](#getusagerequest))
[GetUsageResponse](#getusageresponse)
Returns the usage metrics of an instance
GET: /instances/{id}/usage
### ListDomains ### ListDomains
> **rpc** ListDomains([ListDomainsRequest](#listdomainsrequest)) > **rpc** ListDomains([ListDomainsRequest](#listdomainsrequest))
@ -91,7 +79,7 @@ Returns the custom domains of an instance
GET: /instances/{id}/domains POST: /instances/{id}/domains/_search
### AddDomain ### AddDomain
@ -227,13 +215,12 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| instance_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | instance_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| first_org_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | first_org_name | string | - | string.max_len: 200<br /> |
| custom_domain | string | - | string.max_len: 200<br /> | | custom_domain | string | - | string.max_len: 200<br /> |
| owner_first_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | owner_first_name | string | - | string.max_len: 200<br /> |
| owner_last_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | owner_last_name | string | - | string.max_len: 200<br /> |
| owner_email | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | owner_email | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| owner_username | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | owner_username | string | - | string.max_len: 200<br /> |
| password | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| request_limit | uint64 | - | | | request_limit | uint64 | - | |
| action_mins_limit | uint64 | - | | | action_mins_limit | uint64 | - | |
@ -247,6 +234,7 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation | | Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| id | string | - | | | id | string | - | |
| details | zitadel.v1.ObjectDetails | - | |

View File

@ -69,7 +69,9 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.Server) erro
return err return err
} }
a.RegisterHandler(prefix, handler) a.RegisterHandler(prefix, handler)
a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods()) if a.verifier != nil {
a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
}
return nil return nil
} }

View File

@ -1,9 +1,8 @@
package admin_test package admin
import ( import (
"testing" "testing"
admin_grpc "github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/test" "github.com/caos/zitadel/internal/test"
"github.com/caos/zitadel/internal/view/model" "github.com/caos/zitadel/internal/view/model"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin" admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
@ -34,7 +33,7 @@ func TestFailedEventsToPbFields(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := admin_grpc.FailedEventsViewToPb(tt.args.failedEvents) got := FailedEventsViewToPb(tt.args.failedEvents)
for _, g := range got { for _, g := range got {
test.AssertFieldsMapped(t, g) test.AssertFieldsMapped(t, g)
} }
@ -64,7 +63,7 @@ func TestFailedEventToPbFields(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
converted := admin_grpc.FailedEventViewToPb(tt.args.failedEvent) converted := FailedEventViewToPb(tt.args.failedEvent)
test.AssertFieldsMapped(t, converted) test.AssertFieldsMapped(t, converted)
} }
} }
@ -89,7 +88,7 @@ func TestRemoveFailedEventRequestToModelFields(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
converted := admin_grpc.RemoveFailedEventRequestToModel(tt.args.req) converted := RemoveFailedEventRequestToModel(tt.args.req)
test.AssertFieldsMapped(t, converted, "FailureCount", "ErrMsg") test.AssertFieldsMapped(t, converted, "FailureCount", "ErrMsg")
} }
} }

View File

@ -22,16 +22,3 @@ func (s *Server) ListViews(ctx context.Context, _ *admin_pb.ListViewsRequest) (*
convertedCurrentSequences = append(convertedCurrentSequences, convertedViews...) convertedCurrentSequences = append(convertedCurrentSequences, convertedViews...)
return &admin_pb.ListViewsResponse{Result: convertedCurrentSequences}, nil return &admin_pb.ListViewsResponse{Result: convertedCurrentSequences}, nil
} }
func (s *Server) ClearView(ctx context.Context, req *admin_pb.ClearViewRequest) (*admin_pb.ClearViewResponse, error) {
var err error
if req.Database != "zitadel" {
err = s.administrator.ClearView(ctx, req.Database, req.ViewName)
} else {
err = s.query.ClearCurrentSequence(ctx, req.ViewName)
}
if err != nil {
return nil, err
}
return &admin_pb.ClearViewResponse{}, nil
}

View File

@ -7,6 +7,46 @@ import (
instance_pb "github.com/caos/zitadel/pkg/grpc/instance" instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
) )
func InstancesToPb(instances []*query.Instance) []*instance_pb.Instance {
list := make([]*instance_pb.Instance, len(instances))
for i, instance := range instances {
list[i] = InstanceToPb(instance)
}
return list
}
func InstanceToPb(instance *query.Instance) *instance_pb.Instance {
return &instance_pb.Instance{
Details: object.ToViewDetailsPb(
instance.Sequence,
instance.CreationDate,
instance.ChangeDate,
instance.InstanceID(),
),
Id: instance.InstanceID(),
}
}
func InstanceQueriesToModel(queries []*instance_pb.Query) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = InstanceQueryToModel(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func InstanceQueryToModel(searchQuery *instance_pb.Query) (query.SearchQuery, error) {
switch q := searchQuery.Query.(type) {
case *instance_pb.Query_IdQuery:
return query.NewInstanceIDsListSearchQuery(q.IdQuery.Ids...)
default:
return nil, errors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
}
}
func DomainQueriesToModel(queries []*instance_pb.DomainSearchQuery) (_ []query.SearchQuery, err error) { func DomainQueriesToModel(queries []*instance_pb.DomainSearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries)) q := make([]query.SearchQuery, len(queries))
for i, query := range queries { for i, query := range queries {
@ -27,7 +67,7 @@ func DomainQueryToModel(searchQuery *instance_pb.DomainSearchQuery) (query.Searc
case *instance_pb.DomainSearchQuery_PrimaryQuery: case *instance_pb.DomainSearchQuery_PrimaryQuery:
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary) return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary)
default: default:
return nil, errors.ThrowInvalidArgument(nil, "ORG-Ags42", "List.Query.Invalid") return nil, errors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
} }
} }

View File

@ -15,6 +15,10 @@ import (
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor { func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
//TODO: Change as soon as we know how to authenticate system api
if verifier == nil {
return handler(ctx, req)
}
return authorize(ctx, req, info, handler, verifier, authConfig) return authorize(ctx, req, info, handler, verifier, authConfig)
} }
} }

View File

@ -3,6 +3,7 @@ package middleware
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -16,13 +17,19 @@ type InstanceVerifier interface {
GetInstance(ctx context.Context) GetInstance(ctx context.Context)
} }
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string) grpc.UnaryServerInterceptor { func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, ignoredServices ...string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return setInstance(ctx, req, info, handler, verifier, headerName) return setInstance(ctx, req, info, handler, verifier, headerName, ignoredServices...)
} }
} }
func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string) (_ interface{}, err error) { func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string, ignoredServices ...string) (_ interface{}, err error) {
for _, service := range ignoredServices {
if strings.HasPrefix(info.FullMethod, service) {
return handler(ctx, req)
}
}
host, err := hostNameFromContext(ctx, headerName) host, err := hostNameFromContext(ctx, headerName)
if err != nil { if err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error()) return nil, status.Error(codes.PermissionDenied, err.Error())

View File

@ -30,7 +30,8 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
middleware.SentryHandler(), middleware.SentryHandler(),
middleware.NoCacheInterceptor(), middleware.NoCacheInterceptor(),
middleware.ErrorHandler(), middleware.ErrorHandler(),
middleware.InstanceInterceptor(queries, hostHeaderName), //TODO: Handle Ignored Services
middleware.InstanceInterceptor(queries, hostHeaderName, "/zitadel.system.v1.SystemService"),
middleware.AuthorizationInterceptor(verifier, authConfig), middleware.AuthorizationInterceptor(verifier, authConfig),
middleware.TranslationHandler(queries), middleware.TranslationHandler(queries),
middleware.ValidationHandler(), middleware.ValidationHandler(),

View File

@ -0,0 +1,37 @@
package system
import (
"context"
"github.com/caos/zitadel/internal/query"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func (s *Server) ListFailedEvents(ctx context.Context, req *system_pb.ListFailedEventsRequest) (*system_pb.ListFailedEventsResponse, error) {
failedEventsOld, err := s.administrator.GetFailedEvents(ctx)
if err != nil {
return nil, err
}
convertedOld := FailedEventsViewToPb(failedEventsOld)
failedEvents, err := s.query.SearchFailedEvents(ctx, new(query.FailedEventSearchQueries))
if err != nil {
return nil, err
}
convertedNew := FailedEventsToPb(failedEvents)
convertedOld = append(convertedOld, convertedNew...)
return &system_pb.ListFailedEventsResponse{Result: convertedOld}, nil
}
func (s *Server) RemoveFailedEvent(ctx context.Context, req *system_pb.RemoveFailedEventRequest) (*system_pb.RemoveFailedEventResponse, error) {
var err error
if req.Database != "zitadel" {
err = s.administrator.RemoveFailedEvent(ctx, RemoveFailedEventRequestToModel(req))
} else {
err = s.query.RemoveFailedEvent(ctx, req.ViewName, req.FailedSequence)
}
if err != nil {
return nil, err
}
return &system_pb.RemoveFailedEventResponse{}, nil
}

View File

@ -0,0 +1,51 @@
package system
import (
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/view/model"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func FailedEventsViewToPb(failedEvents []*model.FailedEvent) []*system_pb.FailedEvent {
events := make([]*system_pb.FailedEvent, len(failedEvents))
for i, failedEvent := range failedEvents {
events[i] = FailedEventViewToPb(failedEvent)
}
return events
}
func FailedEventViewToPb(failedEvent *model.FailedEvent) *system_pb.FailedEvent {
return &system_pb.FailedEvent{
Database: failedEvent.Database,
ViewName: failedEvent.ViewName,
FailedSequence: failedEvent.FailedSequence,
FailureCount: failedEvent.FailureCount,
ErrorMessage: failedEvent.ErrMsg,
}
}
func FailedEventsToPb(failedEvents *query.FailedEvents) []*system_pb.FailedEvent {
events := make([]*system_pb.FailedEvent, len(failedEvents.FailedEvents))
for i, failedEvent := range failedEvents.FailedEvents {
events[i] = FailedEventToPb(failedEvent)
}
return events
}
func FailedEventToPb(failedEvent *query.FailedEvent) *system_pb.FailedEvent {
return &system_pb.FailedEvent{
Database: "zitadel",
ViewName: failedEvent.ProjectionName,
FailedSequence: failedEvent.FailedSequence,
FailureCount: failedEvent.FailureCount,
ErrorMessage: failedEvent.Error,
}
}
func RemoveFailedEventRequestToModel(req *system_pb.RemoveFailedEventRequest) *model.FailedEvent {
return &model.FailedEvent{
Database: req.Database,
ViewName: req.ViewName,
FailedSequence: req.FailedSequence,
}
}

View File

@ -0,0 +1,95 @@
package system_test
import (
"testing"
system_grpc "github.com/caos/zitadel/internal/api/grpc/system"
"github.com/caos/zitadel/internal/test"
"github.com/caos/zitadel/internal/view/model"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func TestFailedEventsToPbFields(t *testing.T) {
type args struct {
failedEvents []*model.FailedEvent
}
tests := []struct {
name string
args args
}{
{
name: "all fields",
args: args{
failedEvents: []*model.FailedEvent{
{
Database: "admin",
ViewName: "users",
FailedSequence: 456,
FailureCount: 5,
ErrMsg: "some error",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := system_grpc.FailedEventsViewToPb(tt.args.failedEvents)
for _, g := range got {
test.AssertFieldsMapped(t, g)
}
})
}
}
func TestFailedEventToPbFields(t *testing.T) {
type args struct {
failedEvent *model.FailedEvent
}
tests := []struct {
name string
args args
}{
{
"all fields",
args{
failedEvent: &model.FailedEvent{
Database: "admin",
ViewName: "users",
FailedSequence: 456,
FailureCount: 5,
ErrMsg: "some error",
},
},
},
}
for _, tt := range tests {
converted := system_grpc.FailedEventViewToPb(tt.args.failedEvent)
test.AssertFieldsMapped(t, converted)
}
}
func TestRemoveFailedEventRequestToModelFields(t *testing.T) {
type args struct {
req *system_pb.RemoveFailedEventRequest
}
tests := []struct {
name string
args args
}{
{
"all fields",
args{
req: &system_pb.RemoveFailedEventRequest{
Database: "admin",
ViewName: "users",
FailedSequence: 456,
},
},
},
}
for _, tt := range tests {
converted := system_grpc.RemoveFailedEventRequestToModel(tt.args.req)
test.AssertFieldsMapped(t, converted, "FailureCount", "ErrMsg")
}
}

View File

@ -0,0 +1,127 @@
package system
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
object_pb "github.com/caos/zitadel/pkg/grpc/object"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func (s *Server) ListInstances(ctx context.Context, req *system_pb.ListInstancesRequest) (*system_pb.ListInstancesResponse, error) {
queries, err := ListInstancesRequestToModel(req)
if err != nil {
return nil, err
}
result, err := s.query.SearchInstances(ctx, queries)
if err != nil {
return nil, err
}
return &system_pb.ListInstancesResponse{
Result: instance_grpc.InstancesToPb(result.Instances),
Details: &object_pb.ListDetails{
TotalResult: result.Count,
},
}, nil
}
func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequest) (*system_pb.GetInstanceResponse, error) {
ctx = authz.WithInstanceID(ctx, req.Id)
instance, err := s.query.Instance(ctx)
if err != nil {
return nil, err
}
return &system_pb.GetInstanceResponse{
Instance: instance_grpc.InstanceToPb(instance),
}, nil
}
func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequest) (*system_pb.AddInstanceResponse, error) {
id, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.DefaultInstance), s.ExternalSecure, s.BaseURL)
if err != nil {
return nil, err
}
return &system_pb.AddInstanceResponse{
Id: id,
Details: object.AddToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
return nil, nil
}
func (s *Server) ListDomains(ctx context.Context, req *system_pb.ListDomainsRequest) (*system_pb.ListDomainsResponse, error) {
ctx = authz.WithInstanceID(ctx, req.Id)
queries, err := ListInstanceDomainsRequestToModel(req)
if err != nil {
return nil, err
}
domains, err := s.query.SearchInstanceDomains(ctx, queries)
if err != nil {
return nil, err
}
return &system_pb.ListDomainsResponse{
Result: instance_grpc.DomainsToPb(domains.Domains),
Details: object.ToListDetails(
domains.Count,
domains.Sequence,
domains.Timestamp,
),
}, nil
}
func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest) (*system_pb.AddDomainResponse, error) {
ctx = authz.WithInstanceID(ctx, req.Id)
instance, err := s.query.Instance(ctx)
if err != nil {
return nil, err
}
ctx = authz.WithInstance(ctx, instance)
details, err := s.command.AddInstanceDomain(ctx, req.Domain)
if err != nil {
return nil, err
}
return &system_pb.AddDomainResponse{
Details: object.AddToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveDomain(ctx context.Context, req *system_pb.RemoveDomainRequest) (*system_pb.RemoveDomainResponse, error) {
ctx = authz.WithInstanceID(ctx, req.Id)
details, err := s.command.RemoveInstanceDomain(ctx, req.Domain)
if err != nil {
return nil, err
}
return &system_pb.RemoveDomainResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
}
func (s *Server) SetPrimaryDomain(ctx context.Context, req *system_pb.SetPrimaryDomainRequest) (*system_pb.SetPrimaryDomainResponse, error) {
ctx = authz.WithInstanceID(ctx, req.Id)
details, err := s.command.SetPrimaryInstanceDomain(ctx, req.Domain)
if err != nil {
return nil, err
}
return &system_pb.SetPrimaryDomainResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
}

View File

@ -0,0 +1,97 @@
package system
import (
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/query"
instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
if req.InstanceName != "" {
defaultInstance.InstanceName = req.InstanceName
defaultInstance.Org.Name = req.InstanceName
}
if req.CustomDomain != "" {
defaultInstance.CustomDomain = req.CustomDomain
}
if req.FirstOrgName != "" {
defaultInstance.Org.Name = req.FirstOrgName
}
if req.OwnerEmail != "" {
defaultInstance.Org.Human.Email.Address = req.OwnerEmail
}
if req.OwnerUsername != "" {
defaultInstance.Org.Human.Username = req.OwnerUsername
}
if req.OwnerFirstName != "" {
defaultInstance.Org.Human.FirstName = req.OwnerFirstName
}
if req.OwnerLastName != "" {
defaultInstance.Org.Human.LastName = req.OwnerLastName
}
return &defaultInstance
}
func ListInstancesRequestToModel(req *system_pb.ListInstancesRequest) (*query.InstanceSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := instance_grpc.InstanceQueriesToModel(req.Queries)
if err != nil {
return nil, err
}
return &query.InstanceSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: fieldNameToInstanceColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func fieldNameToInstanceColumn(fieldName instance_pb.FieldName) query.Column {
switch fieldName {
case instance_pb.FieldName_FIELD_NAME_ID:
return query.InstanceColumnID
case instance_pb.FieldName_FIELD_NAME_NAME:
return query.InstanceColumnName
case instance_pb.FieldName_FIELD_NAME_CREATION_DATE:
return query.InstanceColumnCreationDate
default:
return query.Column{}
}
}
func ListInstanceDomainsRequestToModel(req *system_pb.ListDomainsRequest) (*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: fieldNameToInstanceDomainColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func fieldNameToInstanceDomainColumn(fieldName instance_pb.DomainFieldName) query.Column {
switch fieldName {
case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
return query.InstanceDomainDomainCol
case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
return query.InstanceDomainIsGeneratedCol
case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
return query.InstanceDomainIsPrimaryCol
case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
return query.InstanceDomainCreationDateCol
default:
return query.Column{}
}
}

View File

@ -0,0 +1,75 @@
package system
import (
"github.com/caos/zitadel/internal/admin/repository"
http_util "github.com/caos/zitadel/internal/api/http"
"google.golang.org/grpc"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/system"
)
const (
systemAPI = "System-API"
)
var _ system.SystemServiceServer = (*Server)(nil)
type Server struct {
system.UnimplementedSystemServiceServer
command *command.Commands
query *query.Queries
administrator repository.AdministratorRepository
DefaultInstance command.InstanceSetup
ExternalSecure bool
BaseURL string
}
type Config struct {
Repository eventsourcing.Config
}
func CreateServer(command *command.Commands,
query *query.Queries,
repo repository.Repository,
defaultInstance command.InstanceSetup,
externalPort uint16,
externalDomain string,
externalSecure bool) *Server {
return &Server{
command: command,
query: query,
administrator: repo,
DefaultInstance: defaultInstance,
ExternalSecure: externalSecure,
BaseURL: http_util.BuildHTTP(externalDomain, externalPort, externalSecure),
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
system.RegisterSystemServiceServer(grpcServer, s)
}
func (s *Server) AppName() string {
return systemAPI
}
func (s *Server) MethodPrefix() string {
return system.SystemService_MethodPrefix
}
func (s *Server) AuthMethods() authz.MethodMapping {
return system.SystemService_AuthMethods
}
func (s *Server) RegisterGateway() server.GatewayFunc {
return system.RegisterSystemServiceHandlerFromEndpoint
}
func (s *Server) GatewayPathPrefix() string {
return "/system/v1"
}

View File

@ -0,0 +1,37 @@
package system
import (
"context"
"github.com/caos/zitadel/internal/query"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
)
func (s *Server) ListViews(ctx context.Context, _ *system_pb.ListViewsRequest) (*system_pb.ListViewsResponse, error) {
currentSequences, err := s.query.SearchCurrentSequences(ctx, new(query.CurrentSequencesSearchQueries))
if err != nil {
return nil, err
}
convertedCurrentSequences := CurrentSequencesToPb(currentSequences)
views, err := s.administrator.GetViews()
if err != nil {
return nil, err
}
convertedViews := ViewsToPb(views)
convertedCurrentSequences = append(convertedCurrentSequences, convertedViews...)
return &system_pb.ListViewsResponse{Result: convertedCurrentSequences}, nil
}
func (s *Server) ClearView(ctx context.Context, req *system_pb.ClearViewRequest) (*system_pb.ClearViewResponse, error) {
var err error
if req.Database != "zitadel" {
err = s.administrator.ClearView(ctx, req.Database, req.ViewName)
} else {
err = s.query.ClearCurrentSequence(ctx, req.ViewName)
}
if err != nil {
return nil, err
}
return &system_pb.ClearViewResponse{}, nil
}

View File

@ -0,0 +1,43 @@
package system
import (
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/view/model"
system_pb "github.com/caos/zitadel/pkg/grpc/system"
"google.golang.org/protobuf/types/known/timestamppb"
)
func ViewsToPb(views []*model.View) []*system_pb.View {
v := make([]*system_pb.View, len(views))
for i, view := range views {
v[i] = ViewToPb(view)
}
return v
}
func ViewToPb(view *model.View) *system_pb.View {
return &system_pb.View{
Database: view.Database,
ViewName: view.ViewName,
LastSuccessfulSpoolerRun: timestamppb.New(view.LastSuccessfulSpoolerRun),
ProcessedSequence: view.CurrentSequence,
EventTimestamp: timestamppb.New(view.EventTimestamp),
}
}
func CurrentSequencesToPb(currentSequences *query.CurrentSequences) []*system_pb.View {
v := make([]*system_pb.View, len(currentSequences.CurrentSequences))
for i, currentSequence := range currentSequences.CurrentSequences {
v[i] = CurrentSequenceToPb(currentSequence)
}
return v
}
func CurrentSequenceToPb(currentSequence *query.CurrentSequence) *system_pb.View {
return &system_pb.View{
Database: "zitadel",
ViewName: currentSequence.ProjectionName,
ProcessedSequence: currentSequence.CurrentSequence,
EventTimestamp: timestamppb.New(currentSequence.Timestamp),
}
}

View File

@ -28,10 +28,22 @@ const (
consolePostLogoutPath = console.HandlerPrefix + "/signedout" consolePostLogoutPath = console.HandlerPrefix + "/signedout"
) )
type AddInstance struct {
InstanceName string
CustomDomain string
FirstOrgName string
OwnerEmail string
OwnerUsername string
OwnerFirstName string
OwnerLastName string
}
type InstanceSetup struct { type InstanceSetup struct {
Org OrgSetup zitadel ZitadelConfig
Zitadel ZitadelConfig InstanceName string
Features struct { CustomDomain string
Org OrgSetup
Features struct {
TierName string TierName string
TierDescription string TierDescription string
Retention time.Duration Retention time.Duration
@ -120,9 +132,6 @@ type InstanceSetup struct {
} }
type ZitadelConfig struct { type ZitadelConfig struct {
IsDevMode bool
BaseURL string
projectID string projectID string
mgmtAppID string mgmtAppID string
adminAppID string adminAppID string
@ -131,41 +140,41 @@ type ZitadelConfig struct {
} }
func (s *InstanceSetup) generateIDs() (err error) { func (s *InstanceSetup) generateIDs() (err error) {
s.Zitadel.projectID, err = id.SonyFlakeGenerator.Next() s.zitadel.projectID, err = id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return err return err
} }
s.Zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next() s.zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return err return err
} }
s.Zitadel.adminAppID, err = id.SonyFlakeGenerator.Next() s.zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return err return err
} }
s.Zitadel.authAppID, err = id.SonyFlakeGenerator.Next() s.zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return err return err
} }
s.Zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next() s.zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) { func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup, externalSecure bool, baseURL string) (string, *domain.ObjectDetails, error) {
instanceID, err := id.SonyFlakeGenerator.Next() instanceID, err := id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
if err = c.eventstore.NewInstance(ctx, instanceID); err != nil { if err = c.eventstore.NewInstance(ctx, instanceID); err != nil {
return nil, err return "", nil, err
} }
ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID}) ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
@ -174,16 +183,16 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
orgID, err := id.SonyFlakeGenerator.Next() orgID, err := id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
userID, err := id.SonyFlakeGenerator.Next() userID, err := id.SonyFlakeGenerator.Next()
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
if err = setup.generateIDs(); err != nil { if err = setup.generateIDs(); err != nil {
return nil, err return "", nil, err
} }
setup.Org.Human.PasswordChangeRequired = true setup.Org.Human.PasswordChangeRequired = true
@ -191,9 +200,11 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
instanceAgg := instance.NewAggregate(instanceID) instanceAgg := instance.NewAggregate(instanceID)
orgAgg := org.NewAggregate(orgID) orgAgg := org.NewAggregate(orgID)
userAgg := user.NewAggregate(userID, orgID) userAgg := user.NewAggregate(userID, orgID)
projectAgg := project.NewAggregate(setup.Zitadel.projectID, orgID) projectAgg := project.NewAggregate(setup.zitadel.projectID, orgID)
validations := []preparation.Validation{ validations := []preparation.Validation{
addInstance(instanceAgg, setup.InstanceName),
c.addGeneratedInstanceDomain(instanceAgg, setup.InstanceName),
SetDefaultFeatures( SetDefaultFeatures(
instanceAgg, instanceAgg,
setup.Features.TierName, setup.Features.TierName,
@ -289,20 +300,24 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg)) validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg))
} }
if setup.CustomDomain != "" {
validations = append(validations, c.addInstanceDomain(instanceAgg, setup.CustomDomain, false))
}
console := &addOIDCApp{ console := &addOIDCApp{
AddApp: AddApp{ AddApp: AddApp{
Aggregate: *projectAgg, Aggregate: *projectAgg,
ID: setup.Zitadel.consoleAppID, ID: setup.zitadel.consoleAppID,
Name: consoleAppName, Name: consoleAppName,
}, },
Version: domain.OIDCVersionV1, Version: domain.OIDCVersionV1,
RedirectUris: []string{setup.Zitadel.BaseURL + consoleRedirectPath}, RedirectUris: []string{baseURL + consoleRedirectPath},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeUserAgent, ApplicationType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone, AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectUris: []string{setup.Zitadel.BaseURL + consolePostLogoutPath}, PostLogoutRedirectUris: []string{baseURL + consolePostLogoutPath},
DevMode: setup.Zitadel.IsDevMode, DevMode: !externalSecure,
AccessTokenType: domain.OIDCTokenTypeBearer, AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false, AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false, IDTokenRoleAssertion: false,
@ -323,7 +338,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{ &addAPIApp{
AddApp: AddApp{ AddApp: AddApp{
Aggregate: *projectAgg, Aggregate: *projectAgg,
ID: setup.Zitadel.mgmtAppID, ID: setup.zitadel.mgmtAppID,
Name: mgmtAppName, Name: mgmtAppName,
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@ -335,7 +350,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{ &addAPIApp{
AddApp: AddApp{ AddApp: AddApp{
Aggregate: *projectAgg, Aggregate: *projectAgg,
ID: setup.Zitadel.adminAppID, ID: setup.zitadel.adminAppID,
Name: adminAppName, Name: adminAppName,
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@ -347,7 +362,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{ &addAPIApp{
AddApp: AddApp{ AddApp: AddApp{
Aggregate: *projectAgg, Aggregate: *projectAgg,
ID: setup.Zitadel.authAppID, ID: setup.zitadel.authAppID,
Name: authAppName, Name: authAppName,
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@ -356,25 +371,35 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
), ),
AddOIDCAppCommand(console, nil), AddOIDCAppCommand(console, nil),
SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.Zitadel.consoleAppID), SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.zitadel.consoleAppID),
) )
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
events, err := c.eventstore.Push(ctx, cmds...) events, err := c.eventstore.Push(ctx, cmds...)
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
return &domain.ObjectDetails{ return instanceID, &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(), Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(), EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: orgID, ResourceOwner: orgID,
}, nil }, nil
} }
func addInstance(a *instance.Aggregate, instanceName string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
instance.NewInstanceAddedEvent(ctx, &a.Aggregate, instanceName),
}, nil
}, nil
}
}
//SetIAMProject defines the command to set the id of the IAM project onto the instance //SetIAMProject defines the command to set the id of the IAM project onto the instance
func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation { func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {

View File

@ -67,6 +67,11 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
}, nil }, nil
} }
func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
return c.addInstanceDomain(a, domain, true)
}
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation { func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" { if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
@ -80,28 +85,32 @@ 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")
} }
events := []eventstore.Command{
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
}
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "") appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath) if appWriteModel.State.Exists() {
logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath) redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
consoleChangeEvent, err := project.NewOIDCConfigChangedEvent( logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
ctx, consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
ProjectAggregateFromWriteModel(&appWriteModel.WriteModel), ctx,
appWriteModel.AppID, ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
[]project.OIDCConfigChanges{ appWriteModel.AppID,
project.ChangeRedirectURIs(redirectUrls), []project.OIDCConfigChanges{
project.ChangePostLogoutRedirectURIs(logoutUrls), project.ChangeRedirectURIs(redirectUrls),
}, project.ChangePostLogoutRedirectURIs(logoutUrls),
) },
if err != nil { )
return nil, err if err != nil {
return nil, err
}
events = append(events, consoleChangeEvent)
} }
return []eventstore.Command{
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated), return events, nil
consoleChangeEvent,
}, nil
}, nil }, nil
} }
} }

View File

@ -291,7 +291,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err return err
} }
if !app.State.Exists() { if !app.State.Exists() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NoExisting") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NotExisting")
} }
if !app.IsOIDC() { if !app.IsOIDC() {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC") return caos_errs.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC")

View File

@ -23,5 +23,7 @@ func (f InstanceDomainState) Exists() bool {
} }
func NewGeneratedInstanceDomain(instanceName, iamDomain string) string { func NewGeneratedInstanceDomain(instanceName, iamDomain string) string {
//TODO: Add random number/string to be unique
instanceName = strings.TrimSpace(instanceName)
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain) return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain)
} }

View File

@ -34,8 +34,12 @@ func Migrate(ctx context.Context, es *eventstore.Eventstore, migration Migration
err = migration.Execute(ctx) err = migration.Execute(ctx)
logging.OnError(err).Error("migration failed") logging.OnError(err).Error("migration failed")
_, err = es.Push(ctx, setupDoneCmd(migration, err)) _, pushErr := es.Push(ctx, setupDoneCmd(migration, err))
return err logging.OnError(pushErr).Error("migration failed")
if err != nil {
return err
}
return pushErr
} }
func shouldExec(ctx context.Context, es *eventstore.Eventstore, migration Migration) (should bool, err error) { func shouldExec(ctx context.Context, es *eventstore.Eventstore, migration Migration) (should bool, err error) {

View File

@ -23,6 +23,14 @@ var (
name: projection.InstanceColumnID, name: projection.InstanceColumnID,
table: instanceTable, table: instanceTable,
} }
InstanceColumnName = Column{
name: projection.InstanceColumnName,
table: instanceTable,
}
InstanceColumnCreationDate = Column{
name: projection.InstanceColumnCreationDate,
table: instanceTable,
}
InstanceColumnChangeDate = Column{ InstanceColumnChangeDate = Column{
name: projection.InstanceColumnChangeDate, name: projection.InstanceColumnChangeDate,
table: instanceTable, table: instanceTable,
@ -62,9 +70,10 @@ var (
) )
type Instance struct { type Instance struct {
ID string ID string
ChangeDate time.Time ChangeDate time.Time
Sequence uint64 CreationDate time.Time
Sequence uint64
GlobalOrgID string GlobalOrgID string
IAMProjectID string IAMProjectID string
@ -76,6 +85,11 @@ type Instance struct {
Host string Host string
} }
type Instances struct {
SearchResponse
Instances []*Instance
}
func (i *Instance) InstanceID() string { func (i *Instance) InstanceID() string {
return i.ID return i.ID
} }
@ -101,6 +115,14 @@ type InstanceSearchQueries struct {
Queries []SearchQuery Queries []SearchQuery
} }
func NewInstanceIDsListSearchQuery(ids ...string) (SearchQuery, error) {
list := make([]interface{}, len(ids))
for i, value := range ids {
list[i] = value
}
return NewListQuery(InstanceColumnID, list, ListIn)
}
func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query) query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries { for _, q := range q.Queries {
@ -109,6 +131,24 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder
return query return query
} }
func (q *Queries) SearchInstances(ctx context.Context, queries *InstanceSearchQueries) (instances *Instances, err error) {
query, scan := prepareInstancesQuery()
stmt, args, err := queries.toQuery(query).ToSql()
if err != nil {
return nil, errors.ThrowInvalidArgument(err, "QUERY-M9fow", "Errors.Query.SQLStatement")
}
rows, err := q.client.QueryContext(ctx, stmt, args...)
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-3j98f", "Errors.Internal")
}
instances, err = scan(rows)
if err != nil {
return nil, err
}
return instances, err
}
func (q *Queries) Instance(ctx context.Context) (*Instance, error) { func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain()) stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain())
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
@ -146,6 +186,7 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) { func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return sq.Select( return sq.Select(
InstanceColumnID.identifier(), InstanceColumnID.identifier(),
InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(), InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(), InstanceColumnSequence.identifier(),
InstanceColumnGlobalOrgID.identifier(), InstanceColumnGlobalOrgID.identifier(),
@ -162,6 +203,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
lang := "" lang := ""
err := row.Scan( err := row.Scan(
&instance.ID, &instance.ID,
&instance.CreationDate,
&instance.ChangeDate, &instance.ChangeDate,
&instance.Sequence, &instance.Sequence,
&instance.GlobalOrgID, &instance.GlobalOrgID,
@ -182,3 +224,58 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
return instance, nil return instance, nil
} }
} }
func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
return sq.Select(
InstanceColumnID.identifier(),
InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(),
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(),
countColumn.identifier(),
).From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Instances, error) {
instances := make([]*Instance, 0)
var count uint64
for rows.Next() {
instance := new(Instance)
lang := ""
//TODO: Get Host
err := rows.Scan(
&instance.ID,
&instance.CreationDate,
&instance.ChangeDate,
&instance.Sequence,
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
&instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang,
&count,
)
if err != nil {
return nil, err
}
instances = append(instances, instance)
}
if err := rows.Close(); err != nil {
return nil, errors.ThrowInternal(err, "QUERY-8nlWW", "Errors.Query.CloseRows")
}
return &Instances{
Instances: instances,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}

View File

@ -34,6 +34,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.instances.id,`+ regexp.QuoteMeta(`SELECT projections.instances.id,`+
` projections.instances.creation_date,`+
` projections.instances.change_date,`+ ` projections.instances.change_date,`+
` projections.instances.sequence,`+ ` projections.instances.sequence,`+
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+
@ -64,6 +65,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.instances.id,`+ regexp.QuoteMeta(`SELECT projections.instances.id,`+
` projections.instances.creation_date,`+
` projections.instances.change_date,`+ ` projections.instances.change_date,`+
` projections.instances.sequence,`+ ` projections.instances.sequence,`+
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+
@ -76,6 +78,7 @@ func Test_InstancePrepares(t *testing.T) {
` FROM projections.instances`), ` FROM projections.instances`),
[]string{ []string{
"id", "id",
"creation_date",
"change_date", "change_date",
"sequence", "sequence",
"global_org_id", "global_org_id",
@ -89,6 +92,7 @@ func Test_InstancePrepares(t *testing.T) {
[]driver.Value{ []driver.Value{
"id", "id",
testNow, testNow,
testNow,
uint64(20211108), uint64(20211108),
"global-org-id", "global-org-id",
"project-id", "project-id",
@ -102,6 +106,7 @@ func Test_InstancePrepares(t *testing.T) {
}, },
object: &Instance{ object: &Instance{
ID: "id", ID: "id",
CreationDate: testNow,
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211108, Sequence: 20211108,
GlobalOrgID: "global-org-id", GlobalOrgID: "global-org-id",
@ -121,6 +126,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{ want: want{
sqlExpectations: mockQueryErr( sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.instances.id,`+ regexp.QuoteMeta(`SELECT projections.instances.id,`+
` projections.instances.creation_date,`+
` projections.instances.change_date,`+ ` projections.instances.change_date,`+
` projections.instances.sequence,`+ ` projections.instances.sequence,`+
` projections.instances.global_org_id,`+ ` projections.instances.global_org_id,`+

View File

@ -14,7 +14,9 @@ const (
InstanceProjectionTable = "projections.instances" InstanceProjectionTable = "projections.instances"
InstanceColumnID = "id" InstanceColumnID = "id"
InstanceColumnName = "name"
InstanceColumnChangeDate = "change_date" InstanceColumnChangeDate = "change_date"
InstanceColumnCreationDate = "creation_date"
InstanceColumnGlobalOrgID = "global_org_id" InstanceColumnGlobalOrgID = "global_org_id"
InstanceColumnProjectID = "iam_project_id" InstanceColumnProjectID = "iam_project_id"
InstanceColumnConsoleID = "console_client_id" InstanceColumnConsoleID = "console_client_id"
@ -36,10 +38,13 @@ func NewInstanceProjection(ctx context.Context, config crdb.StatementHandlerConf
config.InitCheck = crdb.NewTableCheck( config.InitCheck = crdb.NewTableCheck(
crdb.NewTable([]*crdb.Column{ crdb.NewTable([]*crdb.Column{
crdb.NewColumn(InstanceColumnID, crdb.ColumnTypeText), crdb.NewColumn(InstanceColumnID, crdb.ColumnTypeText),
crdb.NewColumn(InstanceColumnName, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnChangeDate, crdb.ColumnTypeTimestamp), crdb.NewColumn(InstanceColumnChangeDate, crdb.ColumnTypeTimestamp),
crdb.NewColumn(InstanceColumnCreationDate, crdb.ColumnTypeTimestamp),
crdb.NewColumn(InstanceColumnGlobalOrgID, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnGlobalOrgID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnProjectID, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnProjectID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnConsoleID, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnConsoleID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnConsoleAppID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnSequence, crdb.ColumnTypeInt64), crdb.NewColumn(InstanceColumnSequence, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceColumnSetUpStarted, crdb.ColumnTypeInt64, crdb.Default(0)), crdb.NewColumn(InstanceColumnSetUpStarted, crdb.ColumnTypeInt64, crdb.Default(0)),
crdb.NewColumn(InstanceColumnSetUpDone, crdb.ColumnTypeInt64, crdb.Default(0)), crdb.NewColumn(InstanceColumnSetUpDone, crdb.ColumnTypeInt64, crdb.Default(0)),
@ -57,6 +62,10 @@ func (p *InstanceProjection) reducers() []handler.AggregateReducer {
{ {
Aggregate: instance.AggregateType, Aggregate: instance.AggregateType,
EventRedusers: []handler.EventReducer{ EventRedusers: []handler.EventReducer{
{
Event: instance.InstanceAddedEventType,
Reduce: p.reduceInstanceAdded,
},
{ {
Event: instance.GlobalOrgSetEventType, Event: instance.GlobalOrgSetEventType,
Reduce: p.reduceGlobalOrgSet, Reduce: p.reduceGlobalOrgSet,
@ -86,19 +95,38 @@ func (p *InstanceProjection) reducers() []handler.AggregateReducer {
} }
} }
func (p *InstanceProjection) reduceInstanceAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.InstanceAddedEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-29nlS", "reduce.wrong.event.type %s", instance.InstanceAddedEventType)
}
return crdb.NewCreateStatement(
e,
[]handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnCreationDate, e.CreationDate()),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnName, e.Name),
},
), nil
}
func (p *InstanceProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) { func (p *InstanceProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.GlobalOrgSetEvent) e, ok := event.(*instance.GlobalOrgSetEvent)
if !ok { if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-2n9f2", "reduce.wrong.event.type %s", instance.GlobalOrgSetEventType) return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-2n9f2", "reduce.wrong.event.type %s", instance.GlobalOrgSetEventType)
} }
return crdb.NewUpsertStatement( return crdb.NewUpdateStatement(
e, e,
[]handler.Column{ []handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()), handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()), handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnGlobalOrgID, e.OrgID), handler.NewCol(InstanceColumnGlobalOrgID, e.OrgID),
}, },
[]handler.Condition{
handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
},
), nil ), nil
} }
@ -107,14 +135,16 @@ func (p *InstanceProjection) reduceIAMProjectSet(event eventstore.Event) (*handl
if !ok { if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.ProjectSetEventType) return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.ProjectSetEventType)
} }
return crdb.NewUpsertStatement( return crdb.NewUpdateStatement(
e, e,
[]handler.Column{ []handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()), handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()), handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnProjectID, e.ProjectID), handler.NewCol(InstanceColumnProjectID, e.ProjectID),
}, },
[]handler.Condition{
handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
},
), nil ), nil
} }
@ -123,15 +153,17 @@ func (p *InstanceProjection) reduceConsoleSet(event eventstore.Event) (*handler.
if !ok { if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Dgf11", "reduce.wrong.event.type %s", instance.ConsoleSetEventType) return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Dgf11", "reduce.wrong.event.type %s", instance.ConsoleSetEventType)
} }
return crdb.NewUpsertStatement( return crdb.NewUpdateStatement(
e, e,
[]handler.Column{ []handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
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), handler.NewCol(InstanceColumnConsoleAppID, e.AppID),
}, },
[]handler.Condition{
handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
},
), nil ), nil
} }
@ -140,14 +172,16 @@ func (p *InstanceProjection) reduceDefaultLanguageSet(event eventstore.Event) (*
if !ok { if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.DefaultLanguageSetEventType) return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.DefaultLanguageSetEventType)
} }
return crdb.NewUpsertStatement( return crdb.NewUpdateStatement(
e, e,
[]handler.Column{ []handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()), handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()), handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnDefaultLanguage, e.Language.String()), handler.NewCol(InstanceColumnDefaultLanguage, e.Language.String()),
}, },
[]handler.Condition{
handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
},
), nil ), nil
} }

View File

@ -57,7 +57,7 @@ func (p *InstanceDomainProjection) reducers() []handler.AggregateReducer {
Reduce: p.reduceDomainAdded, Reduce: p.reduceDomainAdded,
}, },
{ {
Event: instance.InstanceDomainAddedEventType, Event: instance.InstanceDomainPrimarySetEventType,
Reduce: p.reduceDomainPrimarySet, Reduce: p.reduceDomainPrimarySet,
}, },
{ {

View File

@ -20,7 +20,37 @@ func TestInstanceProjection_reduces(t *testing.T) {
args args args args
reduce func(event eventstore.Event) (*handler.Statement, error) reduce func(event eventstore.Event) (*handler.Statement, error)
want wantReduce want wantReduce
}{ }{{
name: "reduceInstanceAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.InstanceAddedEventType),
instance.AggregateType,
[]byte(`{"name": "Name"}`),
), instance.InstanceAddedEventMapper),
},
reduce: (&InstanceProjection{}).reduceInstanceAdded,
want: wantReduce{
projection: InstanceProjectionTable,
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.instances (id, creation_date, change_date, sequence, name) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{
"instance-id",
anyArg{},
anyArg{},
uint64(15),
"Name",
},
},
},
},
},
},
{ {
name: "reduceGlobalOrgSet", name: "reduceGlobalOrgSet",
args: args{ args: args{
@ -39,12 +69,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, global_org_id) VALUES ($1, $2, $3, $4)", expectedStmt: "UPDATE projections.instances SET (change_date, sequence, global_org_id) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id",
anyArg{}, anyArg{},
uint64(15), uint64(15),
"orgid", "orgid",
"instance-id",
}, },
}, },
}, },
@ -69,12 +99,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, iam_project_id) VALUES ($1, $2, $3, $4)", expectedStmt: "UPDATE projections.instances SET (change_date, sequence, iam_project_id) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id",
anyArg{}, anyArg{},
uint64(15), uint64(15),
"project-id", "project-id",
"instance-id",
}, },
}, },
}, },
@ -99,12 +129,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, default_language) VALUES ($1, $2, $3, $4)", expectedStmt: "UPDATE projections.instances SET (change_date, sequence, default_language) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id",
anyArg{}, anyArg{},
uint64(15), uint64(15),
"en", "en",
"instance-id",
}, },
}, },
}, },

View File

@ -96,7 +96,7 @@ func NewDomainPrimarySetEvent(ctx context.Context, aggregate *eventstore.Aggrega
} }
func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) { func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) {
orgDomainAdded := &DomainAddedEvent{ orgDomainAdded := &DomainPrimarySetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),
} }
err := json.Unmarshal(event.Data, orgDomainAdded) err := json.Unmarshal(event.Data, orgDomainAdded)

View File

@ -2525,34 +2525,6 @@ service AdminService {
}; };
} }
//Truncates the delta of the change stream
// be carefull with this function because ZITADEL has to
// recompute the deltas after they got cleared.
// Search requests will return wrong results until all deltas are recomputed
rpc ClearView(ClearViewRequest) returns (ClearViewResponse) {
option (google.api.http) = {
post: "/views/{database}/{view_name}";
};
option (zitadel.v1.auth_option) = {
permission: "iam.write";
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "views";
external_docs: {
url: "https://docs.zitadel.ch/concepts#Software_Architecture";
description: "details of ZITADEL's event driven software concepts";
};
responses: {
key: "200";
value: {
description: "View cleared";
};
};
};
}
//Returns event descriptions which cannot be processed. //Returns event descriptions which cannot be processed.
// It's possible that some events need some retries. // It's possible that some events need some retries.
// For example if the SMTP-API wasn't able to send an email at the first time // For example if the SMTP-API wasn't able to send an email at the first time
@ -4512,34 +4484,6 @@ message ListViewsResponse {
repeated View result = 1; repeated View result = 1;
} }
message ClearViewRequest {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
required: ["database", "view_name"]
};
};
string database = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"adminapi\"";
min_length: 1;
max_length: 200;
}
];
string view_name = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"iam_members\"";
min_length: 1;
max_length: 200;
}
];
}
//This is an empty response
message ClearViewResponse {}
//This is an empty request //This is an empty request
message ListFailedEventsRequest {} message ListFailedEventsRequest {}

View File

@ -25,11 +25,6 @@ message Instance {
example: "\"ZITADEL\""; example: "\"ZITADEL\"";
} }
]; ];
string version = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"v1.0.0\"";
}
];
} }
enum State { enum State {
@ -44,31 +39,19 @@ message Query {
oneof query { oneof query {
option (validate.required) = true; option (validate.required) = true;
IdQuery id_query = 1; IdsQuery id_query = 1;
StateQuery state_query = 2;
} }
} }
//IdQuery is always equals //IdQuery is always equals
message IdQuery { message IdsQuery {
string id = 1 [ repeated string ids = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "4820840938402429"; description: "4820840938402429";
} }
]; ];
} }
//StateQuery is always equals
message StateQuery {
State state = 1 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the instance";
}
];
}
enum FieldName { enum FieldName {
FIELD_NAME_UNSPECIFIED = 0; FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1; FIELD_NAME_ID = 1;

View File

@ -105,7 +105,7 @@ service SystemService {
// Returns a list of ZITADEL instances/tenants // Returns a list of ZITADEL instances/tenants
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) { rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/instances" post: "/instances/_search"
body: "*" body: "*"
}; };
} }
@ -134,17 +134,11 @@ service SystemService {
}; };
} }
// Returns the usage metrics of an instance
rpc GetUsage(GetUsageRequest) returns (GetUsageResponse) {
option (google.api.http) = {
get: "/instances/{id}/usage";
};
}
// Returns the custom domains of an instance // Returns the custom domains of an instance
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) { rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
option (google.api.http) = { option (google.api.http) = {
get: "/instances/{id}/domains"; post: "/instances/{id}/domains/_search";
body: "*"
}; };
} }
@ -178,6 +172,7 @@ service SystemService {
rpc ListViews(ListViewsRequest) returns (ListViewsResponse) { rpc ListViews(ListViewsRequest) returns (ListViewsResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/views/_search"; post: "/views/_search";
body: "*"
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
@ -229,6 +224,7 @@ service SystemService {
rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) { rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/failedevents/_search"; post: "/failedevents/_search";
body: "*"
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
@ -322,19 +318,19 @@ message GetInstanceResponse {
message AddInstanceRequest { message AddInstanceRequest {
string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string first_org_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
string custom_domain = 3 [(validate.rules).string = {max_len: 200}]; string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
string owner_first_name = 4 [(validate.rules).string = {min_len: 1, max_len: 200}]; string owner_first_name = 4 [(validate.rules).string = {max_len: 200}];
string owner_last_name = 5 [(validate.rules).string = {min_len: 1, max_len: 200}]; string owner_last_name = 5 [(validate.rules).string = {max_len: 200}];
string owner_email = 6 [(validate.rules).string = {min_len: 1, max_len: 200}]; string owner_email = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
string owner_username = 7 [(validate.rules).string = {min_len: 1, max_len: 200}]; string owner_username = 7 [(validate.rules).string = {max_len: 200}];
string password = 8 [(validate.rules).string = {min_len: 1, max_len: 200}]; uint64 request_limit = 8;
uint64 request_limit = 9; uint64 action_mins_limit = 9;
uint64 action_mins_limit = 10;
} }
message AddInstanceResponse { message AddInstanceResponse {
string id = 1; string id = 1;
zitadel.v1.ObjectDetails details = 2;
} }
message RemoveInstanceRequest { message RemoveInstanceRequest {