feat: handle instance from context (#3382)

* commander

* commander

* selber!

* move to packages

* fix(errors): implement Is interface

* test: command

* test: commands

* add init steps

* setup tenant

* add default step yaml

* possibility to set password

* merge v2 into v2-commander

* fix: rename iam command side to instance

* fix: rename iam command side to instance

* fix: rename iam command side to instance

* fix: rename iam command side to instance

* fix: search query builder can filter events in memory

* fix: filters for add member

* fix(setup): add `ExternalSecure` to config

* chore: name iam to instance

* fix: matching

* remove unsued func

* base url

* base url

* test(command): filter funcs

* test: commands

* fix: rename orgiampolicy to domain policy

* start from init

* commands

* config

* fix indexes and add constraints

* fixes

* fix: merge conflicts

* fix: protos

* fix: md files

* setup

* add deprecated org iam policy again

* typo

* fix search query

* fix filter

* Apply suggestions from code review

* remove custom org from org setup

* add todos for verification

* change apps creation

* simplify package structure

* fix error

* move preparation helper for tests

* fix unique constraints

* fix config mapping in setup

* fix error handling in encryption_keys.go

* fix projection config

* fix query from old views to projection

* fix setup of mgmt api

* set iam project and fix instance projection

* fix tokens view

* fix steps.yaml and defaults.yaml

* fix projections

* change instance context to interface

* instance interceptors and additional events in setup

* cleanup

* tests for interceptors

* fix label policy

* add todo

* single api endpoint in environment.json

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Livio Amstutz
2022-03-29 11:53:19 +02:00
committed by GitHub
parent c5b99274d7
commit 958362e6c9
101 changed files with 1520 additions and 274 deletions

View File

@@ -44,6 +44,7 @@ func New(
},
authZ internal_authz.Config,
externalSecure bool,
http2HostName string,
) *API {
verifier := internal_authz.Start(repo)
api := &API{
@@ -53,7 +54,7 @@ func New(
router: router,
externalSecure: externalSecure,
}
api.grpcServer = server.CreateServer(api.verifier, authZ, repo.Queries)
api.grpcServer = server.CreateServer(api.verifier, authZ, repo.Queries, http2HostName)
api.routeGRPC()
api.RegisterHandler("/debug", api.healthHandler())

View File

@@ -31,10 +31,6 @@ func (ctxData CtxData) IsZero() bool {
return ctxData.UserID == "" || ctxData.OrgID == ""
}
type Instance struct {
ID string
}
type Grants []*Grant
type Grant struct {
@@ -116,15 +112,6 @@ func GetCtxData(ctx context.Context) CtxData {
return ctxData
}
func GetInstance(ctx context.Context) Instance {
instance, _ := ctx.Value(instanceKey).(Instance)
return instance
}
func WithInstance(ctx context.Context, instance Instance) context.Context {
return context.WithValue(ctx, instanceKey, instance)
}
func GetRequestPermissionsFromCtx(ctx context.Context) []string {
ctxPermission, _ := ctx.Value(requestPermissionsKey).([]string)
return ctxPermission

View File

@@ -0,0 +1,51 @@
package authz
import (
"context"
)
var (
emptyInstance = &instance{}
)
type Instance interface {
InstanceID() string
ProjectID() string
ConsoleClientID() string
}
type InstanceVerifier interface {
InstanceByHost(context.Context, string) (Instance, error)
}
type instance struct {
ID string
}
func (i *instance) InstanceID() string {
return i.ID
}
func (i *instance) ProjectID() string {
return ""
}
func (i *instance) ConsoleClientID() string {
return ""
}
func GetInstance(ctx context.Context) Instance {
instance, ok := ctx.Value(instanceKey).(Instance)
if !ok {
return emptyInstance
}
return instance
}
func WithInstance(ctx context.Context, instance Instance) context.Context {
return context.WithValue(ctx, instanceKey, instance)
}
func WithInstanceID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, instanceKey, &instance{ID: id})
}

View File

@@ -0,0 +1,80 @@
package authz
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Instance(t *testing.T) {
type args struct {
ctx context.Context
}
type res struct {
instanceID string
projectID string
consoleID string
}
tests := []struct {
name string
args args
res res
}{
{
"empty context",
args{
context.Background(),
},
res{
instanceID: "",
projectID: "",
consoleID: "",
},
},
{
"WithInstanceID",
args{
WithInstanceID(context.Background(), "id"),
},
res{
instanceID: "id",
projectID: "",
consoleID: "",
},
},
{
"WithInstance",
args{
WithInstance(context.Background(), &mockInstance{}),
},
res{
instanceID: "instanceID",
projectID: "projectID",
consoleID: "consoleID",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetInstance(tt.args.ctx)
assert.Equal(t, tt.res.instanceID, got.InstanceID())
assert.Equal(t, tt.res.projectID, got.ProjectID())
assert.Equal(t, tt.res.consoleID, got.ConsoleClientID())
})
}
}
type mockInstance struct{}
func (m *mockInstance) InstanceID() string {
return "instanceID"
}
func (m *mockInstance) ProjectID() string {
return "projectID"
}
func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}

View File

@@ -152,14 +152,10 @@ func (s *Server) ListMyProjectOrgs(ctx context.Context, req *auth_pb.ListMyProje
return nil, err
}
iam, err := s.query.Instance(ctx)
if err != nil {
return nil, err
}
ctxData := authz.GetCtxData(ctx)
//client of user is not in project of ZITADEL
if ctxData.ProjectID != iam.IAMProjectID {
if ctxData.ProjectID != authz.GetInstance(ctx).ProjectID() {
userGrantProjectID, err := query.NewUserGrantProjectIDSearchQuery(ctxData.ProjectID)
if err != nil {
return nil, err

View File

@@ -0,0 +1,50 @@
package middleware
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"github.com/caos/zitadel/internal/api/authz"
)
type InstanceVerifier interface {
GetInstance(ctx context.Context)
}
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return setInstance(ctx, req, info, handler, verifier, headerName)
}
}
func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string) (_ interface{}, err error) {
host, err := hostNameFromContext(ctx, headerName)
if err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
instance, err := verifier.InstanceByHost(ctx, host)
if err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
return handler(authz.WithInstance(ctx, instance), req)
}
func hostNameFromContext(ctx context.Context, headerName string) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", fmt.Errorf("cannot read metadata")
}
host, ok := md[headerName]
if !ok {
return "", fmt.Errorf("cannot find header: %v", headerName)
}
if len(host) != 1 {
return "", fmt.Errorf("invalid host header: %v", host)
}
return host[0], nil
}

