2023-05-05 15:34:53 +00:00
package query
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
2023-10-12 12:16:59 +00:00
"net"
"net/http"
2023-05-05 15:34:53 +00:00
"regexp"
"testing"
sq "github.com/Masterminds/squirrel"
2023-10-12 12:16:59 +00:00
"github.com/muhlemmer/gu"
2023-05-05 15:34:53 +00:00
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/domain"
errs "github.com/zitadel/zitadel/internal/errors"
)
var (
2023-11-16 06:35:50 +00:00
expectedSessionQuery = regexp . QuoteMeta ( ` SELECT projections.sessions8.id, ` +
` projections.sessions8.creation_date, ` +
` projections.sessions8.change_date, ` +
` projections.sessions8.sequence, ` +
` projections.sessions8.state, ` +
` projections.sessions8.resource_owner, ` +
` projections.sessions8.creator, ` +
` projections.sessions8.user_id, ` +
` projections.sessions8.user_resource_owner, ` +
` projections.sessions8.user_checked_at, ` +
2023-05-05 15:34:53 +00:00
` projections.login_names2.login_name, ` +
` projections.users8_humans.display_name, ` +
2023-11-16 06:35:50 +00:00
` projections.sessions8.password_checked_at, ` +
` projections.sessions8.intent_checked_at, ` +
` projections.sessions8.webauthn_checked_at, ` +
` projections.sessions8.webauthn_user_verified, ` +
` projections.sessions8.totp_checked_at, ` +
` projections.sessions8.otp_sms_checked_at, ` +
` projections.sessions8.otp_email_checked_at, ` +
` projections.sessions8.metadata, ` +
` projections.sessions8.token_id, ` +
` projections.sessions8.user_agent_fingerprint_id, ` +
` projections.sessions8.user_agent_ip, ` +
` projections.sessions8.user_agent_description, ` +
` projections.sessions8.user_agent_header, ` +
` projections.sessions8.expiration ` +
` FROM projections.sessions8 ` +
` LEFT JOIN projections.login_names2 ON projections.sessions8.user_id = projections.login_names2.user_id AND projections.sessions8.instance_id = projections.login_names2.instance_id ` +
` LEFT JOIN projections.users8_humans ON projections.sessions8.user_id = projections.users8_humans.user_id AND projections.sessions8.instance_id = projections.users8_humans.instance_id ` +
` LEFT JOIN projections.users8 ON projections.sessions8.user_id = projections.users8.id AND projections.sessions8.instance_id = projections.users8.instance_id ` +
2023-05-05 15:34:53 +00:00
` AS OF SYSTEM TIME '-1 ms' ` )
2023-11-16 06:35:50 +00:00
expectedSessionsQuery = regexp . QuoteMeta ( ` SELECT projections.sessions8.id, ` +
` projections.sessions8.creation_date, ` +
` projections.sessions8.change_date, ` +
` projections.sessions8.sequence, ` +
` projections.sessions8.state, ` +
` projections.sessions8.resource_owner, ` +
` projections.sessions8.creator, ` +
` projections.sessions8.user_id, ` +
` projections.sessions8.user_resource_owner, ` +
` projections.sessions8.user_checked_at, ` +
2023-05-05 15:34:53 +00:00
` projections.login_names2.login_name, ` +
` projections.users8_humans.display_name, ` +
2023-11-16 06:35:50 +00:00
` projections.sessions8.password_checked_at, ` +
` projections.sessions8.intent_checked_at, ` +
` projections.sessions8.webauthn_checked_at, ` +
` projections.sessions8.webauthn_user_verified, ` +
` projections.sessions8.totp_checked_at, ` +
` projections.sessions8.otp_sms_checked_at, ` +
` projections.sessions8.otp_email_checked_at, ` +
` projections.sessions8.metadata, ` +
` projections.sessions8.expiration, ` +
2023-05-05 15:34:53 +00:00
` COUNT(*) OVER () ` +
2023-11-16 06:35:50 +00:00
` FROM projections.sessions8 ` +
` LEFT JOIN projections.login_names2 ON projections.sessions8.user_id = projections.login_names2.user_id AND projections.sessions8.instance_id = projections.login_names2.instance_id ` +
` LEFT JOIN projections.users8_humans ON projections.sessions8.user_id = projections.users8_humans.user_id AND projections.sessions8.instance_id = projections.users8_humans.instance_id ` +
` LEFT JOIN projections.users8 ON projections.sessions8.user_id = projections.users8.id AND projections.sessions8.instance_id = projections.users8.instance_id ` +
2023-05-05 15:34:53 +00:00
` AS OF SYSTEM TIME '-1 ms' ` )
sessionCols = [ ] string {
"id" ,
"creation_date" ,
"change_date" ,
"sequence" ,
"state" ,
"resource_owner" ,
"creator" ,
"user_id" ,
2023-11-16 06:35:50 +00:00
"user_resource_owner" ,
2023-05-05 15:34:53 +00:00
"user_checked_at" ,
"login_name" ,
"display_name" ,
"password_checked_at" ,
2023-06-21 14:06:18 +00:00
"intent_checked_at" ,
2023-08-11 15:36:18 +00:00
"webauthn_checked_at" ,
"webauthn_user_verified" ,
2023-08-15 09:50:42 +00:00
"totp_checked_at" ,
2023-08-24 09:41:52 +00:00
"otp_sms_checked_at" ,
"otp_email_checked_at" ,
2023-05-05 15:34:53 +00:00
"metadata" ,
"token" ,
2023-10-12 12:16:59 +00:00
"user_agent_fingerprint_id" ,
"user_agent_ip" ,
"user_agent_description" ,
"user_agent_header" ,
2023-11-06 09:48:28 +00:00
"expiration" ,
2023-05-05 15:34:53 +00:00
}
sessionsCols = [ ] string {
"id" ,
"creation_date" ,
"change_date" ,
"sequence" ,
"state" ,
"resource_owner" ,
"creator" ,
"user_id" ,
2023-11-16 06:35:50 +00:00
"user_resource_owner" ,
2023-05-05 15:34:53 +00:00
"user_checked_at" ,
"login_name" ,
"display_name" ,
"password_checked_at" ,
2023-06-21 14:06:18 +00:00
"intent_checked_at" ,
2023-08-11 15:36:18 +00:00
"webauthn_checked_at" ,
"webauthn_user_verified" ,
2023-08-15 09:50:42 +00:00
"totp_checked_at" ,
2023-08-24 09:41:52 +00:00
"otp_sms_checked_at" ,
"otp_email_checked_at" ,
2023-05-05 15:34:53 +00:00
"metadata" ,
2023-11-06 09:48:28 +00:00
"expiration" ,
2023-05-05 15:34:53 +00:00
"count" ,
}
)
func Test_SessionsPrepare ( t * testing . T ) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := [ ] struct {
name string
prepare interface { }
want want
object interface { }
} {
{
name : "prepareSessionsQuery no result" ,
prepare : prepareSessionsQuery ,
want : want {
sqlExpectations : mockQueries (
expectedSessionsQuery ,
nil ,
nil ,
) ,
} ,
object : & Sessions { Sessions : [ ] * Session { } } ,
} ,
{
name : "prepareSessionQuery" ,
prepare : prepareSessionsQuery ,
want : want {
sqlExpectations : mockQueries (
expectedSessionsQuery ,
sessionsCols ,
[ ] [ ] driver . Value {
{
"session-id" ,
testNow ,
testNow ,
uint64 ( 20211109 ) ,
domain . SessionStateActive ,
"ro" ,
"creator" ,
"user-id" ,
2023-11-16 06:35:50 +00:00
"resourceOwner" ,
2023-05-05 15:34:53 +00:00
testNow ,
"login-name" ,
"display-name" ,
testNow ,
2023-06-07 15:28:42 +00:00
testNow ,
2023-06-21 14:06:18 +00:00
testNow ,
2023-08-11 15:36:18 +00:00
true ,
2023-08-15 09:50:42 +00:00
testNow ,
2023-08-24 09:41:52 +00:00
testNow ,
testNow ,
2023-05-05 15:34:53 +00:00
[ ] byte ( ` { "key": "dmFsdWU="} ` ) ,
2023-11-06 09:48:28 +00:00
testNow ,
2023-05-05 15:34:53 +00:00
} ,
} ,
) ,
} ,
object : & Sessions {
SearchResponse : SearchResponse {
Count : 1 ,
} ,
Sessions : [ ] * Session {
{
ID : "session-id" ,
CreationDate : testNow ,
ChangeDate : testNow ,
Sequence : 20211109 ,
State : domain . SessionStateActive ,
ResourceOwner : "ro" ,
Creator : "creator" ,
UserFactor : SessionUserFactor {
UserID : "user-id" ,
UserCheckedAt : testNow ,
LoginName : "login-name" ,
DisplayName : "display-name" ,
2023-07-14 11:16:16 +00:00
ResourceOwner : "resourceOwner" ,
2023-05-05 15:34:53 +00:00
} ,
PasswordFactor : SessionPasswordFactor {
PasswordCheckedAt : testNow ,
} ,
2023-06-21 14:06:18 +00:00
IntentFactor : SessionIntentFactor {
IntentCheckedAt : testNow ,
} ,
2023-08-11 15:36:18 +00:00
WebAuthNFactor : SessionWebAuthNFactor {
WebAuthNCheckedAt : testNow ,
UserVerified : true ,
2023-06-07 15:28:42 +00:00
} ,
2023-08-15 09:50:42 +00:00
TOTPFactor : SessionTOTPFactor {
TOTPCheckedAt : testNow ,
} ,
2023-08-24 09:41:52 +00:00
OTPSMSFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
OTPEmailFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
2023-05-05 15:34:53 +00:00
Metadata : map [ string ] [ ] byte {
"key" : [ ] byte ( "value" ) ,
} ,
2023-11-06 09:48:28 +00:00
Expiration : testNow ,
2023-05-05 15:34:53 +00:00
} ,
} ,
} ,
} ,
{
name : "prepareSessionsQuery multiple result" ,
prepare : prepareSessionsQuery ,
want : want {
sqlExpectations : mockQueries (
expectedSessionsQuery ,
sessionsCols ,
[ ] [ ] driver . Value {
{
"session-id" ,
testNow ,
testNow ,
uint64 ( 20211109 ) ,
domain . SessionStateActive ,
"ro" ,
"creator" ,
"user-id" ,
2023-11-16 06:35:50 +00:00
"resourceOwner" ,
2023-05-05 15:34:53 +00:00
testNow ,
"login-name" ,
"display-name" ,
testNow ,
2023-06-07 15:28:42 +00:00
testNow ,
2023-06-21 14:06:18 +00:00
testNow ,
2023-08-11 15:36:18 +00:00
true ,
2023-08-15 09:50:42 +00:00
testNow ,
2023-08-24 09:41:52 +00:00
testNow ,
testNow ,
2023-05-05 15:34:53 +00:00
[ ] byte ( ` { "key": "dmFsdWU="} ` ) ,
2023-11-06 09:48:28 +00:00
testNow ,
2023-05-05 15:34:53 +00:00
} ,
{
"session-id2" ,
testNow ,
testNow ,
uint64 ( 20211109 ) ,
domain . SessionStateActive ,
"ro" ,
"creator2" ,
"user-id2" ,
2023-11-16 06:35:50 +00:00
"resourceOwner" ,
2023-05-05 15:34:53 +00:00
testNow ,
"login-name2" ,
"display-name2" ,
testNow ,
2023-06-07 15:28:42 +00:00
testNow ,
2023-06-21 14:06:18 +00:00
testNow ,
2023-08-11 15:36:18 +00:00
false ,
2023-08-15 09:50:42 +00:00
testNow ,
2023-08-24 09:41:52 +00:00
testNow ,
testNow ,
2023-05-05 15:34:53 +00:00
[ ] byte ( ` { "key": "dmFsdWU="} ` ) ,
2023-11-06 09:48:28 +00:00
testNow ,
2023-05-05 15:34:53 +00:00
} ,
} ,
) ,
} ,
object : & Sessions {
SearchResponse : SearchResponse {
Count : 2 ,
} ,
Sessions : [ ] * Session {
{
ID : "session-id" ,
CreationDate : testNow ,
ChangeDate : testNow ,
Sequence : 20211109 ,
State : domain . SessionStateActive ,
ResourceOwner : "ro" ,
Creator : "creator" ,
UserFactor : SessionUserFactor {
UserID : "user-id" ,
UserCheckedAt : testNow ,
LoginName : "login-name" ,
DisplayName : "display-name" ,
2023-07-14 11:16:16 +00:00
ResourceOwner : "resourceOwner" ,
2023-05-05 15:34:53 +00:00
} ,
PasswordFactor : SessionPasswordFactor {
PasswordCheckedAt : testNow ,
} ,
2023-06-21 14:06:18 +00:00
IntentFactor : SessionIntentFactor {
IntentCheckedAt : testNow ,
} ,
2023-08-11 15:36:18 +00:00
WebAuthNFactor : SessionWebAuthNFactor {
WebAuthNCheckedAt : testNow ,
UserVerified : true ,
2023-06-07 15:28:42 +00:00
} ,
2023-08-15 09:50:42 +00:00
TOTPFactor : SessionTOTPFactor {
TOTPCheckedAt : testNow ,
} ,
2023-08-24 09:41:52 +00:00
OTPSMSFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
OTPEmailFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
2023-05-05 15:34:53 +00:00
Metadata : map [ string ] [ ] byte {
"key" : [ ] byte ( "value" ) ,
} ,
2023-11-06 09:48:28 +00:00
Expiration : testNow ,
2023-05-05 15:34:53 +00:00
} ,
{
ID : "session-id2" ,
CreationDate : testNow ,
ChangeDate : testNow ,
Sequence : 20211109 ,
State : domain . SessionStateActive ,
ResourceOwner : "ro" ,
Creator : "creator2" ,
UserFactor : SessionUserFactor {
UserID : "user-id2" ,
UserCheckedAt : testNow ,
LoginName : "login-name2" ,
DisplayName : "display-name2" ,
2023-07-14 11:16:16 +00:00
ResourceOwner : "resourceOwner" ,
2023-05-05 15:34:53 +00:00
} ,
PasswordFactor : SessionPasswordFactor {
PasswordCheckedAt : testNow ,
} ,
2023-06-21 14:06:18 +00:00
IntentFactor : SessionIntentFactor {
IntentCheckedAt : testNow ,
} ,
2023-08-11 15:36:18 +00:00
WebAuthNFactor : SessionWebAuthNFactor {
WebAuthNCheckedAt : testNow ,
UserVerified : false ,
2023-06-07 15:28:42 +00:00
} ,
2023-08-15 09:50:42 +00:00
TOTPFactor : SessionTOTPFactor {
TOTPCheckedAt : testNow ,
} ,
2023-08-24 09:41:52 +00:00
OTPSMSFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
OTPEmailFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
2023-05-05 15:34:53 +00:00
Metadata : map [ string ] [ ] byte {
"key" : [ ] byte ( "value" ) ,
} ,
2023-11-06 09:48:28 +00:00
Expiration : testNow ,
2023-05-05 15:34:53 +00:00
} ,
} ,
} ,
} ,
{
name : "prepareSessionsQuery sql err" ,
prepare : prepareSessionsQuery ,
want : want {
sqlExpectations : mockQueryErr (
expectedSessionsQuery ,
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 10:49:22 +00:00
object : ( * Sessions ) ( nil ) ,
2023-05-05 15:34:53 +00:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
assertPrepare ( t , tt . prepare , tt . object , tt . want . sqlExpectations , tt . want . err , defaultPrepareArgs ... )
} )
}
}
func Test_SessionPrepare ( t * testing . T ) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := [ ] struct {
name string
prepare interface { }
want want
object interface { }
} {
{
name : "prepareSessionQuery no result" ,
prepare : prepareSessionQueryTesting ( t , "" ) ,
want : want {
2023-08-22 10:49:22 +00:00
sqlExpectations : mockQueriesScanErr (
2023-05-05 15:34:53 +00:00
expectedSessionQuery ,
nil ,
nil ,
) ,
err : func ( err error ) ( error , bool ) {
if ! errs . IsNotFound ( err ) {
return fmt . Errorf ( "err should be zitadel.NotFoundError got: %w" , err ) , false
}
return nil , true
} ,
} ,
object : ( * Session ) ( nil ) ,
} ,
{
name : "prepareSessionQuery found" ,
prepare : prepareSessionQueryTesting ( t , "tokenID" ) ,
want : want {
sqlExpectations : mockQuery (
expectedSessionQuery ,
sessionCols ,
[ ] driver . Value {
"session-id" ,
testNow ,
testNow ,
uint64 ( 20211109 ) ,
domain . SessionStateActive ,
"ro" ,
"creator" ,
"user-id" ,
2023-11-16 06:35:50 +00:00
"resourceOwner" ,
2023-05-05 15:34:53 +00:00
testNow ,
"login-name" ,
"display-name" ,
testNow ,
2023-06-07 15:28:42 +00:00
testNow ,
2023-06-21 14:06:18 +00:00
testNow ,
2023-08-11 15:36:18 +00:00
true ,
2023-08-15 09:50:42 +00:00
testNow ,
2023-08-24 09:41:52 +00:00
testNow ,
testNow ,
2023-05-05 15:34:53 +00:00
[ ] byte ( ` { "key": "dmFsdWU="} ` ) ,
"tokenID" ,
2023-10-12 12:16:59 +00:00
"fingerPrintID" ,
"1.2.3.4" ,
"agentDescription" ,
[ ] byte ( ` { "foo":["foo","bar"]} ` ) ,
2023-11-06 09:48:28 +00:00
testNow ,
2023-05-05 15:34:53 +00:00
} ,
) ,
} ,
object : & Session {
ID : "session-id" ,
CreationDate : testNow ,
ChangeDate : testNow ,
Sequence : 20211109 ,
State : domain . SessionStateActive ,
ResourceOwner : "ro" ,
Creator : "creator" ,
UserFactor : SessionUserFactor {
UserID : "user-id" ,
UserCheckedAt : testNow ,
LoginName : "login-name" ,
DisplayName : "display-name" ,
2023-07-14 11:16:16 +00:00
ResourceOwner : "resourceOwner" ,
2023-05-05 15:34:53 +00:00
} ,
PasswordFactor : SessionPasswordFactor {
PasswordCheckedAt : testNow ,
} ,
2023-06-21 14:06:18 +00:00
IntentFactor : SessionIntentFactor {
IntentCheckedAt : testNow ,
} ,
2023-08-11 15:36:18 +00:00
WebAuthNFactor : SessionWebAuthNFactor {
WebAuthNCheckedAt : testNow ,
UserVerified : true ,
2023-06-07 15:28:42 +00:00
} ,
2023-08-15 09:50:42 +00:00
TOTPFactor : SessionTOTPFactor {
TOTPCheckedAt : testNow ,
} ,
2023-08-24 09:41:52 +00:00
OTPSMSFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
OTPEmailFactor : SessionOTPFactor {
OTPCheckedAt : testNow ,
} ,
2023-05-05 15:34:53 +00:00
Metadata : map [ string ] [ ] byte {
"key" : [ ] byte ( "value" ) ,
} ,
2023-10-12 12:16:59 +00:00
UserAgent : domain . UserAgent {
FingerprintID : gu . Ptr ( "fingerPrintID" ) ,
IP : net . IPv4 ( 1 , 2 , 3 , 4 ) ,
Description : gu . Ptr ( "agentDescription" ) ,
Header : http . Header { "foo" : [ ] string { "foo" , "bar" } } ,
} ,
2023-11-06 09:48:28 +00:00
Expiration : testNow ,
2023-05-05 15:34:53 +00:00
} ,
} ,
{
name : "prepareSessionQuery sql err" ,
prepare : prepareSessionQueryTesting ( t , "" ) ,
want : want {
sqlExpectations : mockQueryErr (
expectedSessionQuery ,
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 10:49:22 +00:00
object : ( * Session ) ( nil ) ,
2023-05-05 15:34:53 +00:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
assertPrepare ( t , tt . prepare , tt . object , tt . want . sqlExpectations , tt . want . err , defaultPrepareArgs ... )
} )
}
}
func prepareSessionQueryTesting ( t * testing . T , token string ) func ( context . Context , prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Row ) ( * Session , error ) ) {
return func ( ctx context . Context , db prepareDatabase ) ( sq . SelectBuilder , func ( * sql . Row ) ( * Session , error ) ) {
builder , scan := prepareSessionQuery ( ctx , db )
return builder , func ( row * sql . Row ) ( * Session , error ) {
session , tokenID , err := scan ( row )
require . Equal ( t , tokenID , token )
return session , err
}
}
}