feat(login): use default org for login without provided org context (#6625)

* start feature flags

* base feature events on domain const

* setup default features

* allow setting feature in system api

* allow setting feature in admin api

* set settings in login based on feature

* fix rebasing

* unit tests

* i18n

* update policy after domain discovery

* some changes from review

* check feature and value type

* check feature and value type
This commit is contained in:
Livio Spring
2023-09-29 10:21:32 +02:00
committed by GitHub
parent d01f4d229f
commit 68bfab2fb3
41 changed files with 875 additions and 38 deletions

View File

@@ -0,0 +1,20 @@
package admin
import (
"context"
object_pb "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
)
func (s *Server) ActivateFeatureLoginDefaultOrg(ctx context.Context, _ *admin_pb.ActivateFeatureLoginDefaultOrgRequest) (*admin_pb.ActivateFeatureLoginDefaultOrgResponse, error) {
details, err := s.command.SetBooleanInstanceFeature(ctx, domain.FeatureLoginDefaultOrg, true)
if err != nil {
return nil, err
}
return &admin_pb.ActivateFeatureLoginDefaultOrgResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}

View File

@@ -0,0 +1,34 @@
package system
import (
"context"
object_pb "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
)
func (s *Server) SetInstanceFeature(ctx context.Context, req *system_pb.SetInstanceFeatureRequest) (*system_pb.SetInstanceFeatureResponse, error) {
details, err := s.setInstanceFeature(ctx, req)
if err != nil {
return nil, err
}
return &system_pb.SetInstanceFeatureResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) setInstanceFeature(ctx context.Context, req *system_pb.SetInstanceFeatureRequest) (*domain.ObjectDetails, error) {
feat := domain.Feature(req.FeatureId)
if !feat.IsAFeature() {
return nil, errors.ThrowInvalidArgument(nil, "SYST-SGV45", "Errors.Feature.NotExisting")
}
switch t := req.Value.(type) {
case *system_pb.SetInstanceFeatureRequest_Bool:
return s.command.SetBooleanInstanceFeature(ctx, feat, t.Bool)
default:
return nil, errors.ThrowInvalidArgument(nil, "SYST-dag5g", "Errors.Feature.TypeNotSupported")
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
"github.com/zitadel/zitadel/feature"
"github.com/zitadel/zitadel/internal/api/authz"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/http/middleware"
@@ -40,6 +41,7 @@ type Login struct {
samlAuthCallbackURL func(context.Context, string) string
idpConfigAlg crypto.EncryptionAlgorithm
userCodeAlg crypto.EncryptionAlgorithm
featureCheck feature.Checker
}
type Config struct {
@@ -76,6 +78,7 @@ func CreateLogin(config Config,
userCodeAlg crypto.EncryptionAlgorithm,
idpConfigAlg crypto.EncryptionAlgorithm,
csrfCookieKey []byte,
featureCheck feature.Checker,
) (*Login, error) {
login := &Login{
oidcAuthCallbackURL: oidcAuthCallbackURL,
@@ -88,6 +91,7 @@ func CreateLogin(config Config,
authRepo: authRepo,
idpConfigAlg: idpConfigAlg,
userCodeAlg: userCodeAlg,
featureCheck: featureCheck,
}
statikFS, err := fs.NewWithNamespace("login")
if err != nil {

View File

@@ -506,25 +506,19 @@ func (l *Login) getOrgID(r *http.Request, authReq *domain.AuthRequest) string {
}
func (l *Login) getPrivateLabelingID(r *http.Request, authReq *domain.AuthRequest) string {
privateLabelingOrgID := authz.GetInstance(r.Context()).InstanceID()
if authReq == nil {
if id := r.FormValue(queryOrgID); id != "" {
return id
}
return privateLabelingOrgID
defaultID := authz.GetInstance(r.Context()).DefaultOrganisationID()
f, err := l.featureCheck.CheckInstanceBooleanFeature(r.Context(), domain.FeatureLoginDefaultOrg)
logging.OnError(err).Warnf("could not check feature %s", domain.FeatureLoginDefaultOrg)
if !f.Boolean {
defaultID = authz.GetInstance(r.Context()).InstanceID()
}
if authReq.PrivateLabelingSetting != domain.PrivateLabelingSettingUnspecified {
privateLabelingOrgID = authReq.ApplicationResourceOwner
if authReq != nil {
return authReq.PrivateLabelingOrgID(defaultID)
}
if authReq.PrivateLabelingSetting == domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || authReq.PrivateLabelingSetting == domain.PrivateLabelingSettingUnspecified {
if authReq.UserOrgID != "" {
privateLabelingOrgID = authReq.UserOrgID
}
if id := r.FormValue(queryOrgID); id != "" {
return id
}
if authReq.RequestedOrgID != "" {
privateLabelingOrgID = authReq.RequestedOrgID
}
return privateLabelingOrgID
return defaultID
}
func (l *Login) getOrgName(authReq *domain.AuthRequest) string {

View File

@@ -7,6 +7,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/feature"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
cache "github.com/zitadel/zitadel/internal/auth_request/repository"
@@ -52,6 +53,8 @@ type AuthRequestRepo struct {
ApplicationProvider applicationProvider
CustomTextProvider customTextProvider
FeatureCheck feature.Checker
IdGenerator id.Generator
}
@@ -650,7 +653,12 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A
orgID = request.UserOrgID
}
if orgID == "" {
orgID = authz.GetInstance(ctx).InstanceID()
orgID = authz.GetInstance(ctx).DefaultOrganisationID()
f, err := repo.FeatureCheck.CheckInstanceBooleanFeature(ctx, domain.FeatureLoginDefaultOrg)
logging.WithFields("authReq", request.ID).OnError(err).Warnf("could not check feature %s", domain.FeatureLoginDefaultOrg)
if !f.Boolean {
orgID = authz.GetInstance(ctx).InstanceID()
}
}
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)
@@ -671,19 +679,7 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A
return err
}
request.PrivacyPolicy = privacyPolicy
privateLabelingOrgID := authz.GetInstance(ctx).InstanceID()
if request.PrivateLabelingSetting != domain.PrivateLabelingSettingUnspecified {
privateLabelingOrgID = request.ApplicationResourceOwner
}
if request.PrivateLabelingSetting == domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || request.PrivateLabelingSetting == domain.PrivateLabelingSettingUnspecified {
if request.UserOrgID != "" {
privateLabelingOrgID = request.UserOrgID
}
}
if request.RequestedOrgID != "" {
privateLabelingOrgID = request.RequestedOrgID
}
labelPolicy, err := repo.getLabelPolicy(ctx, privateLabelingOrgID)
labelPolicy, err := repo.getLabelPolicy(ctx, request.PrivateLabelingOrgID(orgID))
if err != nil {
return err
}
@@ -749,8 +745,9 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
}
// the user was either not found or not active
// so check if the loginname suffix matches a verified org domain
if repo.checkDomainDiscovery(ctx, request, loginName) {
return nil
ok, err := repo.checkDomainDiscovery(ctx, request, loginName)
if err != nil || ok {
return err
}
// let's once again check if the user was just inactive
if user != nil && user.State == int32(domain.UserStateInactive) {
@@ -782,30 +779,34 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
return errors.ThrowInternal(nil, "AUTH-asf3df", "Errors.Internal")
}
func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) bool {
func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) (bool, error) {
// check if there's a suffix in the loginname
loginName = strings.TrimSpace(strings.ToLower(loginName))
index := strings.LastIndex(loginName, "@")
if index < 0 {
return false
return false, nil
}
// check if the suffix matches a verified domain
org, err := repo.Query.OrgByVerifiedDomain(ctx, loginName[index+1:])
if err != nil {
return false
return false, nil
}
// and if the login policy allows domain discovery
policy, err := repo.Query.LoginPolicyByID(ctx, true, org.ID, false)
if err != nil || !policy.AllowDomainDiscovery {
return false
return false, nil
}
// discovery was allowed, so set the org as requested org
// and clear all potentially existing user information and only set the loginname as hint (for registration)
// also ensure that the policies are read from the org
request.SetOrgInformation(org.ID, org.Name, org.Domain, false)
request.SetUserInfo("", "", "", "", "", org.ID)
if err = repo.fillPolicies(ctx, request); err != nil {
return false, err
}
request.LoginHint = loginName
request.Prompt = append(request.Prompt, domain.PromptCreate) // to trigger registration
return true
return true, nil
}
func (repo *AuthRequestRepo) checkLoginNameInput(ctx context.Context, request *domain.AuthRequest, loginNameInput string) (*user_view_model.UserView, error) {

View File

@@ -3,6 +3,7 @@ package eventsourcing
import (
"context"
"github.com/zitadel/zitadel/feature"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/eventstore"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/spooler"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
@@ -88,6 +89,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c
ProjectProvider: queryView,
ApplicationProvider: queries,
CustomTextProvider: queries,
FeatureCheck: feature.NewCheck(esV2),
IdGenerator: idGenerator,
},
eventstore.TokenRepo{

View File

@@ -16,6 +16,7 @@ import (
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/repository/action"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/idpintent"
instance_repo "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/keypair"
@@ -145,6 +146,7 @@ func StartCommands(
authrequest.RegisterEventMappers(repo.eventstore)
oidcsession.RegisterEventMappers(repo.eventstore)
milestone.RegisterEventMappers(repo.eventstore)
feature.RegisterEventMappers(repo.eventstore)
repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher()

View File

@@ -15,6 +15,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
@@ -112,6 +113,7 @@ type InstanceSetup struct {
Quotas *struct {
Items []*SetQuota
}
Features map[domain.Feature]any
}
type SecretGenerators struct {
@@ -432,6 +434,19 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
)
}
for f, value := range setup.Features {
switch v := value.(type) {
case bool:
wm, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f)
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, prepareSetFeature(wm, feature.Boolean{Boolean: v}, c.idGenerator))
default:
return "", "", nil, nil, errors.ThrowInvalidArgument(nil, "INST-GE4tg", "Errors.Feature.TypeNotSupported")
}
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
if err != nil {
return "", "", nil, nil, err

View File

@@ -0,0 +1,63 @@
package command
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/repository/feature"
)
func (c *Commands) SetBooleanInstanceFeature(ctx context.Context, f domain.Feature, value bool) (*domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
writeModel, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f)
if err != nil {
return nil, err
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter,
prepareSetFeature(writeModel, feature.Boolean{Boolean: value}, c.idGenerator))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
return writeModelToObjectDetails(&writeModel.FeatureWriteModel.WriteModel), nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func prepareSetFeature[T feature.SetEventType](writeModel *InstanceFeatureWriteModel[T], value T, idGenerator id.Generator) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !writeModel.feature.IsAFeature() || writeModel.feature == domain.FeatureUnspecified {
return nil, errors.ThrowPreconditionFailed(nil, "FEAT-JK3td", "Errors.Feature.NotExisting")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
if len(events) == 0 {
writeModel.AggregateID, err = idGenerator.Next()
if err != nil {
return nil, err
}
}
setEvent, err := writeModel.Set(ctx, value)
if err != nil || setEvent == nil {
return nil, err
}
return []eventstore.Command{setEvent}, nil
}, nil
}
}

View File

@@ -0,0 +1,81 @@
package command
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/feature"
)
type FeatureWriteModel[T feature.SetEventType] struct {
eventstore.WriteModel
feature domain.Feature
Value T
}
func NewFeatureWriteModel[T feature.SetEventType](instanceID, resourceOwner string, feature domain.Feature) (*FeatureWriteModel[T], error) {
wm := &FeatureWriteModel[T]{
WriteModel: eventstore.WriteModel{
InstanceID: instanceID,
ResourceOwner: resourceOwner,
},
feature: feature,
}
if wm.Value.FeatureType() != feature.Type() {
return nil, errors.ThrowPreconditionFailed(nil, "FEAT-AS4k1", "Errors.Feature.InvalidValue")
}
return wm, nil
}
func (wm *FeatureWriteModel[T]) Set(ctx context.Context, value T) (event *feature.SetEvent[T], err error) {
if wm.Value == value {
return nil, nil
}
return feature.NewSetEvent[T](
ctx,
&feature.NewAggregate(wm.AggregateID, wm.ResourceOwner).Aggregate,
wm.eventType(),
value,
), nil
}
func (wm *FeatureWriteModel[T]) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(feature.AggregateType).
EventTypes(wm.eventType()).
Builder()
}
func (wm *FeatureWriteModel[T]) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *feature.SetEvent[T]:
wm.Value = e.Value
default:
return errors.ThrowPreconditionFailed(nil, "FEAT-SDfjk", "Errors.Feature.TypeNotSupported")
}
}
return wm.WriteModel.Reduce()
}
func (wm *FeatureWriteModel[T]) eventType() eventstore.EventType {
return feature.EventTypeFromFeature(wm.feature)
}
type InstanceFeatureWriteModel[T feature.SetEventType] struct {
FeatureWriteModel[T]
}
func NewInstanceFeatureWriteModel[T feature.SetEventType](instanceID string, feature domain.Feature) (*InstanceFeatureWriteModel[T], error) {
wm, err := NewFeatureWriteModel[T](instanceID, instanceID, feature)
if err != nil {
return nil, err
}
return &InstanceFeatureWriteModel[T]{
FeatureWriteModel: *wm,
}, nil
}

View File

@@ -0,0 +1,179 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/instance"
)
func TestCommands_SetBooleanInstanceFeature(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
ctx context.Context
f domain.Feature
value bool
}
type res struct {
details *domain.ObjectDetails
err error
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"unknown feature",
fields{
eventstore: expectEventstore(),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
f: domain.FeatureUnspecified,
value: true,
},
res{
err: errors.ThrowPreconditionFailed(nil, "FEAT-AS4k1", "Errors.Feature.InvalidValue"),
},
},
{
"wrong type",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithInstanceID("instanceID",
// as there's currently no other [feature.SetEventType] than [feature.Boolean],
// we need to use a completely other event type to demonstrate the behaviour
instance.NewInstanceAddedEvent(context.Background(), &instance.NewAggregate("instanceID").Aggregate,
"instance",
),
),
),
),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
f: domain.FeatureLoginDefaultOrg,
value: true,
},
res{
err: errors.ThrowPreconditionFailed(nil, "FEAT-SDfjk", "Errors.Feature.TypeNotSupported"),
},
},
{
"first set",
fields{
eventstore: expectEventstore(
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID("instanceID",
feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate,
feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg),
feature.Boolean{Boolean: true},
),
),
},
),
),
idGenerator: mock.ExpectID(t, "featureID"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
f: domain.FeatureLoginDefaultOrg,
value: true,
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instanceID",
},
},
},
{
"update flag",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithInstanceID("instanceID",
feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate,
feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg),
feature.Boolean{Boolean: true},
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID("instanceID",
feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate,
feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg),
feature.Boolean{Boolean: false},
),
),
},
),
),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
f: domain.FeatureLoginDefaultOrg,
value: false,
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instanceID",
},
},
},
{
"no change",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithInstanceID("instanceID",
feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate,
feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg),
feature.Boolean{Boolean: true},
),
),
),
),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
f: domain.FeatureLoginDefaultOrg,
value: true,
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instanceID",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
}
got, err := c.SetBooleanInstanceFeature(tt.args.ctx, tt.args.f, tt.args.value)
assert.ErrorIs(t, err, tt.res.err)
assert.Equal(t, tt.res.details, got)
})
}
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore/repository/mock"
action_repo "github.com/zitadel/zitadel/internal/repository/action"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/idpintent"
iam_repo "github.com/zitadel/zitadel/internal/repository/instance"
key_repo "github.com/zitadel/zitadel/internal/repository/keypair"
@@ -52,6 +53,7 @@ func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore {
authrequest.RegisterEventMappers(es)
oidcsession.RegisterEventMappers(es)
quota_repo.RegisterEventMappers(es)
feature.RegisterEventMappers(es)
return es
}