View File

@@ -0,0 +1,174 @@
package middleware
import (
"context"
"fmt"
"reflect"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/caos/zitadel/internal/api/authz"
)
func Test_hostNameFromContext(t *testing.T) {
type args struct {
ctx context.Context
headerName string
}
type res struct {
want string
err bool
}
tests := []struct {
name string
args args
res res
}{
{
"empty context, error",
args{
ctx: context.Background(),
headerName: "header",
},
res{
want: "",
err: true,
},
},
{
"header not found",
args{
ctx: metadata.NewIncomingContext(context.Background(), nil),
headerName: "header",
},
res{
want: "",
err: true,
},
},
{
"header not found",
args{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("header", "value")),
headerName: "header",
},
res{
want: "value",
err: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := hostNameFromContext(tt.args.ctx, tt.args.headerName)
if (err != nil) != tt.res.err {
t.Errorf("hostNameFromContext() error = %v, wantErr %v", err, tt.res.err)
return
}
if got != tt.res.want {
t.Errorf("hostNameFromContext() got = %v, want %v", got, tt.res.want)
}
})
}
}
func Test_setInstance(t *testing.T) {
type args struct {
ctx context.Context
req interface{}
info *grpc.UnaryServerInfo
handler grpc.UnaryHandler
verifier authz.InstanceVerifier
headerName string
}
type res struct {
want interface{}
err bool
}
tests := []struct {
name string
args args
res res
}{
{
"hostname not found, error",
args{
ctx: context.Background(),
},
res{
want: nil,
err: true,
},
},
{
"invalid host, error",
args{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("header", "host2")),
req: &mockRequest{},
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
},
res{
want: nil,
err: true,
},
},
{
"valid host",
args{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("header", "host")),
req: &mockRequest{},
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
handler: func(ctx context.Context, req interface{}) (interface{}, error) {
return req, nil
},
},
res{
want: &mockRequest{},
err: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := setInstance(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier, tt.args.headerName)
if (err != nil) != tt.res.err {
t.Errorf("setInstance() error = %v, wantErr %v", err, tt.res.err)
return
}
if !reflect.DeepEqual(got, tt.res.want) {
t.Errorf("setInstance() got = %v, want %v", got, tt.res.want)
}
})
}
}
type mockRequest struct{}
type mockInstanceVerifier struct {
host string
}
func (m *mockInstanceVerifier) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
if host != m.host {
return nil, fmt.Errorf("invalid host")
}
return &mockInstance{}, nil
}
type mockInstance struct{}
func (m *mockInstance) InstanceID() string {
return "instanceID"
}
func (m *mockInstance) ProjectID() string {
return "projectID"
}
func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}

