From 81207b49ad41ad72b899c9042bcca7716db43ac5 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Wed, 27 Mar 2019 21:57:16 +0100 Subject: [PATCH] Fix failing second factor when no default redirection url set. When no default redirection url was set, Duo push second factor was shown as failing even if authentication was successful. --- client/src/behaviors/TriggerDuoPushAuth.ts | 7 ++-- .../SecondFactorDuoPush.ts | 12 +++---- client/src/services/AutheliaService.ts | 20 ++++++++--- client/src/services/RedirectResponse.ts | 6 ++++ .../helpers/context/AutheliaServerFromDist.ts | 3 +- .../README.md | 0 .../config.yml | 8 +++++ .../environment.ts | 4 +++ .../scenarii/BypassPolicy.ts | 0 .../scenarii/NoDefaultRedirectionUrl.ts | 33 +++++++++++++++++++ .../test.ts | 2 ++ .../users_database.yml | 0 test/suites/basic/config.yml | 12 ------- test/suites/duo-push/config.yml | 14 -------- 14 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 client/src/services/RedirectResponse.ts rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/README.md (100%) rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/config.yml (76%) rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/environment.ts (88%) rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/scenarii/BypassPolicy.ts (100%) create mode 100644 test/suites/basic-bypass-no-redirect/scenarii/NoDefaultRedirectionUrl.ts rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/test.ts (74%) rename test/suites/{acl-full-bypass => basic-bypass-no-redirect}/users_database.yml (100%) diff --git a/client/src/behaviors/TriggerDuoPushAuth.ts b/client/src/behaviors/TriggerDuoPushAuth.ts index 61cbe67f..d64024b0 100644 --- a/client/src/behaviors/TriggerDuoPushAuth.ts +++ b/client/src/behaviors/TriggerDuoPushAuth.ts @@ -5,14 +5,11 @@ import { triggerDuoPushAuth, triggerDuoPushAuthSuccess, triggerDuoPushAuthFailur export default async function(dispatch: Dispatch, redirectionUrl: string | null) { dispatch(triggerDuoPushAuth()); try { - const res = await AutheliaService.triggerDuoPush(redirectionUrl); - const body = await res.json(); - if ('error' in body) { - throw new Error(body['error']); - } + const body = await AutheliaService.triggerDuoPush(redirectionUrl); dispatch(triggerDuoPushAuthSuccess()); return body; } catch (err) { + console.error(err); dispatch(triggerDuoPushAuthFailure(err.message)) } } \ No newline at end of file diff --git a/client/src/containers/components/SecondFactorDuoPush/SecondFactorDuoPush.ts b/client/src/containers/components/SecondFactorDuoPush/SecondFactorDuoPush.ts index 2f23d23e..c6cfb086 100644 --- a/client/src/containers/components/SecondFactorDuoPush/SecondFactorDuoPush.ts +++ b/client/src/containers/components/SecondFactorDuoPush/SecondFactorDuoPush.ts @@ -4,6 +4,7 @@ import { Dispatch } from 'redux'; import SecondFactorDuoPush, { StateProps, OwnProps, DispatchProps } from '../../../components/SecondFactorDuoPush/SecondFactorDuoPush'; import FetchStateBehavior from '../../../behaviors/FetchStateBehavior'; import TriggerDuoPushAuth from '../../../behaviors/TriggerDuoPushAuth'; +import RedirectionResponse from '../../../services/RedirectResponse'; const mapStateToProps = (state: RootState): StateProps => ({ @@ -12,16 +13,16 @@ const mapStateToProps = (state: RootState): StateProps => ({ }); async function redirectIfPossible(body: any) { - if ('redirect' in body) { + if (body && 'redirect' in body) { window.location.href = body['redirect']; return true; } return false; } -async function handleSuccess(dispatch: Dispatch, res: Response, duration?: number) { +async function handleSuccess(dispatch: Dispatch, body: RedirectionResponse | undefined, duration?: number) { async function handle() { - const redirected = await redirectIfPossible(res); + const redirected = await redirectIfPossible(body); if (!redirected) { await FetchStateBehavior(dispatch); } @@ -35,9 +36,8 @@ async function handleSuccess(dispatch: Dispatch, res: Response, duration?: numbe } async function triggerDuoPushAuth(dispatch: Dispatch, redirectionUrl: string | null) { - const res = await TriggerDuoPushAuth(dispatch, redirectionUrl); - if (!res) return; - await handleSuccess(dispatch, res, 2000); + const body = await TriggerDuoPushAuth(dispatch, redirectionUrl); + await handleSuccess(dispatch, body, 1000); } const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => { diff --git a/client/src/services/AutheliaService.ts b/client/src/services/AutheliaService.ts index 2e4ee0fd..9d311cea 100644 --- a/client/src/services/AutheliaService.ts +++ b/client/src/services/AutheliaService.ts @@ -1,6 +1,7 @@ import RemoteState from "../views/AuthenticationView/RemoteState"; import u2fApi, { SignRequest } from "u2f-api"; import Method2FA from "../types/Method2FA"; +import RedirectResponse from "./RedirectResponse"; class AutheliaService { static async fetchSafe(url: string, options?: RequestInit): Promise { @@ -113,19 +114,28 @@ class AutheliaService { }) } - static async triggerDuoPush(redirectionUrl: string | null): Promise { - - const headers: Record = { + static async triggerDuoPush(redirectionUrl: string | null): Promise { + const headers: Record = { 'Accept': 'application/json', 'Content-Type': 'application/json', } if (redirectionUrl) { headers['X-Target-Url'] = redirectionUrl; } - return this.fetchSafe('/api/duo-push', { + const res = await this.fetchSafe('/api/duo-push', { method: 'POST', headers: headers, - }) + }); + + if (res.status === 204) { + return; + } + + const body = await res.json(); + if ('error' in body) { + throw new Error(body['error']); + } + return body; } static async initiatePasswordResetIdentityValidation(username: string) { diff --git a/client/src/services/RedirectResponse.ts b/client/src/services/RedirectResponse.ts new file mode 100644 index 00000000..b3b16c46 --- /dev/null +++ b/client/src/services/RedirectResponse.ts @@ -0,0 +1,6 @@ + + +export default interface RedirectResponse { + redirect?: string; + error?: string; +} \ No newline at end of file diff --git a/test/helpers/context/AutheliaServerFromDist.ts b/test/helpers/context/AutheliaServerFromDist.ts index 3a347b37..6e0954d0 100644 --- a/test/helpers/context/AutheliaServerFromDist.ts +++ b/test/helpers/context/AutheliaServerFromDist.ts @@ -15,7 +15,8 @@ class AutheliaServerFromDist implements AutheliaServerInterface { async start() { this.serverProcess = ChildProcess.spawn('./scripts/authelia-scripts serve ' + this.configPath, { - shell: true + shell: true, + env: process.env, } as any); if (this.logInFile) { var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'}); diff --git a/test/suites/acl-full-bypass/README.md b/test/suites/basic-bypass-no-redirect/README.md similarity index 100% rename from test/suites/acl-full-bypass/README.md rename to test/suites/basic-bypass-no-redirect/README.md diff --git a/test/suites/acl-full-bypass/config.yml b/test/suites/basic-bypass-no-redirect/config.yml similarity index 76% rename from test/suites/acl-full-bypass/config.yml rename to test/suites/basic-bypass-no-redirect/config.yml index 5adfe2f8..bc4ace58 100644 --- a/test/suites/acl-full-bypass/config.yml +++ b/test/suites/basic-bypass-no-redirect/config.yml @@ -20,11 +20,19 @@ storage: local: path: /tmp/authelia/db +# The Duo Push Notification API configuration +duo_api: + hostname: duo.example.com + integration_key: ABCDEFGHIJKL + secret_key: abcdefghijklmnopqrstuvwxyz123456789 + access_control: default_policy: bypass rules: - domain: 'public.example.com' policy: bypass + - domain: 'secure.example.com' + policy: two_factor notifier: smtp: diff --git a/test/suites/acl-full-bypass/environment.ts b/test/suites/basic-bypass-no-redirect/environment.ts similarity index 88% rename from test/suites/acl-full-bypass/environment.ts rename to test/suites/basic-bypass-no-redirect/environment.ts index 8fa6b2f2..d5c67445 100644 --- a/test/suites/acl-full-bypass/environment.ts +++ b/test/suites/basic-bypass-no-redirect/environment.ts @@ -3,12 +3,16 @@ import { exec } from "../../helpers/utils/exec"; import AutheliaServer from "../../helpers/context/AutheliaServer"; import DockerEnvironment from "../../helpers/context/DockerEnvironment"; +// required to query duo-api over https +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0 as any; + const autheliaServer = new AutheliaServer(__dirname + '/config.yml'); const dockerEnv = new DockerEnvironment([ 'docker-compose.yml', 'example/compose/nginx/backend/docker-compose.yml', 'example/compose/nginx/portal/docker-compose.yml', 'example/compose/smtp/docker-compose.yml', + 'example/compose/duo-api/docker-compose.yml', ]) async function setup() { diff --git a/test/suites/acl-full-bypass/scenarii/BypassPolicy.ts b/test/suites/basic-bypass-no-redirect/scenarii/BypassPolicy.ts similarity index 100% rename from test/suites/acl-full-bypass/scenarii/BypassPolicy.ts rename to test/suites/basic-bypass-no-redirect/scenarii/BypassPolicy.ts diff --git a/test/suites/basic-bypass-no-redirect/scenarii/NoDefaultRedirectionUrl.ts b/test/suites/basic-bypass-no-redirect/scenarii/NoDefaultRedirectionUrl.ts new file mode 100644 index 00000000..3af66283 --- /dev/null +++ b/test/suites/basic-bypass-no-redirect/scenarii/NoDefaultRedirectionUrl.ts @@ -0,0 +1,33 @@ +import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver"; +import LoginAs from "../../../helpers/LoginAs"; +import VerifyIsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage"; +import ClickOnLink from "../../../helpers/ClickOnLink"; +import VerifyIsUseAnotherMethodView from "../../../helpers/assertions/VerifyIsUseAnotherMethodView"; +import ClickOnButton from "../../../helpers/behaviors/ClickOnButton"; +import Request from 'request-promise'; +import VerifyIsAlreadyAuthenticatedStage from "../../../helpers/assertions/VerifyIsAlreadyAuthenticatedStage"; + +export default function() { + before(async function() { + this.driver = await StartDriver(); + + // Configure the fake API to return allowing response. + await Request('https://duo.example.com/allow', {method: 'POST'}); + }); + + after(async function () { + await StopDriver(this.driver); + }); + + it('should send user to already authenticated page', async function() { + await LoginAs(this.driver, "john", "password"); + await VerifyIsSecondFactorStage(this.driver); + + await ClickOnLink(this.driver, 'Use another method'); + await VerifyIsUseAnotherMethodView(this.driver); + await ClickOnButton(this.driver, 'Duo Push Notification'); + await VerifyIsAlreadyAuthenticatedStage(this.driver, 10000); + + await ClickOnButton(this.driver, "Logout"); + }); +} \ No newline at end of file diff --git a/test/suites/acl-full-bypass/test.ts b/test/suites/basic-bypass-no-redirect/test.ts similarity index 74% rename from test/suites/acl-full-bypass/test.ts rename to test/suites/basic-bypass-no-redirect/test.ts index 167a323b..aeb4ac12 100644 --- a/test/suites/acl-full-bypass/test.ts +++ b/test/suites/basic-bypass-no-redirect/test.ts @@ -1,6 +1,7 @@ import AutheliaSuite from "../../helpers/context/AutheliaSuite"; import { exec } from '../../helpers/utils/exec'; import BypassPolicy from "./scenarii/BypassPolicy"; +import NoDefaultRedirectionUrl from "./scenarii/NoDefaultRedirectionUrl"; AutheliaSuite(__dirname, function() { this.timeout(10000); @@ -10,4 +11,5 @@ AutheliaSuite(__dirname, function() { }); describe('Bypass policy', BypassPolicy); + describe("No default redirection", NoDefaultRedirectionUrl); }); \ No newline at end of file diff --git a/test/suites/acl-full-bypass/users_database.yml b/test/suites/basic-bypass-no-redirect/users_database.yml similarity index 100% rename from test/suites/acl-full-bypass/users_database.yml rename to test/suites/basic-bypass-no-redirect/users_database.yml diff --git a/test/suites/basic/config.yml b/test/suites/basic/config.yml index 534f2568..122f100b 100644 --- a/test/suites/basic/config.yml +++ b/test/suites/basic/config.yml @@ -87,18 +87,6 @@ regulation: ban_time: 900 notifier: - # For testing purpose, notifications can be sent in a file - # filesystem: - # filename: /tmp/authelia/notification.txt - - # Use your email account to send the notifications. You can use an app password. - # List of valid services can be found here: https://nodemailer.com/smtp/well-known/ - ## email: - ## username: user@example.com - ## password: yourpassword - ## sender: admin@example.com - ## service: gmail - # Use a SMTP server for sending notifications smtp: username: test diff --git a/test/suites/duo-push/config.yml b/test/suites/duo-push/config.yml index c3320d65..f02a0fe5 100644 --- a/test/suites/duo-push/config.yml +++ b/test/suites/duo-push/config.yml @@ -6,8 +6,6 @@ port: 9091 logs_level: debug -default_redirection_url: https://home.example.com:8080/ - authentication_backend: file: path: ./test/suites/basic/users_database.test.yml @@ -93,18 +91,6 @@ regulation: ban_time: 900 notifier: - # For testing purpose, notifications can be sent in a file - # filesystem: - # filename: /tmp/authelia/notification.txt - - # Use your email account to send the notifications. You can use an app password. - # List of valid services can be found here: https://nodemailer.com/smtp/well-known/ - ## email: - ## username: user@example.com - ## password: yourpassword - ## sender: admin@example.com - ## service: gmail - # Use a SMTP server for sending notifications smtp: username: test