authelia/internal/suites/suite_duo_push_test.go
James Elliott ad8e844af6
feat(totp): algorithm and digits config (#2634)
Allow users to configure the TOTP Algorithm and Digits. This should be used with caution as many TOTP applications do not support it. Some will also fail to notify the user that there is an issue. i.e. if the algorithm in the QR code is sha512, they continue to generate one time passwords with sha1. In addition this drastically refactors TOTP in general to be more user friendly by not forcing them to register a new device if the administrator changes the period (or algorithm).

Fixes #1226.
2021-12-01 23:11:29 +11:00

496 lines
17 KiB
Go

package suites
import (
"context"
"log"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/authelia/authelia/v4/internal/duo"
"github.com/authelia/authelia/v4/internal/models"
"github.com/authelia/authelia/v4/internal/storage"
)
type DuoPushWebDriverSuite struct {
*RodSuite
}
func NewDuoPushWebDriverSuite() *DuoPushWebDriverSuite {
return &DuoPushWebDriverSuite{RodSuite: new(RodSuite)}
}
func (s *DuoPushWebDriverSuite) SetupSuite() {
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.RodSession = browser
}
func (s *DuoPushWebDriverSuite) TearDownSuite() {
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
}
}
func (s *DuoPushWebDriverSuite) SetupTest() {
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (s *DuoPushWebDriverSuite) TearDownTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
s.collectCoverage(s.Page)
s.MustClose()
}()
// Set default 2FA preference and clean up any Duo device already in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferred2FAMethod(ctx, "john", "totp"))
require.NoError(s.T(), provider.DeletePreferredDuoDevice(ctx, "john"))
}
func (s *DuoPushWebDriverSuite) TestShouldBypassDeviceSelection() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "allow",
StatusMessage: "Allowing unknown user",
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldDenyDeviceSelection() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "deny",
StatusMessage: "We're sorry, access is not allowed.",
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Device selection was denied by Duo policy")
}
func (s *DuoPushWebDriverSuite) TestShouldAskUserToRegister() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "enroll",
EnrollPortalURL: "https://api-example.duosecurity.com/portal?code=1234567890ABCDEF&akey=12345ABCDEFGHIJ67890",
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-not-registered")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "No compatible device found")
enrollPage := s.Page.MustWaitOpen()
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "register-link").MustClick()
s.Page = enrollPage()
assert.Contains(s.T(), s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "description").MustText(), "This enrollment code has expired. Contact your administrator to get a new enrollment code.")
}
func (s *DuoPushWebDriverSuite) TestShouldAutoSelectDevice() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}},
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
// Authenticate
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
// Switch Method where single Device should be selected automatically.
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Context(ctx))
// Re-Login the user
s.doLogout(s.T(), s.Context(ctx))
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
// And check the latest method and device is still used.
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method")
// Meaning the authentication is successful
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldSelectDevice() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Set default 2FA preference to enable Select Device link in frontend.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "ABCDEFGHIJ1234567890", Method: "push"}))
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}, {
Device: "1234567890ABCDEFGHIJ",
DisplayName: "Test Device 2",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}},
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
// Authenticate
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
// Switch Method where Device Selection should open automatically.
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
// Check for available Device 1.
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "device-12345ABCDEFGHIJ67890")
// Test Back button.
s.doClickButton(s.T(), s.Context(ctx), "device-selection-back")
// then select Device 2 for further use and be redirected.
s.doChangeDevice(s.T(), s.Context(ctx), "1234567890ABCDEFGHIJ")
s.verifyIsHome(s.T(), s.Context(ctx))
// Re-Login the user
s.doLogout(s.T(), s.Context(ctx))
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
// And check the latest method and device is still used.
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method")
// Meaning the authentication is successful
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldFailInitialSelectionBecauseOfUnsupportedMethod() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"auto", "sms"},
}},
}
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-not-registered")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "No compatible device found")
}
func (s *DuoPushWebDriverSuite) TestShouldSelectNewDeviceAfterSavedDeviceMethodIsNoLongerSupported() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"push", "sms"},
}, {
Device: "1234567890ABCDEFGHIJ",
DisplayName: "Test Device 2",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}},
}
// Setup unsupported Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "ABCDEFGHIJ1234567890", Method: "sms"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "device-selection")
s.doSelectDevice(s.T(), s.Context(ctx), "12345ABCDEFGHIJ67890")
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldAutoSelectNewDeviceAfterSavedDeviceIsNoLongerAvailable() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"push", "sms"},
}},
}
// Setup unsupported Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "ABCDEFGHIJ1234567890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldFailSelectionBecauseOfSelectionBypassed() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "allow",
StatusMessage: "Allowing unknown user",
}
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Deny)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.doClickButton(s.T(), s.Context(ctx), "selection-link")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Device selection was bypassed by Duo policy")
}
func (s *DuoPushWebDriverSuite) TestShouldFailSelectionBecauseOfSelectionDenied() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "deny",
StatusMessage: "We're sorry, access is not allowed.",
}
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Deny)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
err := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "selection-link").Click("left")
require.NoError(s.T(), err)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Device selection was denied by Duo policy")
}
func (s *DuoPushWebDriverSuite) TestShouldFailAuthenticationBecausePreauthDenied() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "deny",
StatusMessage: "We're sorry, access is not allowed.",
}
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "failure-icon")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "There was an issue completing sign in process")
}
func (s *DuoPushWebDriverSuite) TestShouldSucceedAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}},
}
// Setup Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldFailAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "auth",
Devices: []duo.Device{{
Device: "12345ABCDEFGHIJ67890",
DisplayName: "Test Device 1",
Capabilities: []string{"auto", "push", "sms", "mobile_otp"},
}},
}
// Setup Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Deny)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "failure-icon")
}
type DuoPushDefaultRedirectionSuite struct {
*RodSuite
}
func NewDuoPushDefaultRedirectionSuite() *DuoPushDefaultRedirectionSuite {
return &DuoPushDefaultRedirectionSuite{RodSuite: new(RodSuite)}
}
func (s *DuoPushDefaultRedirectionSuite) SetupSuite() {
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.RodSession = browser
}
func (s *DuoPushDefaultRedirectionSuite) TearDownSuite() {
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
}
}
func (s *DuoPushDefaultRedirectionSuite) SetupTest() {
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (s *DuoPushDefaultRedirectionSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *DuoPushDefaultRedirectionSuite) TestUserIsRedirectedToDefaultURL() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "allow",
StatusMessage: "Allowing unknown user",
}
// Setup Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Page)
// Clean up any Duo device already in DB.
require.NoError(s.T(), provider.DeletePreferredDuoDevice(ctx, "john"))
}
type DuoPushSuite struct {
suite.Suite
}
func NewDuoPushSuite() *DuoPushSuite {
return &DuoPushSuite{}
}
func (s *DuoPushSuite) TestDuoPushWebDriverSuite() {
suite.Run(s.T(), NewDuoPushWebDriverSuite())
}
func (s *DuoPushSuite) TestDuoPushRedirectionURLSuite() {
suite.Run(s.T(), NewDuoPushDefaultRedirectionSuite())
}
func (s *DuoPushSuite) TestAvailableMethodsScenario() {
suite.Run(s.T(), NewAvailableMethodsScenario([]string{
"TIME-BASED ONE-TIME PASSWORD",
"PUSH NOTIFICATION",
}))
}
func (s *DuoPushSuite) TestUserPreferencesScenario() {
var PreAuthAPIResponse = duo.PreAuthResponse{
Result: "allow",
StatusMessage: "Allowing unknown user",
}
ctx := context.Background()
// Setup Duo device in DB.
provider := storage.NewSQLiteProvider(&storageLocalTmpConfig)
require.NoError(s.T(), provider.SavePreferredDuoDevice(ctx, models.DuoDevice{Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}))
ConfigureDuoPreAuth(s.T(), PreAuthAPIResponse)
ConfigureDuo(s.T(), Allow)
suite.Run(s.T(), NewUserPreferencesScenario())
// Clean up any Duo device already in DB.
require.NoError(s.T(), provider.DeletePreferredDuoDevice(ctx, "john"))
}
func TestDuoPushSuite(t *testing.T) {
if testing.Short() {
t.Skip("skipping suite test in short mode")
}
suite.Run(t, NewDuoPushSuite())
}