View File

@@ -20,7 +20,7 @@ type Server interface {
AuthMethods() authz.MethodMapping
}
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, queries *query.Queries) *grpc.Server {
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, queries *query.Queries, hostHeaderName string) *grpc.Server {
metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode}
return grpc.NewServer(
grpc.UnaryInterceptor(
@@ -30,6 +30,7 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
middleware.SentryHandler(),
middleware.NoCacheInterceptor(),
middleware.ErrorHandler(),
middleware.InstanceInterceptor(queries, hostHeaderName),
middleware.AuthorizationInterceptor(verifier, authConfig),
middleware.TranslationHandler(queries),
middleware.ValidationHandler(),

View File

@@ -0,0 +1,65 @@
package middleware
import (
"context"
"fmt"
"net/http"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type instanceInterceptor struct {
verifier authz.InstanceVerifier
headerName string
}
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string) *instanceInterceptor {
return &instanceInterceptor{
verifier: verifier,
headerName: headerName,
}
}
func (a *instanceInterceptor) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := setInstance(r, a.verifier, a.headerName)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func (a *instanceInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, err := setInstance(r, a.verifier, a.headerName)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
}
}
func setInstance(r *http.Request, verifier authz.InstanceVerifier, headerName string) (_ context.Context, err error) {
ctx := r.Context()
authCtx, span := tracing.NewServerInterceptorSpan(ctx)
defer func() { span.EndWithError(err) }()
host := r.Header.Get(headerName)
if host == "" {
return nil, fmt.Errorf("host header %s not found", headerName)
}
instance, err := verifier.InstanceByHost(authCtx, host)
if err != nil {
return nil, err
}
span.End()
return authz.WithInstance(ctx, instance), nil
}

View File

@@ -0,0 +1,258 @@
package middleware
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/api/authz"
)
func Test_instanceInterceptor_Handler(t *testing.T) {
type fields struct {
verifier authz.InstanceVerifier
headerName string
}
type args struct {
request *http.Request
}
type res struct {
statusCode int
context context.Context
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"setInstance error",
fields{
verifier: &mockInstanceVerifier{},
headerName: "header",
},
args{
request: httptest.NewRequest("", "/url", nil),
},
res{
statusCode: 403,
context: nil,
},
},
{
"setInstance ok",
fields{
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
},
args{
request: func() *http.Request {
r := httptest.NewRequest("", "/url", nil)
r.Header.Set("header", "host")
return r
}(),
},
res{
statusCode: 200,
context: authz.WithInstance(context.Background(), &mockInstance{}),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &instanceInterceptor{
verifier: tt.fields.verifier,
headerName: tt.fields.headerName,
}
next := &testHandler{}
got := a.HandlerFunc(next.ServeHTTP)
rr := httptest.NewRecorder()
got.ServeHTTP(rr, tt.args.request)
assert.Equal(t, tt.res.statusCode, rr.Code)
assert.Equal(t, tt.res.context, next.context)
})
}
}
func Test_instanceInterceptor_HandlerFunc(t *testing.T) {
type fields struct {
verifier authz.InstanceVerifier
headerName string
}
type args struct {
request *http.Request
}
type res struct {
statusCode int
context context.Context
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"setInstance error",
fields{
verifier: &mockInstanceVerifier{},
headerName: "header",
},
args{
request: httptest.NewRequest("", "/url", nil),
},
res{
statusCode: 403,
context: nil,
},
},
{
"setInstance ok",
fields{
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
},
args{
request: func() *http.Request {
r := httptest.NewRequest("", "/url", nil)
r.Header.Set("header", "host")
return r
}(),
},
res{
statusCode: 200,
context: authz.WithInstance(context.Background(), &mockInstance{}),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &instanceInterceptor{
verifier: tt.fields.verifier,
headerName: tt.fields.headerName,
}
next := &testHandler{}
got := a.HandlerFunc(next.ServeHTTP)
rr := httptest.NewRecorder()
got.ServeHTTP(rr, tt.args.request)
assert.Equal(t, tt.res.statusCode, rr.Code)
assert.Equal(t, tt.res.context, next.context)
})
}
}
func Test_setInstance(t *testing.T) {
type args struct {
r *http.Request
verifier authz.InstanceVerifier
headerName string
}
type res struct {
want context.Context
err bool
}
tests := []struct {
name string
args args
res res
}{
{
"hostname not found, error",
args{
r: func() *http.Request {
r := httptest.NewRequest("", "/url", nil)
return r
}(),
verifier: &mockInstanceVerifier{},
headerName: "",
},
res{
want: nil,
err: true,
},
},
{
"invalid host, error",
args{
r: func() *http.Request {
r := httptest.NewRequest("", "/url", nil)
r.Header.Set("header", "host2")
return r
}(),
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
},
res{
want: nil,
err: true,
},
},
{
"valid host",
args{
r: func() *http.Request {
r := httptest.NewRequest("", "/url", nil)
r.Header.Set("header", "host")
return r
}(),
verifier: &mockInstanceVerifier{"host"},
headerName: "header",
},
res{
want: authz.WithInstance(context.Background(), &mockInstance{}),
err: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := setInstance(tt.args.r, tt.args.verifier, tt.args.headerName)
if (err != nil) != tt.res.err {
t.Errorf("setInstance() error = %v, wantErr %v", err, tt.res.err)
return
}
if !reflect.DeepEqual(got, tt.res.want) {
t.Errorf("setInstance() got = %v, want %v", got, tt.res.want)
}
})
}
}
type testHandler struct {
context context.Context
}
func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.context = r.Context()
}
type mockInstanceVerifier struct {
host string
}
func (m *mockInstanceVerifier) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
if host != m.host {
return nil, fmt.Errorf("invalid host")
}
return &mockInstance{}, nil
}
type mockInstance struct{}
func (m *mockInstance) InstanceID() string {
return "instanceID"
}
func (m *mockInstance) ProjectID() string {
return "projectID"
}
func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}