View File

@@ -0,0 +1,27 @@
package hook
import (
"reflect"
"github.com/mitchellh/mapstructure"
"github.com/zitadel/zitadel/internal/domain"
)
func StringToFeatureHookFunc() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(domain.FeatureUnspecified) {
return data, nil
}
return domain.FeatureString(data.(string))
}
}

View File

@@ -208,3 +208,17 @@ func (a *AuthRequest) Done() bool {
}
return false
}
func (a *AuthRequest) PrivateLabelingOrgID(defaultID string) string {
if a.RequestedOrgID != "" {
return a.RequestedOrgID
}
if (a.PrivateLabelingSetting == PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || a.PrivateLabelingSetting == PrivateLabelingSettingUnspecified) &&
a.UserOrgID != "" {
return a.UserOrgID
}
if a.PrivateLabelingSetting != PrivateLabelingSettingUnspecified {
return a.ApplicationResourceOwner
}
return defaultID
}

View File

@@ -0,0 +1,28 @@
//go:generate enumer -type Feature
package domain
type Feature int
func (f Feature) Type() FeatureType {
switch f {
case FeatureUnspecified:
return FeatureTypeUnspecified
case FeatureLoginDefaultOrg:
return FeatureTypeBoolean
default:
return FeatureTypeUnspecified
}
}
const (
FeatureTypeUnspecified FeatureType = iota
FeatureTypeBoolean
)
type FeatureType int
const (
FeatureUnspecified Feature = iota
FeatureLoginDefaultOrg
)

