2022-01-20 13:21:59 +01:00
package query
import (
2023-07-14 13:16:16 +02:00
"context"
2022-01-20 13:21:59 +01:00
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"testing"
2023-07-14 13:16:16 +02:00
sq "github.com/Masterminds/squirrel"
2022-04-27 01:01:45 +02:00
"github.com/zitadel/zitadel/internal/domain"
2022-01-20 13:21:59 +01:00
)
2023-02-27 22:36:43 +01:00
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, ` +
` COUNT(*) OVER () ` +
` FROM projections.user_auth_methods4 ` +
` AS OF SYSTEM TIME '-1 ms' `
prepareUserAuthMethodsCols = [ ] string {
"token_id" ,
"creation_date" ,
"change_date" ,
"resource_owner" ,
"user_id" ,
"sequence" ,
"name" ,
"state" ,
"method_type" ,
"count" ,
}
2024-04-05 12:35:49 +03:00
prepareActiveAuthMethodTypesStmt = ` SELECT projections.users12_notifications.password_set, ` +
2023-06-20 18:23:28 +02:00
` auth_method_types.method_type, ` +
` user_idps_count.count ` +
2024-04-05 12:35:49 +03:00
` FROM projections.users12 ` +
` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id ` +
2023-06-20 18:23:28 +02:00
` 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 ` +
` WHERE auth_method_types.state = $1) AS auth_method_types ` +
2024-04-05 12:35:49 +03:00
` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id ` +
2023-06-20 18:23:28 +02:00
` 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 ` +
2024-04-05 12:35:49 +03:00
` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id ` +
2023-06-20 18:23:28 +02:00
` AS OF SYSTEM TIME '-1 ms `
prepareActiveAuthMethodTypesCols = [ ] string {
"password_set" ,
"method_type" ,
"idps_count" ,
}
2024-04-05 12:35:49 +03:00
prepareAuthMethodTypesRequiredStmt = ` SELECT projections.users12_notifications.password_set, ` +
2023-07-14 13:16:16 +02:00
` auth_method_types.method_type, ` +
` user_idps_count.count, ` +
2023-07-20 06:06:16 +02:00
` auth_methods_force_mfa.force_mfa, ` +
` auth_methods_force_mfa.force_mfa_local_only ` +
2024-04-05 12:35:49 +03:00
` FROM projections.users12 ` +
` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id ` +
2023-07-14 13:16:16 +02:00
` 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 ` +
` WHERE auth_method_types.state = $1) AS auth_method_types ` +
2024-04-05 12:35:49 +03:00
` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id ` +
2023-07-14 13:16:16 +02:00
` 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 ` +
2024-04-05 12:35:49 +03:00
` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id ` +
2023-07-20 06:06:16 +02:00
` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.force_mfa_local_only, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies5 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa ` +
2024-04-05 12:35:49 +03:00
` ON (auth_methods_force_mfa.aggregate_id = projections.users12.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users12.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users12.instance_id ` +
2023-07-14 13:16:16 +02:00
` AS OF SYSTEM TIME ' - 1 ms
`
prepareAuthMethodTypesRequiredCols = [ ] string {
"password_set" ,
"method_type" ,
"idps_count" ,
"force_mfa" ,
2023-07-20 06:06:16 +02:00
"force_mfa_local_only" ,
2023-07-14 13:16:16 +02:00
}
2023-02-27 22:36:43 +01:00
)
2022-01-20 13:21:59 +01:00
func Test_UserAuthMethodPrepares ( t * testing . T ) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := [ ] struct {
name string
prepare interface { }
want want
object interface { }
} {
{
name : "prepareUserAuthMethodsQuery no result" ,
prepare : prepareUserAuthMethodsQuery ,
want : want {
sqlExpectations : mockQueries (
2023-02-27 22:36:43 +01:00
regexp . QuoteMeta ( prepareUserAuthMethodsStmt ) ,
2022-01-20 13:21:59 +01:00
nil ,
nil ,
) ,
} ,
object : & AuthMethods { AuthMethods : [ ] * AuthMethod { } } ,
} ,
{
name : "prepareUserAuthMethodsQuery one result" ,
prepare : prepareUserAuthMethodsQuery ,
want : want {
sqlExpectations : mockQueries (
2023-02-27 22:36:43 +01:00
regexp . QuoteMeta ( prepareUserAuthMethodsStmt ) ,
prepareUserAuthMethodsCols ,
2022-01-20 13:21:59 +01:00
[ ] [ ] driver . Value {
{
"token_id" ,
testNow ,
testNow ,
"ro" ,
"user_id" ,
uint64 ( 20211108 ) ,
"name" ,
domain . MFAStateReady ,
domain . UserAuthMethodTypeU2F ,
} ,
} ,
) ,
} ,
object : & AuthMethods {
SearchResponse : SearchResponse {
Count : 1 ,
} ,
AuthMethods : [ ] * AuthMethod {
{
TokenID : "token_id" ,
CreationDate : testNow ,
ChangeDate : testNow ,
ResourceOwner : "ro" ,
UserID : "user_id" ,
Sequence : 20211108 ,
Name : "name" ,
State : domain . MFAStateReady ,
Type : domain . UserAuthMethodTypeU2F ,
} ,
} ,
} ,
} ,
{
name : "prepareUserAuthMethodsQuery multiple result" ,
prepare : prepareUserAuthMethodsQuery ,
want : want {
sqlExpectations : mockQueries (
2023-02-27 22:36:43 +01:00
regexp . QuoteMeta ( prepareUserAuthMethodsStmt ) ,
prepareUserAuthMethodsCols ,
2022-01-20 13:21:59 +01:00
[ ] [ ] driver . Value {
{
"token_id" ,
testNow ,
testNow ,
"ro" ,
"user_id" ,
uint64 ( 20211108 ) ,
"name" ,
domain . MFAStateReady ,
domain . UserAuthMethodTypeU2F ,
} ,
{
"token_id-2" ,
testNow ,
testNow ,
"ro" ,
"user_id" ,
uint64 ( 20211108 ) ,
"name-2" ,
domain . MFAStateReady ,
domain . UserAuthMethodTypePasswordless ,
} ,
} ,
) ,
} ,
object : & AuthMethods {
SearchResponse : SearchResponse {
Count : 2 ,
} ,
AuthMethods : [ ] * AuthMethod {
{
TokenID : "token_id" ,
CreationDate : testNow ,
ChangeDate : testNow ,
ResourceOwner : "ro" ,
UserID : "user_id" ,
Sequence : 20211108 ,
Name : "name" ,
State : domain . MFAStateReady ,
Type : domain . UserAuthMethodTypeU2F ,
} ,
{
TokenID : "token_id-2" ,
CreationDate : testNow ,
ChangeDate : testNow ,
ResourceOwner : "ro" ,
UserID : "user_id" ,
Sequence : 20211108 ,
Name : "name-2" ,
State : domain . MFAStateReady ,
Type : domain . UserAuthMethodTypePasswordless ,
} ,
} ,
} ,
} ,
{
name : "prepareUserAuthMethodsQuery sql err" ,
prepare : prepareUserAuthMethodsQuery ,
want : want {
sqlExpectations : mockQueryErr (
2023-02-27 22:36:43 +01:00
regexp . QuoteMeta ( prepareUserAuthMethodsStmt ) ,
2022-01-20 13:21:59 +01:00
sql . ErrConnDone ,
) ,
err : func ( err error ) ( error , bool ) {
if ! errors . Is ( err , sql . ErrConnDone ) {
return fmt . Errorf ( "err should be sql.ErrConnDone got: %w" , err ) , false
}
return nil , true
} ,
} ,
2023-08-22 12:49:22 +02:00
object : ( * AuthMethodTypes ) ( nil ) ,
2022-01-20 13:21:59 +01:00
} ,
2023-06-20 18:23:28 +02:00
{
name : "prepareActiveUserAuthMethodTypesQuery no result" ,
prepare : prepareActiveUserAuthMethodTypesQuery ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareActiveAuthMethodTypesStmt ) ,
nil ,
nil ,
) ,
} ,
object : & AuthMethodTypes { AuthMethodTypes : [ ] domain . UserAuthMethodType { } } ,
} ,
{
name : "prepareActiveUserAuthMethodTypesQuery one second factor" ,
prepare : prepareActiveUserAuthMethodTypesQuery ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareActiveAuthMethodTypesStmt ) ,
prepareActiveAuthMethodTypesCols ,
[ ] [ ] driver . Value {
{
true ,
domain . UserAuthMethodTypePasswordless ,
1 ,
} ,
} ,
) ,
} ,
object : & AuthMethodTypes {
SearchResponse : SearchResponse {
Count : 3 ,
} ,
AuthMethodTypes : [ ] domain . UserAuthMethodType {
domain . UserAuthMethodTypePasswordless ,
domain . UserAuthMethodTypePassword ,
domain . UserAuthMethodTypeIDP ,
} ,
} ,
} ,
{
name : "prepareActiveUserAuthMethodTypesQuery multiple second factors" ,
prepare : prepareActiveUserAuthMethodTypesQuery ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareActiveAuthMethodTypesStmt ) ,
prepareActiveAuthMethodTypesCols ,
[ ] [ ] driver . Value {
{
true ,
domain . UserAuthMethodTypePasswordless ,
1 ,
} ,
{
true ,
2023-08-02 18:57:53 +02:00
domain . UserAuthMethodTypeTOTP ,
2023-06-20 18:23:28 +02:00
1 ,
} ,
} ,
) ,
} ,
object : & AuthMethodTypes {
SearchResponse : SearchResponse {
Count : 4 ,
} ,
AuthMethodTypes : [ ] domain . UserAuthMethodType {
domain . UserAuthMethodTypePasswordless ,
2023-08-02 18:57:53 +02:00
domain . UserAuthMethodTypeTOTP ,
2023-06-20 18:23:28 +02:00
domain . UserAuthMethodTypePassword ,
domain . UserAuthMethodTypeIDP ,
} ,
} ,
} ,
{
name : "prepareActiveUserAuthMethodTypesQuery sql err" ,
prepare : prepareActiveUserAuthMethodTypesQuery ,
want : want {
sqlExpectations : mockQueryErr (
regexp . QuoteMeta ( prepareActiveAuthMethodTypesStmt ) ,
sql . ErrConnDone ,
) ,
err : func ( err error ) ( error , bool ) {
if ! errors . Is ( err , sql . ErrConnDone ) {
return fmt . Errorf ( "err should be sql.ErrConnDone got: %w" , err ) , false
}
return nil , true
} ,
} ,
2023-08-22 12:49:22 +02:00
object : ( * AuthMethodTypes ) ( nil ) ,
2023-06-20 18:23:28 +02:00
} ,
2023-07-14 13:16:16 +02:00
{
name : "prepareUserAuthMethodTypesRequiredQuery no result" ,
prepare : func ( ctx context . Context , db prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) ) {
builder , scan := prepareUserAuthMethodTypesRequiredQuery ( ctx , db )
return builder , func ( rows * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) {
2023-07-20 06:06:16 +02:00
authMethods , forceMFA , forceMFALocalOnly , err := scan ( rows )
2023-07-14 13:16:16 +02:00
if err != nil {
return nil , err
}
2023-07-20 06:06:16 +02:00
return & testUserAuthMethodTypesRequired { authMethods : authMethods , forceMFA : forceMFA , forceMFALocalOnly : forceMFALocalOnly } , nil
2023-07-14 13:16:16 +02:00
}
} ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareAuthMethodTypesRequiredStmt ) ,
nil ,
nil ,
) ,
} ,
object : & testUserAuthMethodTypesRequired { authMethods : [ ] domain . UserAuthMethodType { } , forceMFA : false } ,
} ,
{
name : "prepareUserAuthMethodTypesRequiredQuery one second factor" ,
prepare : func ( ctx context . Context , db prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) ) {
builder , scan := prepareUserAuthMethodTypesRequiredQuery ( ctx , db )
return builder , func ( rows * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) {
2023-07-20 06:06:16 +02:00
authMethods , forceMFA , forceMFALocalOnly , err := scan ( rows )
2023-07-14 13:16:16 +02:00
if err != nil {
return nil , err
}
2023-07-20 06:06:16 +02:00
return & testUserAuthMethodTypesRequired { authMethods : authMethods , forceMFA : forceMFA , forceMFALocalOnly : forceMFALocalOnly } , nil
2023-07-14 13:16:16 +02:00
}
} ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareAuthMethodTypesRequiredStmt ) ,
prepareAuthMethodTypesRequiredCols ,
[ ] [ ] driver . Value {
{
true ,
domain . UserAuthMethodTypePasswordless ,
1 ,
true ,
2023-07-20 06:06:16 +02:00
true ,
2023-07-14 13:16:16 +02:00
} ,
} ,
) ,
} ,
object : & testUserAuthMethodTypesRequired {
authMethods : [ ] domain . UserAuthMethodType {
domain . UserAuthMethodTypePasswordless ,
domain . UserAuthMethodTypePassword ,
domain . UserAuthMethodTypeIDP ,
} ,
2023-07-20 06:06:16 +02:00
forceMFA : true ,
forceMFALocalOnly : true ,
2023-07-14 13:16:16 +02:00
} ,
} ,
{
name : "prepareUserAuthMethodTypesRequiredQuery multiple second factors" ,
prepare : func ( ctx context . Context , db prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) ) {
builder , scan := prepareUserAuthMethodTypesRequiredQuery ( ctx , db )
return builder , func ( rows * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) {
2023-07-20 06:06:16 +02:00
authMethods , forceMFA , forceMFALocalOnly , err := scan ( rows )
2023-07-14 13:16:16 +02:00
if err != nil {
return nil , err
}
2023-07-20 06:06:16 +02:00
return & testUserAuthMethodTypesRequired { authMethods : authMethods , forceMFA : forceMFA , forceMFALocalOnly : forceMFALocalOnly } , nil
2023-07-14 13:16:16 +02:00
}
} ,
want : want {
sqlExpectations : mockQueries (
regexp . QuoteMeta ( prepareAuthMethodTypesRequiredStmt ) ,
prepareAuthMethodTypesRequiredCols ,
[ ] [ ] driver . Value {
{
true ,
domain . UserAuthMethodTypePasswordless ,
1 ,
true ,
2023-07-20 06:06:16 +02:00
true ,
2023-07-14 13:16:16 +02:00
} ,
{
true ,
2023-08-02 18:57:53 +02:00
domain . UserAuthMethodTypeTOTP ,
2023-07-14 13:16:16 +02:00
1 ,
true ,
2023-07-20 06:06:16 +02:00
true ,
2023-07-14 13:16:16 +02:00
} ,
} ,
) ,
} ,
object : & testUserAuthMethodTypesRequired {
authMethods : [ ] domain . UserAuthMethodType {
domain . UserAuthMethodTypePasswordless ,
2023-08-02 18:57:53 +02:00
domain . UserAuthMethodTypeTOTP ,
2023-07-14 13:16:16 +02:00
domain . UserAuthMethodTypePassword ,
domain . UserAuthMethodTypeIDP ,
} ,
2023-07-20 06:06:16 +02:00
forceMFA : true ,
forceMFALocalOnly : true ,
2023-07-14 13:16:16 +02:00
} ,
} ,
{
name : "prepareUserAuthMethodTypesRequiredQuery sql err" ,
prepare : func ( ctx context . Context , db prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) ) {
builder , scan := prepareUserAuthMethodTypesRequiredQuery ( ctx , db )
return builder , func ( rows * sql . Rows ) ( * testUserAuthMethodTypesRequired , error ) {
2023-07-20 06:06:16 +02:00
authMethods , forceMFA , forceMFALocalOnly , err := scan ( rows )
2023-07-14 13:16:16 +02:00
if err != nil {
return nil , err
}
2023-07-20 06:06:16 +02:00
return & testUserAuthMethodTypesRequired { authMethods : authMethods , forceMFA : forceMFA , forceMFALocalOnly : forceMFALocalOnly } , nil
2023-07-14 13:16:16 +02:00
}
} ,
want : want {
sqlExpectations : mockQueryErr (
regexp . QuoteMeta ( prepareAuthMethodTypesRequiredStmt ) ,
sql . ErrConnDone ,
) ,
err : func ( err error ) ( error , bool ) {
if ! errors . Is ( err , sql . ErrConnDone ) {
return fmt . Errorf ( "err should be sql.ErrConnDone got: %w" , err ) , false
}
return nil , true
} ,
} ,
object : nil ,
} ,
2022-01-20 13:21:59 +01:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2023-02-27 22:36:43 +01:00
assertPrepare ( t , tt . prepare , tt . object , tt . want . sqlExpectations , tt . want . err , defaultPrepareArgs ... )
2022-01-20 13:21:59 +01:00
} )
}
}
2023-07-14 13:16:16 +02:00
// testUserAuthMethodTypesRequired is required as assetPrepare is only able to return a single object from scan
type testUserAuthMethodTypesRequired struct {
2023-07-20 06:06:16 +02:00
authMethods [ ] domain . UserAuthMethodType
forceMFA bool
forceMFALocalOnly bool
2023-07-14 13:16:16 +02:00
}