View File

@@ -46,7 +46,7 @@ func (o *OPStorage) AuthRequestByID(ctx context.Context, id string) (_ op.AuthRe
if !ok {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-D3g21", "no user agent id")
}
instanceID := authz.GetInstance(ctx).ID
instanceID := authz.GetInstance(ctx).InstanceID()
resp, err := o.repo.AuthRequestByIDCheckLoggedIn(ctx, id, userAgentID, instanceID)
if err != nil {
return nil, err
@@ -58,7 +58,7 @@ func (o *OPStorage) AuthRequestByCode(ctx context.Context, code string) (_ op.Au
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
instanceID := authz.GetInstance(ctx).ID
instanceID := authz.GetInstance(ctx).InstanceID()
resp, err := o.repo.AuthRequestByCode(ctx, code, instanceID)
if err != nil {
return nil, err
@@ -73,7 +73,7 @@ func (o *OPStorage) SaveAuthCode(ctx context.Context, id, code string) (err erro
if !ok {
return errors.ThrowPreconditionFailed(nil, "OIDC-Dgus2", "no user agent id")
}
instanceID := authz.GetInstance(ctx).ID
instanceID := authz.GetInstance(ctx).InstanceID()
return o.repo.SaveAuthCode(ctx, id, code, userAgentID, instanceID)
}
@@ -81,7 +81,7 @@ func (o *OPStorage) DeleteAuthRequest(ctx context.Context, id string) (err error
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
instanceID := authz.GetInstance(ctx).ID
instanceID := authz.GetInstance(ctx).InstanceID()
return o.repo.DeleteAuthRequest(ctx, id, instanceID)
}

View File

@@ -133,7 +133,7 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest,
SelectedIDPConfigID: GetSelectedIDPIDFromScopes(authReq.Scopes),
MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge),
UserID: userID,
InstanceID: authz.GetInstance(ctx).ID,
InstanceID: authz.GetInstance(ctx).InstanceID(),
Request: &domain.AuthRequestOIDC{
Scopes: authReq.Scopes,
ResponseType: ResponseTypeToBusiness(authReq.ResponseType),

View File

@@ -83,13 +83,13 @@ type OPStorage struct {
assetAPIPrefix string
}
func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
}
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan)
options, err := createOptions(config, userAgentCookie)
options, err := createOptions(config, userAgentCookie, instanceHandler)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
}
@@ -131,12 +131,13 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryp
return opConfig, nil
}
func createOptions(config Config, userAgentCookie func(http.Handler) http.Handler) ([]op.Option, error) {
func createOptions(config Config, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) {
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
interceptor := op.WithHttpInterceptors(
middleware.MetricsHandler(metricTypes),
middleware.TelemetryHandler(),
middleware.NoCacheInterceptor,
instanceHandler,
userAgentCookie,
http_utils.CopyHeadersToContext,
)

View File

@@ -11,6 +11,8 @@ import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http/middleware"
)
@@ -51,12 +53,7 @@ func (i *spaHandler) Open(name string) (http.File, error) {
return i.fileSystem.Open("/index.html")
}
func Start(config Config, domain, url, issuer, clientID string) (http.Handler, error) {
environmentJSON, err := createEnvironmentJSON(url, issuer, clientID)
if err != nil {
return nil, fmt.Errorf("unable to marshal env for console: %w", err)
}
func Start(config Config, domain, url, issuer string, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
consoleDir := consoleDefaultDir
if config.ConsoleOverwriteDir != "" {
consoleDir = config.ConsoleOverwriteDir
@@ -73,10 +70,20 @@ func Start(config Config, domain, url, issuer, clientID string) (http.Handler, e
handler := &http.ServeMux{}
handler.Handle("/", cache(security(http.FileServer(&spaHandler{consoleHTTPDir}))))
handler.Handle(envRequestPath, cache(security(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write(environmentJSON)
handler.Handle(envRequestPath, instanceHandler(cache(security(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
instance := authz.GetInstance(r.Context())
if instance.InstanceID() == "" {
http.Error(w, "empty instanceID", http.StatusInternalServerError)
return
}
environmentJSON, err := createEnvironmentJSON(url, issuer, instance.ConsoleClientID())
if err != nil {
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
return
}
_, err = w.Write(environmentJSON)
logging.OnError(err).Error("error serving environment.json")
}))))
})))))
return handler, nil
}
@@ -92,23 +99,15 @@ func csp(zitadelDomain string) *middleware.CSP {
return &csp
}
func createEnvironmentJSON(url, issuer, clientID string) ([]byte, error) {
func createEnvironmentJSON(api, issuer, clientID string) ([]byte, error) {
environment := struct {
AuthServiceUrl string `json:"authServiceUrl,omitempty"`
MgmtServiceUrl string `json:"mgmtServiceUrl,omitempty"`
AdminServiceUrl string `json:"adminServiceUrl,omitempty"`
SubscriptionServiceUrl string `json:"subscriptionServiceUrl,omitempty"`
AssetServiceUrl string `json:"assetServiceUrl,omitempty"`
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientid,omitempty"`
API string `json:"api,omitempty"`
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientid,omitempty"`
}{
AuthServiceUrl: url,
MgmtServiceUrl: url,
AdminServiceUrl: url,
SubscriptionServiceUrl: url,
AssetServiceUrl: url,
Issuer: issuer,
ClientID: clientID,
API: api,
Issuer: issuer,
ClientID: clientID,
}
return json.Marshal(environment)
}

View File

@@ -20,7 +20,7 @@ func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) {
return nil, nil
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
return l.authRepo.AuthRequestByID(r.Context(), authRequestID, userAgentID, instanceID)
}