View File

@@ -0,0 +1,78 @@
// Code generated by "enumer -type Feature"; DO NOT EDIT.
package domain
import (
"fmt"
"strings"
)
const _FeatureName = "FeatureUnspecifiedFeatureLoginDefaultOrg"
var _FeatureIndex = [...]uint8{0, 18, 40}
const _FeatureLowerName = "featureunspecifiedfeaturelogindefaultorg"
func (i Feature) String() string {
if i < 0 || i >= Feature(len(_FeatureIndex)-1) {
return fmt.Sprintf("Feature(%d)", i)
}
return _FeatureName[_FeatureIndex[i]:_FeatureIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _FeatureNoOp() {
var x [1]struct{}
_ = x[FeatureUnspecified-(0)]
_ = x[FeatureLoginDefaultOrg-(1)]
}
var _FeatureValues = []Feature{FeatureUnspecified, FeatureLoginDefaultOrg}
var _FeatureNameToValueMap = map[string]Feature{
_FeatureName[0:18]: FeatureUnspecified,
_FeatureLowerName[0:18]: FeatureUnspecified,
_FeatureName[18:40]: FeatureLoginDefaultOrg,
_FeatureLowerName[18:40]: FeatureLoginDefaultOrg,
}
var _FeatureNames = []string{
_FeatureName[0:18],
_FeatureName[18:40],
}
// FeatureString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func FeatureString(s string) (Feature, error) {
if val, ok := _FeatureNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _FeatureNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to Feature values", s)
}
// FeatureValues returns all values of the enum
func FeatureValues() []Feature {
return _FeatureValues
}
// FeatureStrings returns a slice of all String values of the enum
func FeatureStrings() []string {
strs := make([]string, len(_FeatureNames))
copy(strs, _FeatureNames)
return strs
}
// IsAFeature returns "true" if the value is listed in the enum definition. "false" otherwise
func (i Feature) IsAFeature() bool {
for _, v := range _FeatureValues {
if i == v {
return true
}
}
return false
}

View File

@@ -0,0 +1,30 @@
package feature
import (
"github.com/zitadel/zitadel/internal/eventstore"
)
const (
eventTypePrefix = eventstore.EventType("feature.")
setSuffix = ".set"
)
const (
AggregateType = "feature"
AggregateVersion = "v1"
)
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate(id, resourceOwner string) *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Type: AggregateType,
Version: AggregateVersion,
ID: id,
ResourceOwner: resourceOwner,
},
}
}

