fix: add domain as attribute to list user auth methods (#8718)

# Which Problems Are Solved

There is no option to only query auth methods related to specific
domains.

# How the Problems Are Solved

Add domain as attribute to the ListAuthenticationMethodTypes request.

# Additional Changes

OwnerRemoved column removed from the projection.

# Additional Context

Closes #8615

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2024-10-10 18:50:53 +02:00
committed by GitHub
parent df2033253d
commit 4d593dace2
29 changed files with 649 additions and 86 deletions

View File

@@ -14,7 +14,7 @@ import (
)
const (
UserAuthMethodTable = "projections.user_auth_methods4"
UserAuthMethodTable = "projections.user_auth_methods5"
UserAuthMethodUserIDCol = "user_id"
UserAuthMethodTypeCol = "method_type"
@@ -26,7 +26,7 @@ const (
UserAuthMethodInstanceIDCol = "instance_id"
UserAuthMethodStateCol = "state"
UserAuthMethodNameCol = "name"
UserAuthMethodOwnerRemovedCol = "owner_removed"
UserAuthMethodDomainCol = "domain"
)
type userAuthMethodProjection struct{}
@@ -52,11 +52,10 @@ func (*userAuthMethodProjection) Init() *old_handler.Check {
handler.NewColumn(UserAuthMethodResourceOwnerCol, handler.ColumnTypeText),
handler.NewColumn(UserAuthMethodInstanceIDCol, handler.ColumnTypeText),
handler.NewColumn(UserAuthMethodNameCol, handler.ColumnTypeText),
handler.NewColumn(UserAuthMethodOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)),
handler.NewColumn(UserAuthMethodDomainCol, handler.ColumnTypeText, handler.Nullable()),
},
handler.NewPrimaryKey(UserAuthMethodInstanceIDCol, UserAuthMethodUserIDCol, UserAuthMethodTypeCol, UserAuthMethodTokenIDCol),
handler.WithIndex(handler.NewIndex("resource_owner", []string{UserAuthMethodResourceOwnerCol})),
handler.WithIndex(handler.NewIndex("owner_removed", []string{UserAuthMethodOwnerRemovedCol})),
),
)
}
@@ -151,20 +150,37 @@ func (p *userAuthMethodProjection) Reducers() []handler.AggregateReducer {
func (p *userAuthMethodProjection) reduceInitAuthMethod(event eventstore.Event) (*handler.Statement, error) {
tokenID := ""
var rpID *string
var methodType domain.UserAuthMethodType
switch e := event.(type) {
case *user.HumanPasswordlessAddedEvent:
methodType = domain.UserAuthMethodTypePasswordless
tokenID = e.WebAuthNTokenID
rpID = &e.RPID
case *user.HumanU2FAddedEvent:
methodType = domain.UserAuthMethodTypeU2F
tokenID = e.WebAuthNTokenID
rpID = &e.RPID
case *user.HumanOTPAddedEvent:
methodType = domain.UserAuthMethodTypeTOTP
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-f92f", "reduce.wrong.event.type %v", []eventstore.EventType{user.HumanPasswordlessTokenAddedType, user.HumanU2FTokenAddedType})
}
cols := []handler.Column{
handler.NewCol(UserAuthMethodTokenIDCol, tokenID),
handler.NewCol(UserAuthMethodCreationDateCol, handler.OnlySetValueOnInsert(UserAuthMethodTable, event.CreatedAt())),
handler.NewCol(UserAuthMethodChangeDateCol, event.CreatedAt()),
handler.NewCol(UserAuthMethodResourceOwnerCol, event.Aggregate().ResourceOwner),
handler.NewCol(UserAuthMethodInstanceIDCol, event.Aggregate().InstanceID),
handler.NewCol(UserAuthMethodUserIDCol, event.Aggregate().ID),
handler.NewCol(UserAuthMethodSequenceCol, event.Sequence()),
handler.NewCol(UserAuthMethodStateCol, domain.MFAStateNotReady),
handler.NewCol(UserAuthMethodTypeCol, methodType),
handler.NewCol(UserAuthMethodNameCol, ""),
}
if rpID != nil {
cols = append(cols, handler.NewCol(UserAuthMethodDomainCol, rpID))
}
return handler.NewUpsertStatement(
event,
[]handler.Column{
@@ -173,18 +189,7 @@ func (p *userAuthMethodProjection) reduceInitAuthMethod(event eventstore.Event)
handler.NewCol(UserAuthMethodTypeCol, nil),
handler.NewCol(UserAuthMethodTokenIDCol, nil),
},
[]handler.Column{
handler.NewCol(UserAuthMethodTokenIDCol, tokenID),
handler.NewCol(UserAuthMethodCreationDateCol, handler.OnlySetValueOnInsert(UserAuthMethodTable, event.CreatedAt())),
handler.NewCol(UserAuthMethodChangeDateCol, event.CreatedAt()),
handler.NewCol(UserAuthMethodResourceOwnerCol, event.Aggregate().ResourceOwner),
handler.NewCol(UserAuthMethodInstanceIDCol, event.Aggregate().InstanceID),
handler.NewCol(UserAuthMethodUserIDCol, event.Aggregate().ID),
handler.NewCol(UserAuthMethodSequenceCol, event.Sequence()),
handler.NewCol(UserAuthMethodStateCol, domain.MFAStateNotReady),
handler.NewCol(UserAuthMethodTypeCol, methodType),
handler.NewCol(UserAuthMethodNameCol, ""),
},
cols,
), nil
}
@@ -204,7 +209,6 @@ func (p *userAuthMethodProjection) reduceActivateEvent(event eventstore.Event) (
name = e.WebAuthNTokenName
case *user.HumanOTPVerifiedEvent:
methodType = domain.UserAuthMethodTypeTOTP
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-f92f", "reduce.wrong.event.type %v", []eventstore.EventType{user.HumanPasswordlessTokenAddedType, user.HumanU2FTokenAddedType})
}

View File

@@ -3,6 +3,8 @@ package projection
import (
"testing"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
@@ -30,7 +32,8 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
user.HumanPasswordlessTokenAddedType,
user.AggregateType,
[]byte(`{
"webAuthNTokenId": "token-id"
"webAuthNTokenId": "token-id",
"rpID": "example.com"
}`),
), user.HumanPasswordlessAddedEventMapper),
},
@@ -41,7 +44,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods4 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name) = (projections.user_auth_methods4.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name)",
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name, domain) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name, domain) = (projections.user_auth_methods5.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name, EXCLUDED.domain)",
expectedArgs: []interface{}{
"token-id",
anyArg{},
@@ -53,6 +56,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
domain.MFAStateNotReady,
domain.UserAuthMethodTypePasswordless,
"",
gu.Ptr("example.com"),
},
},
},
@@ -67,7 +71,8 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
user.HumanU2FTokenAddedType,
user.AggregateType,
[]byte(`{
"webAuthNTokenId": "token-id"
"webAuthNTokenId": "token-id",
"rpID": "example.com"
}`),
), user.HumanU2FAddedEventMapper),
},
@@ -78,7 +83,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods4 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name) = (projections.user_auth_methods4.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name)",
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name, domain) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name, domain) = (projections.user_auth_methods5.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name, EXCLUDED.domain)",
expectedArgs: []interface{}{
"token-id",
anyArg{},
@@ -90,6 +95,46 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
domain.MFAStateNotReady,
domain.UserAuthMethodTypeU2F,
"",
gu.Ptr("example.com"),
},
},
},
},
},
},
{
name: "reduceAddedU2F internal",
args: args{
event: getEvent(
testEvent(
user.HumanU2FTokenAddedType,
user.AggregateType,
[]byte(`{
"webAuthNTokenId": "token-id",
"rpID": ""
}`),
), user.HumanU2FAddedEventMapper),
},
reduce: (&userAuthMethodProjection{}).reduceInitAuthMethod,
want: wantReduce{
aggregateType: user.AggregateType,
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name, domain) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name, domain) = (projections.user_auth_methods5.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name, EXCLUDED.domain)",
expectedArgs: []interface{}{
"token-id",
anyArg{},
anyArg{},
"ro-id",
"instance-id",
"agg-id",
uint64(15),
domain.MFAStateNotReady,
domain.UserAuthMethodTypeU2F,
"",
gu.Ptr(""),
},
},
},
@@ -114,7 +159,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods4 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name) = (projections.user_auth_methods4.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name)",
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (instance_id, user_id, method_type, token_id) DO UPDATE SET (creation_date, change_date, resource_owner, sequence, state, name) = (projections.user_auth_methods5.creation_date, EXCLUDED.change_date, EXCLUDED.resource_owner, EXCLUDED.sequence, EXCLUDED.state, EXCLUDED.name)",
expectedArgs: []interface{}{
"",
anyArg{},
@@ -152,7 +197,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.user_auth_methods4 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedStmt: "UPDATE projections.user_auth_methods5 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -189,7 +234,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.user_auth_methods4 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedStmt: "UPDATE projections.user_auth_methods5 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -224,7 +269,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.user_auth_methods4 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedStmt: "UPDATE projections.user_auth_methods5 SET (change_date, sequence, name, state) = ($1, $2, $3, $4) WHERE (user_id = $5) AND (method_type = $6) AND (resource_owner = $7) AND (token_id = $8) AND (instance_id = $9)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -257,7 +302,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods4 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{
"",
anyArg{},
@@ -291,7 +336,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.user_auth_methods4 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedStmt: "INSERT INTO projections.user_auth_methods5 (token_id, creation_date, change_date, resource_owner, instance_id, user_id, sequence, state, method_type, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{
"",
anyArg{},
@@ -327,7 +372,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4) AND (token_id = $5)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4) AND (token_id = $5)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypePasswordless,
@@ -358,7 +403,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4) AND (token_id = $5)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4) AND (token_id = $5)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypeU2F,
@@ -387,7 +432,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypeTOTP,
@@ -415,7 +460,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypeOTPSMS,
@@ -443,7 +488,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypeOTPSMS,
@@ -471,7 +516,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (user_id = $1) AND (method_type = $2) AND (resource_owner = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"agg-id",
domain.UserAuthMethodTypeOTPEmail,
@@ -500,7 +545,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (instance_id = $1) AND (resource_owner = $2)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (instance_id = $1) AND (resource_owner = $2)",
expectedArgs: []interface{}{
"instance-id",
"agg-id",
@@ -527,7 +572,7 @@ func TestUserAuthMethodProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.user_auth_methods4 WHERE (instance_id = $1)",
expectedStmt: "DELETE FROM projections.user_auth_methods5 WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},

View File

@@ -63,8 +63,8 @@ var (
name: projection.UserAuthMethodTypeCol,
table: userAuthMethodTable,
}
UserAuthMethodColumnOwnerRemoved = Column{
name: projection.UserAuthMethodOwnerRemovedCol,
UserAuthMethodColumnDomain = Column{
name: projection.UserAuthMethodDomainCol,
table: userAuthMethodTable,
}
@@ -72,11 +72,8 @@ var (
authMethodTypeUserID = UserAuthMethodColumnUserID.setTable(authMethodTypeTable)
authMethodTypeInstanceID = UserAuthMethodColumnInstanceID.setTable(authMethodTypeTable)
authMethodTypeType = UserAuthMethodColumnMethodType.setTable(authMethodTypeTable)
authMethodTypeTypes = Column{
name: "method_types",
table: authMethodTypeTable,
}
authMethodTypeState = UserAuthMethodColumnState.setTable(authMethodTypeTable)
authMethodTypeState = UserAuthMethodColumnState.setTable(authMethodTypeTable)
authMethodTypeDomain = UserAuthMethodColumnDomain.setTable(authMethodTypeTable)
userIDPsCountTable = idpUserLinkTable.setAlias("user_idps_count")
userIDPsCountUserID = IDPUserLinkUserIDCol.setTable(userIDPsCountTable)
@@ -140,7 +137,7 @@ func (q *UserAuthMethodSearchQueries) hasUserID() bool {
}
func (q *Queries) SearchUserAuthMethods(ctx context.Context, queries *UserAuthMethodSearchQueries, permissionCheck domain.PermissionCheck) (userAuthMethods *AuthMethods, err error) {
methods, err := q.searchUserAuthMethods(ctx, queries, false)
methods, err := q.searchUserAuthMethods(ctx, queries)
if err != nil {
return nil, err
}
@@ -157,16 +154,12 @@ func (q *Queries) SearchUserAuthMethods(ctx context.Context, queries *UserAuthMe
return methods, nil
}
func (q *Queries) searchUserAuthMethods(ctx context.Context, queries *UserAuthMethodSearchQueries, withOwnerRemoved bool) (userAuthMethods *AuthMethods, err error) {
func (q *Queries) searchUserAuthMethods(ctx context.Context, queries *UserAuthMethodSearchQueries) (userAuthMethods *AuthMethods, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareUserAuthMethodsQuery(ctx, q.client)
eq := sq.Eq{UserAuthMethodColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
if !withOwnerRemoved {
eq[UserAuthMethodColumnOwnerRemoved.identifier()] = false
}
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
stmt, args, err := queries.toQuery(query).Where(sq.Eq{UserAuthMethodColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}).ToSql()
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-j9NJd", "Errors.Query.InvalidRequest")
}
@@ -182,7 +175,7 @@ func (q *Queries) searchUserAuthMethods(ctx context.Context, queries *UserAuthMe
return userAuthMethods, err
}
func (q *Queries) ListUserAuthMethodTypes(ctx context.Context, userID string, activeOnly bool) (userAuthMethodTypes *AuthMethodTypes, err error) {
func (q *Queries) ListUserAuthMethodTypes(ctx context.Context, userID string, activeOnly bool, includeWithoutDomain bool, queryDomain string) (userAuthMethodTypes *AuthMethodTypes, err error) {
ctxData := authz.GetCtxData(ctx)
if ctxData.UserID != userID {
if err := q.checkPermission(ctx, domain.PermissionUserRead, ctxData.OrgID, userID); err != nil {
@@ -192,7 +185,7 @@ func (q *Queries) ListUserAuthMethodTypes(ctx context.Context, userID string, ac
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareUserAuthMethodTypesQuery(ctx, q.client, activeOnly)
query, scan := prepareUserAuthMethodTypesQuery(ctx, q.client, activeOnly, includeWithoutDomain, queryDomain)
eq := sq.Eq{
UserIDCol.identifier(): userID,
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
@@ -389,8 +382,8 @@ func prepareUserAuthMethodsQuery(ctx context.Context, db prepareDatabase) (sq.Se
}
}
func prepareUserAuthMethodTypesQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
authMethodsQuery, authMethodsArgs, err := prepareAuthMethodQuery(activeOnly)
func prepareUserAuthMethodTypesQuery(ctx context.Context, db prepareDatabase, activeOnly bool, includeWithoutDomain bool, queryDomain string) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
authMethodsQuery, authMethodsArgs, err := prepareAuthMethodQuery(activeOnly, includeWithoutDomain, queryDomain)
if err != nil {
return sq.SelectBuilder{}, nil
}
@@ -504,7 +497,7 @@ func prepareAuthMethodsIDPsQuery() (string, error) {
return idpsQuery, err
}
func prepareAuthMethodQuery(activeOnly bool) (string, []interface{}, error) {
func prepareAuthMethodQuery(activeOnly bool, includeWithoutDomain bool, queryDomain string) (string, []interface{}, error) {
q := sq.Select(
"DISTINCT("+authMethodTypeType.identifier()+")",
authMethodTypeUserID.identifier(),
@@ -513,6 +506,17 @@ func prepareAuthMethodQuery(activeOnly bool) (string, []interface{}, error) {
if activeOnly {
q = q.Where(sq.Eq{authMethodTypeState.identifier(): domain.MFAStateReady})
}
if queryDomain != "" {
conditions := sq.Or{
sq.Eq{authMethodTypeDomain.identifier(): nil},
sq.Eq{authMethodTypeDomain.identifier(): queryDomain},
}
if includeWithoutDomain {
conditions = append(conditions, sq.Eq{authMethodTypeDomain.identifier(): ""})
}
q = q.Where(conditions)
}
return q.ToSql()
}

View File

@@ -181,17 +181,17 @@ func TestUser_authMethodsCheckPermission(t *testing.T) {
}
var (
prepareUserAuthMethodsStmt = `SELECT projections.user_auth_methods4.token_id,` +
` projections.user_auth_methods4.creation_date,` +
` projections.user_auth_methods4.change_date,` +
` projections.user_auth_methods4.resource_owner,` +
` projections.user_auth_methods4.user_id,` +
` projections.user_auth_methods4.sequence,` +
` projections.user_auth_methods4.name,` +
` projections.user_auth_methods4.state,` +
` projections.user_auth_methods4.method_type,` +
prepareUserAuthMethodsStmt = `SELECT projections.user_auth_methods5.token_id,` +
` projections.user_auth_methods5.creation_date,` +
` projections.user_auth_methods5.change_date,` +
` projections.user_auth_methods5.resource_owner,` +
` projections.user_auth_methods5.user_id,` +
` projections.user_auth_methods5.sequence,` +
` projections.user_auth_methods5.name,` +
` projections.user_auth_methods5.state,` +
` projections.user_auth_methods5.method_type,` +
` COUNT(*) OVER ()` +
` FROM projections.user_auth_methods4` +
` FROM projections.user_auth_methods5` +
` AS OF SYSTEM TIME '-1 ms'`
prepareUserAuthMethodsCols = []string{
"token_id",
@@ -210,7 +210,7 @@ var (
` user_idps_count.count` +
` FROM projections.users13` +
` LEFT JOIN projections.users13_notifications ON projections.users13.id = projections.users13_notifications.user_id AND projections.users13.instance_id = projections.users13_notifications.instance_id` +
` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` +
` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods5 AS auth_method_types` +
` WHERE auth_method_types.state = $1) AS auth_method_types` +
` ON auth_method_types.user_id = projections.users13.id AND auth_method_types.instance_id = projections.users13.instance_id` +
` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` +
@@ -222,6 +222,40 @@ var (
"method_type",
"idps_count",
}
prepareActiveAuthMethodTypesDomainStmt = `SELECT projections.users13_notifications.password_set,` +
` auth_method_types.method_type,` +
` user_idps_count.count` +
` FROM projections.users13` +
` LEFT JOIN projections.users13_notifications ON projections.users13.id = projections.users13_notifications.user_id AND projections.users13.instance_id = projections.users13_notifications.instance_id` +
` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods5 AS auth_method_types` +
` WHERE auth_method_types.state = $1 AND (auth_method_types.domain IS NULL OR auth_method_types.domain = $2 OR auth_method_types.domain = $3)) AS auth_method_types` +
` ON auth_method_types.user_id = projections.users13.id AND auth_method_types.instance_id = projections.users13.instance_id` +
` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` +
` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` +
` ON user_idps_count.user_id = projections.users13.id AND user_idps_count.instance_id = projections.users13.instance_id` +
` AS OF SYSTEM TIME '-1 ms`
prepareActiveAuthMethodTypesDomainCols = []string{
"password_set",
"method_type",
"idps_count",
}
prepareActiveAuthMethodTypesDomainExternalStmt = `SELECT projections.users13_notifications.password_set,` +
` auth_method_types.method_type,` +
` user_idps_count.count` +
` FROM projections.users13` +
` LEFT JOIN projections.users13_notifications ON projections.users13.id = projections.users13_notifications.user_id AND projections.users13.instance_id = projections.users13_notifications.instance_id` +
` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods5 AS auth_method_types` +
` WHERE auth_method_types.state = $1 AND (auth_method_types.domain IS NULL OR auth_method_types.domain = $2)) AS auth_method_types` +
` ON auth_method_types.user_id = projections.users13.id AND auth_method_types.instance_id = projections.users13.instance_id` +
` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` +
` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` +
` ON user_idps_count.user_id = projections.users13.id AND user_idps_count.instance_id = projections.users13.instance_id` +
` AS OF SYSTEM TIME '-1 ms`
prepareActiveAuthMethodTypesDomainExternalCols = []string{
"password_set",
"method_type",
"idps_count",
}
prepareAuthMethodTypesRequiredStmt = `SELECT projections.users13.type,` +
` auth_methods_force_mfa.force_mfa,` +
` auth_methods_force_mfa.force_mfa_local_only` +
@@ -384,7 +418,7 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
{
name: "prepareUserAuthMethodTypesQuery no result",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true)
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
@@ -401,7 +435,7 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
{
name: "prepareUserAuthMethodTypesQuery one second factor",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true)
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
@@ -430,10 +464,74 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
},
},
},
{
name: "prepareUserAuthMethodTypesQuery one second factor with domain",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "example.com")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareActiveAuthMethodTypesDomainStmt),
prepareActiveAuthMethodTypesDomainCols,
[][]driver.Value{
{
true,
domain.UserAuthMethodTypePasswordless,
1,
},
},
),
},
object: &AuthMethodTypes{
SearchResponse: SearchResponse{
Count: 3,
},
AuthMethodTypes: []domain.UserAuthMethodType{
domain.UserAuthMethodTypePasswordless,
domain.UserAuthMethodTypePassword,
domain.UserAuthMethodTypeIDP,
},
},
},
{
name: "prepareUserAuthMethodTypesQuery one second factor with domain external",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, false, "example.com")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareActiveAuthMethodTypesDomainExternalStmt),
prepareActiveAuthMethodTypesDomainExternalCols,
[][]driver.Value{
{
true,
domain.UserAuthMethodTypePasswordless,
1,
},
},
),
},
object: &AuthMethodTypes{
SearchResponse: SearchResponse{
Count: 3,
},
AuthMethodTypes: []domain.UserAuthMethodType{
domain.UserAuthMethodTypePasswordless,
domain.UserAuthMethodTypePassword,
domain.UserAuthMethodTypeIDP,
},
},
},
{
name: "prepareUserAuthMethodTypesQuery multiple second factors",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true)
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
@@ -468,10 +566,86 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
},
},
},
{
name: "prepareUserAuthMethodTypesQuery multiple second factors domain",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "example.com")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareActiveAuthMethodTypesDomainStmt),
prepareActiveAuthMethodTypesDomainCols,
[][]driver.Value{
{
true,
domain.UserAuthMethodTypePasswordless,
1,
},
{
true,
domain.UserAuthMethodTypeTOTP,
1,
},
},
),
},
object: &AuthMethodTypes{
SearchResponse: SearchResponse{
Count: 4,
},
AuthMethodTypes: []domain.UserAuthMethodType{
domain.UserAuthMethodTypePasswordless,
domain.UserAuthMethodTypeTOTP,
domain.UserAuthMethodTypePassword,
domain.UserAuthMethodTypeIDP,
},
},
},
{
name: "prepareUserAuthMethodTypesQuery multiple second factors domain external",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, false, "example.com")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareActiveAuthMethodTypesDomainExternalStmt),
prepareActiveAuthMethodTypesDomainExternalCols,
[][]driver.Value{
{
true,
domain.UserAuthMethodTypePasswordless,
1,
},
{
true,
domain.UserAuthMethodTypeTOTP,
1,
},
},
),
},
object: &AuthMethodTypes{
SearchResponse: SearchResponse{
Count: 4,
},
AuthMethodTypes: []domain.UserAuthMethodType{
domain.UserAuthMethodTypePasswordless,
domain.UserAuthMethodTypeTOTP,
domain.UserAuthMethodTypePassword,
domain.UserAuthMethodTypeIDP,
},
},
},
{
name: "prepareUserAuthMethodTypesQuery sql err",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*AuthMethodTypes, error)) {
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true)
builder, scan := prepareUserAuthMethodTypesQuery(ctx, db, true, true, "")
return builder, func(rows *sql.Rows) (*AuthMethodTypes, error) {
return scan(rows)
}