View File

@@ -89,7 +89,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID, instanceID)
if err != nil {
l.renderLogin(w, r, authReq, err)
@@ -142,7 +142,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID, instanceID)
if err != nil {
l.renderError(w, r, authReq, err)
@@ -202,7 +202,7 @@ func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.R
return
}
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.CheckExternalUserLogin(setContext(r.Context(), ""), authReq.ID, userAgentID, instanceID, externalUser, domain.BrowserInfoFromRequest(r))
if err != nil {
if errors.IsNotFound(err) {
@@ -329,7 +329,7 @@ func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http
return
} else if data.ResetLinking {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.ResetLinkingUsers(r.Context(), authReq.ID, userAgentID, instanceID)
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, nil, err)
@@ -368,7 +368,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
if len(authReq.LinkingUsers) == 0 {
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
return

View File

@@ -68,7 +68,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID, instanceID)
if err != nil {
l.renderLogin(w, r, authReq, err)
@@ -89,7 +89,7 @@ func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Re
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID, instanceID)
if err != nil {
l.renderError(w, r, authReq, err)

View File

@@ -45,7 +45,7 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, nil, err)
return
}
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.AuthRequestID, userAgentID, instanceID)
if err != nil {
l.renderError(w, r, authReq, err)
@@ -209,7 +209,7 @@ func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, nil, err)
return
}
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.AuthRequestID, userAgentID, instanceID)
if err != nil {
l.renderError(w, r, authReq, err)

View File

@@ -14,7 +14,7 @@ const (
func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID, instanceID, domain.BrowserInfoFromRequest(r))
l.renderLinkUsersDone(w, r, authReq, err)
}