View File

@@ -0,0 +1,9 @@
package feature
import (
"github.com/zitadel/zitadel/internal/eventstore"
)
func RegisterEventMappers(es *eventstore.Eventstore) {
es.RegisterFilterEventMapper(AggregateType, DefaultLoginInstanceEventType, eventstore.GenericEventMapper[SetEvent[Boolean]])
}

View File

@@ -0,0 +1,65 @@
package feature
import (
"context"
"strings"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
DefaultLoginInstanceEventType = EventTypeFromFeature(domain.FeatureLoginDefaultOrg)
)
func EventTypeFromFeature(feature domain.Feature) eventstore.EventType {
return eventTypePrefix + eventstore.EventType(strings.ToLower(feature.String())) + setSuffix
}
type SetEvent[T SetEventType] struct {
*eventstore.BaseEvent
Value T
}
func (e *SetEvent[T]) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *SetEvent[T]) Data() interface{} {
return e
}
func (e *SetEvent[T]) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
type SetEventType interface {
Boolean
FeatureType() domain.FeatureType
}
type EventType[T SetEventType] struct {
eventstore.EventType
}
type Boolean struct {
Boolean bool
}
func (b Boolean) FeatureType() domain.FeatureType {
return domain.FeatureTypeBoolean
}
func NewSetEvent[T SetEventType](
ctx context.Context,
aggregate *eventstore.Aggregate,
eventType eventstore.EventType,
setType T,
) *SetEvent[T] {
return &SetEvent[T]{
eventstore.NewBaseEventForPush(
ctx, aggregate, eventType),
setType,
}
}

