mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-04 23:37:49 +02:00
1016 lines
34 KiB
Go
1016 lines
34 KiB
Go
package user_auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/account"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/tests"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/user"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/pborman/uuid"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
test *tests.Test
|
|
repo *Repository
|
|
)
|
|
|
|
// TestMain is the entry point for testing.
|
|
func TestMain(m *testing.M) {
|
|
os.Exit(testMain(m))
|
|
}
|
|
|
|
func testMain(m *testing.M) int {
|
|
test = tests.New()
|
|
defer test.TearDown()
|
|
|
|
tknGen := &auth.MockTokenGenerator{}
|
|
|
|
userRepo := user.MockRepository(test.MasterDB)
|
|
userAccRepo := user_account.NewRepository(test.MasterDB)
|
|
accPrefRepo := account_preference.NewRepository(test.MasterDB)
|
|
|
|
repo = NewRepository(test.MasterDB, tknGen, userRepo, userAccRepo, accPrefRepo)
|
|
|
|
return m.Run()
|
|
}
|
|
|
|
// TestAuthenticate validates the behavior around authenticating users.
|
|
func TestAuthenticate(t *testing.T) {
|
|
defer tests.Recover(t)
|
|
|
|
t.Log("Given the need to authenticate users")
|
|
{
|
|
t.Log("\tWhen handling a single User.")
|
|
{
|
|
ctx := tests.Context()
|
|
|
|
// Auth tokens are valid for an our and is verified against current time.
|
|
// Issue the token one hour ago.
|
|
now := time.Now().Add(time.Hour * -1)
|
|
|
|
// Try to authenticate an invalid user.
|
|
_, err := repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: "doesnotexist@gmail.com",
|
|
Password: "xy7",
|
|
}, time.Hour, now)
|
|
if errors.Cause(err) != ErrAuthenticationFailure {
|
|
t.Logf("\t\tGot : %+v", err)
|
|
t.Logf("\t\tWant: %+v", ErrAuthenticationFailure)
|
|
t.Fatalf("\t%s\tAuthenticate non existant user failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate non existant user ok.", tests.Success)
|
|
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_User)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tCreate user account ok.", tests.Success)
|
|
|
|
// Add 30 minutes to now to simulate time passing.
|
|
now = now.Add(time.Minute * 30)
|
|
|
|
acc2, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tCreate second account ok.", tests.Success)
|
|
|
|
// Associate second new account with user user. Need to ensure that now
|
|
// is always greater than the first user_account entry created so it will
|
|
// be returned consistently back in the same order, last.
|
|
account2Role := auth.RoleUser
|
|
_, err = repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usrAcc.UserID,
|
|
AccountID: acc2.ID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(account2Role)},
|
|
}, now)
|
|
|
|
// Add 30 minutes to now to simulate time passing.
|
|
now = now.Add(time.Minute * 5)
|
|
|
|
// Try to authenticate valid user with invalid password.
|
|
_, err = repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: usrAcc.User.Email,
|
|
Password: "xy7",
|
|
},
|
|
time.Hour, now)
|
|
if errors.Cause(err) != ErrAuthenticationFailure {
|
|
t.Logf("\t\tGot : %+v", err)
|
|
t.Logf("\t\tWant: %+v", ErrAuthenticationFailure)
|
|
t.Fatalf("\t%s\tAuthenticate user w/invalid password failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate user w/invalid password ok.", tests.Success)
|
|
|
|
// Verify that the user can be authenticated with the created user.
|
|
tkn1, err := repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: usrAcc.User.Email,
|
|
Password: usrAcc.User.Password,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate user failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate user ok.", tests.Success)
|
|
|
|
// Ensure the token string was correctly generated.
|
|
claims1, err := repo.TknGen.ParseClaims(tkn1.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn1.claims
|
|
expectClaims.RootUserID = ""
|
|
expectClaims.RootAccountID = ""
|
|
expectClaims.Subject = usrAcc.UserID
|
|
expectClaims.Audience = usrAcc.AccountID
|
|
|
|
if diff := cmpClaims(claims1, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate parse claims from token ok.", tests.Success)
|
|
|
|
// Try switching to a second account using the first set of claims.
|
|
tkn2, err := repo.SwitchAccount(ctx, claims1,
|
|
SwitchAccountRequest{AccountID: acc2.ID}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tSwitchAccount user failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tSwitchAccount user ok.", tests.Success)
|
|
|
|
// Ensure the token string was correctly generated.
|
|
claims2, err := repo.TknGen.ParseClaims(tkn2.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims = tkn2.claims
|
|
expectClaims.RootUserID = usrAcc.UserID
|
|
expectClaims.RootAccountID = acc2.ID
|
|
expectClaims.Subject = usrAcc.UserID
|
|
expectClaims.Audience = acc2.ID
|
|
|
|
if diff := cmpClaims(claims2, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
t.Logf("\t%s\tSwitchAccount parse claims from token ok.", tests.Success)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestUserUpdatePassword validates update user password works.
|
|
func TestUserUpdatePassword(t *testing.T) {
|
|
|
|
t.Log("Given the need ensure a user password can be updated.")
|
|
{
|
|
ctx := tests.Context()
|
|
|
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_User)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tCreate user account ok.", tests.Success)
|
|
|
|
// Verify that the user can be authenticated with the created user.
|
|
_, err = repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: usrAcc.User.Email,
|
|
Password: usrAcc.User.Password,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate failed.", tests.Failed)
|
|
}
|
|
|
|
// Update the users password.
|
|
newPass := uuid.NewRandom().String()
|
|
err = repo.User.UpdatePassword(ctx, auth.Claims{}, user.UserUpdatePasswordRequest{
|
|
ID: usrAcc.UserID,
|
|
Password: newPass,
|
|
PasswordConfirm: newPass,
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tUpdate password failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tUpdatePassword ok.", tests.Success)
|
|
|
|
// Verify that the user can be authenticated with the updated password.
|
|
_, err = repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: usrAcc.User.Email,
|
|
Password: newPass,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate ok.", tests.Success)
|
|
}
|
|
}
|
|
|
|
// TestUserResetPassword validates that reset password for a user works.
|
|
func TestUserResetPassword(t *testing.T) {
|
|
|
|
t.Log("Given the need ensure a user can reset their password.")
|
|
{
|
|
ctx := tests.Context()
|
|
|
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_User)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tCreate user account ok.", tests.Success)
|
|
|
|
ttl := time.Hour
|
|
|
|
// Make the reset password request.
|
|
resetHash, err := repo.User.ResetPassword(ctx, user.UserResetPasswordRequest{
|
|
Email: usrAcc.User.Email,
|
|
TTL: ttl,
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tResetPassword failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tResetPassword ok.", tests.Success)
|
|
|
|
// Assuming we have received the email and clicked the link, we now can ensure confirm works.
|
|
newPass := uuid.NewRandom().String()
|
|
reset, err := repo.User.ResetConfirm(ctx, user.UserResetConfirmRequest{
|
|
ResetHash: resetHash,
|
|
Password: newPass,
|
|
PasswordConfirm: newPass,
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tResetConfirm failed.", tests.Failed)
|
|
} else if reset.ID != usrAcc.User.ID {
|
|
t.Logf("\t\tGot : %+v", reset.ID)
|
|
t.Logf("\t\tWant: %+v", usrAcc.User.ID)
|
|
t.Fatalf("\t%s\tResetConfirm failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tResetConfirm ok.", tests.Success)
|
|
|
|
// Verify that the user can be authenticated with the updated password.
|
|
_, err = repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: usrAcc.User.Email,
|
|
Password: newPass,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate failed.", tests.Failed)
|
|
}
|
|
t.Logf("\t%s\tAuthenticate ok.", tests.Success)
|
|
}
|
|
}
|
|
|
|
// TestSwitchAccount validates the behavior around allowing users to switch between their accounts.
|
|
func TestSwitchAccount(t *testing.T) {
|
|
defer tests.Recover(t)
|
|
|
|
// Auth tokens are valid for an our and is verified against current time.
|
|
// Issue the token one hour ago.
|
|
now := time.Now().Add(time.Hour * -1)
|
|
|
|
ctx := tests.Context()
|
|
|
|
type authTest struct {
|
|
name string
|
|
root *user_account.MockUserAccountResponse
|
|
switch1Req SwitchAccountRequest
|
|
switch1Roles []user_account.UserAccountRole
|
|
switch1Scopes []string
|
|
switch1Err error
|
|
switch2Req SwitchAccountRequest
|
|
switch2Roles []user_account.UserAccountRole
|
|
switch2Scopes []string
|
|
switch2Err error
|
|
}
|
|
|
|
var authTests []authTest
|
|
|
|
// Test all the combinations there the user has access to all three accounts.
|
|
if true {
|
|
for _, roles := range [][]user_account.UserAccountRole{
|
|
[]user_account.UserAccountRole{user_account.UserAccountRole_Admin, user_account.UserAccountRole_Admin, user_account.UserAccountRole_Admin},
|
|
[]user_account.UserAccountRole{user_account.UserAccountRole_User, user_account.UserAccountRole_User, user_account.UserAccountRole_User},
|
|
[]user_account.UserAccountRole{user_account.UserAccountRole_Admin, user_account.UserAccountRole_User, user_account.UserAccountRole_Admin},
|
|
[]user_account.UserAccountRole{user_account.UserAccountRole_User, user_account.UserAccountRole_Admin, user_account.UserAccountRole_User},
|
|
} {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, roles[0])
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
// Create the second account.
|
|
now = now.Add(time.Minute)
|
|
acc2, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate the second account with root user.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usrAcc.UserID,
|
|
AccountID: acc2.ID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(roles[1])},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second account to user failed.", tests.Failed)
|
|
}
|
|
|
|
// Create the third account.
|
|
now = now.Add(time.Minute)
|
|
acc3, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate third account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate the third account with root user.
|
|
usrAcc3, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usrAcc.UserID,
|
|
AccountID: acc3.ID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(roles[2])},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking third account to user failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: fmt.Sprintf("Root account role %s -> role %s account 2 -> role %s account 3.",
|
|
roles[0], roles[1], roles[2]),
|
|
root: usrAcc,
|
|
switch1Req: SwitchAccountRequest{AccountID: acc2.ID},
|
|
switch1Roles: usrAcc2.Roles,
|
|
switch1Err: nil,
|
|
switch2Req: SwitchAccountRequest{AccountID: acc3.ID},
|
|
switch2Err: nil,
|
|
switch2Roles: usrAcc3.Roles,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Root account 1 -> invalid account 2
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_Admin)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
// Create the second account and don't associate it with the root user.
|
|
now = now.Add(time.Minute)
|
|
acc2, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root account 1 -> invalid account 2.",
|
|
root: usrAcc,
|
|
switch1Req: SwitchAccountRequest{AccountID: acc2.ID},
|
|
switch1Err: ErrAuthenticationFailure,
|
|
})
|
|
}
|
|
|
|
// Root account 1 -> valid account 2 with scopes -> valid account 3 with invalid scope.
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_Admin)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
// Create the second account.
|
|
now = now.Add(time.Minute)
|
|
acc2, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate the second account with root user.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usrAcc.UserID,
|
|
AccountID: acc2.ID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole_Admin},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second account to user failed.", tests.Failed)
|
|
}
|
|
|
|
// Create the third account.
|
|
now = now.Add(time.Minute)
|
|
acc3, err := account.MockAccount(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate third account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate the third account with root user.
|
|
usrAcc3, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usrAcc.UserID,
|
|
AccountID: acc3.ID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole_User},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking third account to user failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root account 1 -> valid account 2 with scopes -> valid account 3 with invalid scope.",
|
|
root: usrAcc,
|
|
switch1Req: SwitchAccountRequest{AccountID: acc2.ID},
|
|
switch1Roles: usrAcc2.Roles,
|
|
switch1Scopes: []string{user_account.UserAccountRole_User.String()},
|
|
switch1Err: nil,
|
|
switch2Req: SwitchAccountRequest{AccountID: acc3.ID},
|
|
switch2Roles: usrAcc3.Roles,
|
|
switch2Scopes: []string{user_account.UserAccountRole_Admin.String()},
|
|
switch2Err: ErrForbidden,
|
|
})
|
|
}
|
|
|
|
// Add 30 minutes to now to simulate time passing.
|
|
now = now.Add(time.Minute * 5)
|
|
|
|
t.Log("Given the need to switch accounts.")
|
|
{
|
|
for i, authTest := range authTests {
|
|
t.Logf("\tTest: %d\tWhen running test: %s", i, authTest.name)
|
|
{
|
|
// Verify that the user can be authenticated with the created user.
|
|
var claims1 auth.Claims
|
|
tkn1, err := repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: authTest.root.User.Email,
|
|
Password: authTest.root.User.Password,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate user failed.", tests.Failed)
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims1, err = repo.TknGen.ParseClaims(tkn1.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn1.claims
|
|
expectClaims.RootUserID = ""
|
|
expectClaims.RootAccountID = ""
|
|
expectClaims.Subject = authTest.root.UserID
|
|
expectClaims.Audience = authTest.root.AccountID
|
|
expectClaims.Roles = rolesStringSlice(authTest.root.Roles)
|
|
|
|
if diff := cmpClaims(claims1, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tAuthenticate root user with role %v ok.", tests.Success, authTest.root.Roles)
|
|
|
|
// Try to switch to account 2.
|
|
var claims2 auth.Claims
|
|
tkn2, err := repo.SwitchAccount(ctx, claims1, authTest.switch1Req, time.Hour, now, authTest.switch1Scopes...)
|
|
if err != authTest.switch1Err {
|
|
if errors.Cause(err) != authTest.switch1Err {
|
|
t.Log("\t\tExpected :", authTest.switch1Err)
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tSwitchAccount account 1 with role %v failed.", tests.Failed, authTest.switch1Roles)
|
|
}
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims2, err = repo.TknGen.ParseClaims(tkn2.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn2.claims
|
|
expectClaims.RootUserID = authTest.root.UserID
|
|
expectClaims.RootAccountID = authTest.switch1Req.AccountID
|
|
expectClaims.Subject = authTest.root.UserID
|
|
expectClaims.Audience = authTest.switch1Req.AccountID
|
|
|
|
if len(authTest.switch1Scopes) > 0 {
|
|
expectClaims.Roles = authTest.switch1Scopes
|
|
} else {
|
|
expectClaims.Roles = rolesStringSlice(authTest.switch1Roles)
|
|
}
|
|
|
|
if diff := cmpClaims(claims2, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tSwitchAccount account 1 with role %v ok.", tests.Success, authTest.switch1Roles)
|
|
|
|
// If the user can't login, don't need to test any further.
|
|
if authTest.switch1Err != nil || authTest.switch2Req.AccountID == "" {
|
|
continue
|
|
}
|
|
|
|
// Try to switch to account 3.
|
|
tkn3, err := repo.SwitchAccount(ctx, claims2, authTest.switch2Req, time.Hour, now, authTest.switch2Scopes...)
|
|
if err != authTest.switch2Err {
|
|
if errors.Cause(err) != authTest.switch2Err {
|
|
t.Log("\t\tExpected :", authTest.switch2Err)
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tSwitchAccount account 2 with role %v failed.", tests.Failed, authTest.switch2Roles)
|
|
}
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims3, err := repo.TknGen.ParseClaims(tkn3.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn3.claims
|
|
expectClaims.RootUserID = authTest.root.UserID
|
|
expectClaims.RootAccountID = authTest.switch2Req.AccountID
|
|
expectClaims.Subject = authTest.root.UserID
|
|
expectClaims.Audience = authTest.switch2Req.AccountID
|
|
|
|
if len(authTest.switch2Scopes) > 0 {
|
|
expectClaims.Roles = authTest.switch2Scopes
|
|
} else {
|
|
expectClaims.Roles = rolesStringSlice(authTest.switch2Roles)
|
|
}
|
|
|
|
if diff := cmpClaims(claims3, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tSwitchAccount account 2 with role %v ok.", tests.Success, authTest.switch2Roles)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestVirtualLogin validates the behavior around allowing users to virtual login users.
|
|
func TestVirtualLogin(t *testing.T) {
|
|
defer tests.Recover(t)
|
|
|
|
// Auth tokens are valid for an our and is verified against current time.
|
|
// Issue the token one hour ago.
|
|
now := time.Now().Add(time.Hour * -1)
|
|
|
|
ctx := tests.Context()
|
|
|
|
type authTest struct {
|
|
name string
|
|
root *user_account.MockUserAccountResponse
|
|
login1Req VirtualLoginRequest
|
|
login1Err error
|
|
login1Role user_account.UserAccountRole
|
|
login2Req VirtualLoginRequest
|
|
login2Err error
|
|
login2Role user_account.UserAccountRole
|
|
login2Logout bool
|
|
}
|
|
|
|
var authTests []authTest
|
|
|
|
// Root admin -> role admin -> role admin
|
|
{
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_Admin)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
usr2, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_Admin)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second user to account failed.", tests.Failed)
|
|
}
|
|
|
|
usr3, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc3, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_Admin)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking third user to account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root admin -> role admin -> role admin",
|
|
root: usrAcc,
|
|
login1Req: VirtualLoginRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login1Role: usrAcc2.Roles[0],
|
|
login1Err: nil,
|
|
login2Req: VirtualLoginRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login2Err: nil,
|
|
login2Role: usrAcc3.Roles[0],
|
|
login2Logout: true,
|
|
})
|
|
}
|
|
|
|
// Root admin -> role admin -> role user
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_Admin)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
usr2, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_Admin)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second user to account failed.", tests.Failed)
|
|
}
|
|
|
|
usr3, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc3, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_User)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking third user to account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root admin -> role admin -> role user",
|
|
root: usrAcc,
|
|
login1Req: VirtualLoginRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login1Err: nil,
|
|
login1Role: usrAcc2.Roles[0],
|
|
login2Req: VirtualLoginRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login2Err: nil,
|
|
login2Role: usrAcc3.Roles[0],
|
|
login2Logout: true,
|
|
})
|
|
}
|
|
|
|
// Root admin -> role user -> role admin
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_Admin)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
usr2, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_User)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second user to account failed.", tests.Failed)
|
|
}
|
|
|
|
usr3, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc3, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_Admin)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking third user to account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root admin -> role user -> role admin",
|
|
root: usrAcc,
|
|
login1Req: VirtualLoginRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login1Err: nil,
|
|
login1Role: usrAcc2.Roles[0],
|
|
login2Req: VirtualLoginRequest{
|
|
UserID: usr3.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login2Err: ErrForbidden,
|
|
login2Role: usrAcc3.Roles[0],
|
|
login2Logout: true,
|
|
})
|
|
}
|
|
|
|
// Root user -> role admin
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_User)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
usr2, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_Admin)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second user to account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root user -> role admin",
|
|
root: usrAcc,
|
|
login1Req: VirtualLoginRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login1Err: ErrForbidden,
|
|
login1Role: usrAcc2.Roles[0],
|
|
login2Logout: true,
|
|
})
|
|
}
|
|
|
|
// Root user -> role user
|
|
if true {
|
|
// Create a new user for testing.
|
|
usrAcc, err := user_account.MockUserAccount(ctx, test.MasterDB, now, user_account.UserAccountRole_User)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
|
}
|
|
|
|
usr2, err := user.MockUser(ctx, test.MasterDB, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tCreate second account failed.", tests.Failed)
|
|
}
|
|
|
|
// Associate second user with basic role associated with the same account.
|
|
usrAcc2, err := repo.UserAccount.Create(ctx, auth.Claims{}, user_account.UserAccountCreateRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole(user_account.UserAccountRole_User)},
|
|
}, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tLinking second user to account failed.", tests.Failed)
|
|
}
|
|
|
|
authTests = append(authTests, authTest{
|
|
name: "Root user -> role admin",
|
|
root: usrAcc,
|
|
login1Req: VirtualLoginRequest{
|
|
UserID: usr2.ID,
|
|
AccountID: usrAcc.AccountID,
|
|
},
|
|
login1Err: ErrForbidden,
|
|
login1Role: usrAcc2.Roles[0],
|
|
login2Logout: true,
|
|
})
|
|
}
|
|
|
|
// Add 30 minutes to now to simulate time passing.
|
|
now = now.Add(time.Minute * 5)
|
|
|
|
t.Log("Given the need to virtual login.")
|
|
{
|
|
for i, authTest := range authTests {
|
|
t.Logf("\tTest: %d\tWhen running test: %s", i, authTest.name)
|
|
{
|
|
// Verify that the user can be authenticated with the created user.
|
|
var claims1 auth.Claims
|
|
tkn1, err := repo.Authenticate(ctx,
|
|
AuthenticateRequest{
|
|
Email: authTest.root.User.Email,
|
|
Password: authTest.root.User.Password,
|
|
}, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tAuthenticate user failed.", tests.Failed)
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims1, err = repo.TknGen.ParseClaims(tkn1.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn1.claims
|
|
expectClaims.RootUserID = ""
|
|
expectClaims.RootAccountID = ""
|
|
expectClaims.Subject = authTest.root.UserID
|
|
expectClaims.Audience = authTest.root.AccountID
|
|
|
|
// Hack for Unhandled Exception in go-cmp@v0.3.0/cmp/options.go:229
|
|
if diff := cmpClaims(claims1, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tAuthenticate root user with role %s ok.", tests.Success, authTest.root.Roles[0])
|
|
|
|
// Try virtual login to user 2.
|
|
var claims2 auth.Claims
|
|
tkn2, err := repo.VirtualLogin(ctx, claims1, authTest.login1Req, time.Hour, now)
|
|
if err != authTest.login1Err {
|
|
if errors.Cause(err) != authTest.login1Err {
|
|
t.Log("\t\tExpected :", authTest.login1Err)
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tVirtualLogin user 1 with role %s failed.", tests.Failed, authTest.login1Role)
|
|
}
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims2, err = repo.TknGen.ParseClaims(tkn2.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn2.claims
|
|
expectClaims.RootUserID = authTest.root.UserID
|
|
expectClaims.RootAccountID = authTest.root.AccountID
|
|
expectClaims.Subject = authTest.login1Req.UserID
|
|
expectClaims.Audience = authTest.login1Req.AccountID
|
|
|
|
// Hack for Unhandled Exception in go-cmp@v0.3.0/cmp/options.go:229
|
|
if diff := cmpClaims(claims2, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tVirtualLogin user 1 with role %s ok.", tests.Success, authTest.login1Role)
|
|
|
|
// If the user can't login, don't need to test any further.
|
|
if authTest.login1Err != nil {
|
|
continue
|
|
}
|
|
|
|
// Try virtual login to user 3.
|
|
tkn3, err := repo.VirtualLogin(ctx, claims2, authTest.login2Req, time.Hour, now)
|
|
if err != authTest.login2Err {
|
|
if errors.Cause(err) != authTest.login2Err {
|
|
t.Log("\t\tExpected :", authTest.login2Err)
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tVirtualLogin user 2 with role %s failed.", tests.Failed, authTest.login2Role)
|
|
}
|
|
} else {
|
|
// Ensure the token string was correctly generated.
|
|
claims3, err := repo.TknGen.ParseClaims(tkn3.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tkn3.claims
|
|
expectClaims.RootUserID = authTest.root.UserID
|
|
expectClaims.RootAccountID = authTest.root.AccountID
|
|
expectClaims.Subject = authTest.login2Req.UserID
|
|
expectClaims.Audience = authTest.login2Req.AccountID
|
|
|
|
// Hack for Unhandled Exception in go-cmp@v0.3.0/cmp/options.go:229
|
|
if diff := cmpClaims(claims3, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
}
|
|
t.Logf("\t%s\tVirtualLogin user 2 with role %s ok.", tests.Success, authTest.login2Role)
|
|
|
|
if authTest.login2Logout {
|
|
tknOut, err := repo.VirtualLogout(ctx, claims2, time.Hour, now)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tVirtualLogout user 2 failed.", tests.Failed)
|
|
}
|
|
|
|
// Ensure the token string was correctly generated.
|
|
claimsOut, err := repo.TknGen.ParseClaims(tknOut.AccessToken)
|
|
if err != nil {
|
|
t.Log("\t\tGot :", err)
|
|
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
|
|
}
|
|
expectClaims := tknOut.claims
|
|
expectClaims.RootUserID = authTest.root.UserID
|
|
expectClaims.RootAccountID = authTest.root.AccountID
|
|
expectClaims.Subject = authTest.root.UserID
|
|
expectClaims.Audience = authTest.root.AccountID
|
|
|
|
if diff := cmpClaims(claimsOut, expectClaims); diff != "" {
|
|
t.Fatalf("\t%s\tExpected parsed claims to match from token. Diff:\n%s", tests.Failed, diff)
|
|
}
|
|
t.Logf("\t%s\tVirtualLogout user 2 with role %s ok.", tests.Success, authTest.login2Role)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rolesStringSlice converts a list of roles to a string slice.
|
|
func rolesStringSlice(roles []user_account.UserAccountRole) []string {
|
|
var l []string
|
|
for _, r := range roles {
|
|
l = append(l, string(r))
|
|
}
|
|
return l
|
|
}
|
|
|
|
// cmpClaims is a hack for Unhandled Exception in go-cmp@v0.3.0/cmp/options.go:229
|
|
func cmpClaims(actualClaims, expectedclaims auth.Claims) string {
|
|
dat1, _ := json.Marshal(actualClaims)
|
|
dat2, _ := json.Marshal(expectedclaims)
|
|
return cmp.Diff(string(dat1), string(dat2))
|
|
}
|