View File

@@ -66,7 +66,8 @@ func CreateLogin(config Config,
baseURL,
oidcAuthCallbackURL string,
externalSecure bool,
userAgentCookie mux.MiddlewareFunc,
userAgentCookie,
instanceHandler mux.MiddlewareFunc,
userCodeAlg crypto.EncryptionAlgorithm,
idpConfigAlg crypto.EncryptionAlgorithm,
csrfCookieKey []byte,
@@ -104,7 +105,7 @@ func CreateLogin(config Config,
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
}
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
login.router = CreateRouter(login, statikFS, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
login.parser = form.NewParser()
return login, nil

View File

@@ -60,7 +60,7 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
loginName := data.LoginName
err = l.authRepo.CheckLoginName(r.Context(), authReq.ID, loginName, userAgentID, instanceID)
if err != nil {

View File

@@ -36,7 +36,7 @@ func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) {
}
if data.MFAType == domain.MFATypeOTP {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Code, userAgentID, instanceID, domain.BrowserInfoFromRequest(r))
if err != nil {
l.renderMFAVerifySelected(w, r, authReq, step, domain.MFATypeOTP, err)

View File

@@ -30,7 +30,7 @@ func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, au
var webAuthNLogin *domain.WebAuthNLogin
if err == nil {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
webAuthNLogin, err = l.authRepo.BeginMFAU2FLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, instanceID)
}
if err != nil {
@@ -72,7 +72,7 @@ func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, instanceID, credData, domain.BrowserInfoFromRequest(r))
if err != nil {
l.renderU2FVerification(w, r, authReq, step.MFAProviders, err)

View File

@@ -95,7 +95,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.SelectUser(r.Context(), authRequest.ID, user.AggregateID, userAgentID, instanceID)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)

View File

@@ -39,7 +39,7 @@ func (l *Login) handleSelectUser(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
instanceID := authz.GetInstance(r.Context()).ID
instanceID := authz.GetInstance(r.Context()).InstanceID()
err = l.authRepo.SelectUser(r.Context(), authSession.ID, data.UserID, userAgentID, instanceID)
if err != nil {
l.renderError(w, r, authSession, err)