View File

@@ -522,6 +522,10 @@ Errors:
Token:
Invalid: Токенът е невалиден
Expired: Токенът е изтекъл
Feature:
NotExisting: Функцията не съществува
TypeNotSupported: Типът функция не се поддържа
InvalidValue: Невалидна стойност за тази функция
AggregateTypes:
action: Действие
@@ -532,6 +536,8 @@ AggregateTypes:
user: Потребител
usergrant: Предоставяне на потребител
quota: Квота
feature: Особеност
EventTypes:
user:
added: Добавен потребител

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: Token ist ungültig
Expired: Token ist abgelaufen
InvalidClient: Token wurde nicht für diesen Client ausgestellt
Feature:
NotExisting: Feature existiert nicht
TypeNotSupported: Feature Typ wird nicht unterstützt
InvalidValue: Ungültiger Wert für dieses Feature
AggregateTypes:
action: Action
@@ -515,6 +519,7 @@ AggregateTypes:
user: Benutzer
usergrant: Benutzerberechtigung
quota: Kontingent
feature: Feature
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: Token is invalid
Expired: Token is expired
InvalidClient: Token was not issued for this client
Feature:
NotExisting: Feature does not exist
TypeNotSupported: Feature type is not supported
InvalidValue: Invalid value for this feature
AggregateTypes:
action: Action
@@ -515,6 +519,7 @@ AggregateTypes:
user: User
usergrant: User grant
quota: Quota
feature: Feature
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: El token no es válido
Expired: El token ha caducado
InvalidClient: El token no ha sido emitido para este cliente
Feature:
NotExisting: La característica no existe
TypeNotSupported: El tipo de característica no es compatible
InvalidValue: Valor no válido para esta característica
AggregateTypes:
action: Acción
@@ -515,6 +519,7 @@ AggregateTypes:
user: Usuario
usergrant: Concesión de usuario
quota: Cuota
feature: Característica
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: Le jeton n'est pas valide
Expired: Le jeton est expiré
InvalidClient: Le token n'a pas été émis pour ce client
Feature:
NotExisting: La fonctionnalité n'existe pas
TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge
InvalidValue: Valeur non valide pour cette fonctionnalité
AggregateTypes:
action: Action
@@ -515,6 +519,7 @@ AggregateTypes:
user: Utilisateur
usergrant: Subvention de l'utilisateur
quota: Contingent
feature: Fonctionnalité
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: Token non è valido
Expired: Token è scaduto
InvalidClient: Il token non è stato emesso per questo cliente
Feature:
NotExisting: La funzionalità non esiste
TypeNotSupported: Il tipo di funzionalità non è supportato
InvalidValue: Valore non valido per questa funzionalità
AggregateTypes:
action: Azione
@@ -515,6 +519,7 @@ AggregateTypes:
user: Utente
usergrant: Sovvenzione utente
quota: Quota
feature: Funzionalità
EventTypes:
user:

View File

@@ -494,6 +494,10 @@ Errors:
Invalid: トークンが無効です
Expired: トークンの有効期限が切れている
InvalidClient: トークンが発行されていません
Feature:
NotExisting: 機能が存在しません
TypeNotSupported: 機能タイプはサポートされていません
InvalidValue: この機能には無効な値です
AggregateTypes:
action: アクション
@@ -504,6 +508,7 @@ AggregateTypes:
user: ユーザー
usergrant: ユーザーグラント
quota: クォータ
feature: 特徴
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: токенот е неважечки
Expired: токенот е истечен
InvalidClient: Токен не беше издаден на овој клиент
Feature:
NotExisting: Функцијата не постои
TypeNotSupported: Типот на функција не е поддржан
InvalidValue: Неважечка вредност за оваа функција
AggregateTypes:
action: Акција
@@ -515,6 +519,7 @@ AggregateTypes:
user: Корисник
usergrant: Овластување на корисник
quota: Квота
feature: Карактеристика
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: Token jest nieprawidłowy
Expired: Token wygasł
InvalidClient: Token nie został wydany dla tego klienta
Feature:
NotExisting: Funkcja nie istnieje
TypeNotSupported: Typ funkcji nie jest obsługiwany
InvalidValue: Nieprawidłowa wartość dla tej funkcji
AggregateTypes:
action: Działanie
@@ -515,6 +519,7 @@ AggregateTypes:
user: Użytkownik
usergrant: Uprawnienie użytkownika
quota: Limit
feature: Funkcja
EventTypes:
user:

View File

@@ -499,6 +499,10 @@ Errors:
WrongLoginClient: A solicitação de autenticação foi criada por outro cliente de login
OIDCSession:
RefreshTokenInvalid: O Refresh Token é inválido
Feature:
NotExisting: O recurso não existe
TypeNotSupported: O tipo de recurso não é compatível
InvalidValue: Valor inválido para este recurso
AggregateTypes:
action: Ação
@@ -509,6 +513,7 @@ AggregateTypes:
user: Usuário
usergrant: Concessão de usuário
quota: Cota
feature: Recurso
EventTypes:
user:

View File

@@ -505,6 +505,10 @@ Errors:
Invalid: 令牌无效
Expired: 令牌已过期
InvalidClient: 没有为该客户发放令牌
Feature:
NotExisting: 功能不存在
TypeNotSupported: 不支持功能类型
InvalidValue: 此功能的值无效
AggregateTypes:
action: 动作
@@ -515,6 +519,7 @@ AggregateTypes:
user: 用户
usergrant: 用户授权
quota: 配额
feature: 特征
EventTypes:
user: