Add redirection URL as a query parameter during authentication

Before this fix, the redirection URL was stored in the user session,
but this has a big drawback since user could open several pages in
browser and thus override the redirection URL leading the user to
be incorrectly redirected.
pull/91/head
Clement Michaud 2017-09-22 17:53:18 +02:00
parent 83f5302615
commit 7128970a53
13 changed files with 72 additions and 23 deletions

View File

@ -60,12 +60,6 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
error_page 401 = @error401;
error_page 403 = https://auth.test.local:8080/error/403;
location @error401 {
return 302 https://auth.test.local:8080;
}
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
@ -78,12 +72,20 @@ http {
location = /secret.html { location = /secret.html {
auth_request /auth_verify; auth_request /auth_verify;
auth_request_set $redirect $upstream_http_redirect;
proxy_set_header Redirect $redirect;
auth_request_set $user $upstream_http_x_remote_user; auth_request_set $user $upstream_http_x_remote_user;
proxy_set_header X-Forwarded-User $user; proxy_set_header X-Forwarded-User $user;
auth_request_set $groups $upstream_http_remote_groups; auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups; proxy_set_header Remote-Groups $groups;
auth_request_set $expiry $upstream_http_remote_expiry; auth_request_set $expiry $upstream_http_remote_expiry;
proxy_set_header Remote-Expiry $expiry; proxy_set_header Remote-Expiry $expiry;
error_page 401 =302 https://auth.test.local:8080?redirect=$redirect;
error_page 403 = https://auth.test.local:8080/error/403;
} }
} }
} }

View File

@ -9,7 +9,7 @@ start_services() {
} }
shut_services() { shut_services() {
$DC_SCRIPT down $DC_SCRIPT down --remove-orphans
} }
expect_services_count() { expect_services_count() {

View File

@ -0,0 +1,12 @@
export class QueryParametersRetriever {
static get(name: string, url?: string): string {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return undefined;
if (!results[2]) return "";
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
}

View File

@ -2,6 +2,7 @@ import FirstFactorValidator = require("./FirstFactorValidator");
import JSLogger = require("js-logger"); import JSLogger = require("js-logger");
import UISelectors = require("./UISelectors"); import UISelectors = require("./UISelectors");
import { Notifier } from "../Notifier"; import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Endpoints = require("../../../server/endpoints"); import Endpoints = require("../../../server/endpoints");
@ -22,8 +23,12 @@ export default function (window: Window, $: JQueryStatic,
function onFirstFactorSuccess() { function onFirstFactorSuccess() {
jslogger.debug("First factor validated."); jslogger.debug("First factor validated.");
// Redirect to second factor const redirectUrl = QueryParametersRetriever.get("redirect");
window.location.href = Endpoints.SECOND_FACTOR_GET; if (redirectUrl)
window.location.href = Endpoints.SECOND_FACTOR_GET + "?redirect="
+ encodeURIComponent(redirectUrl);
else
window.location.href = Endpoints.SECOND_FACTOR_GET;
} }
function onFirstFactorFailure(err: Error) { function onFirstFactorFailure(err: Error) {

View File

@ -7,6 +7,7 @@ import U2FValidator = require("./U2FValidator");
import Endpoints = require("../../../server/endpoints"); import Endpoints = require("../../../server/endpoints");
import Constants = require("./constants"); import Constants = require("./constants");
import { Notifier } from "../Notifier"; import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever";
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) { export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
@ -14,7 +15,11 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
const notifierU2f = new Notifier(".notification-u2f", $); const notifierU2f = new Notifier(".notification-u2f", $);
function onAuthenticationSuccess(data: any) { function onAuthenticationSuccess(data: any) {
window.location.href = data.redirection_url; const redirectUrl = QueryParametersRetriever.get("redirect");
if (redirectUrl)
window.location.href = redirectUrl;
else
window.location.href = Endpoints.FIRST_FACTOR_GET;
} }
function onSecondFactorTotpSuccess(data: any) { function onSecondFactorTotpSuccess(data: any) {

View File

@ -59,8 +59,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
logger.debug("1st factor: Mark successful authentication to regulator."); logger.debug("1st factor: Mark successful authentication to regulator.");
regulator.mark(username, true); regulator.mark(username, true);
logger.debug("1st factor: Redirect to %s", Endpoint.SECOND_FACTOR_GET); res.status(204);
res.redirect(Endpoint.SECOND_FACTOR_GET); res.send();
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger)) .catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))

View File

@ -8,7 +8,7 @@ import AuthenticationSession = require("../../AuthenticationSession");
export default function (req: express.Request, res: express.Response) { export default function (req: express.Request, res: express.Response) {
const authSession = AuthenticationSession.get(req); const authSession = AuthenticationSession.get(req);
const redirectUrl = authSession.redirect || Endpoints.FIRST_FACTOR_GET; const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET;
res.json({ res.json({
redirection_url: redirectUrl redirection_url: redirectUrl
}); });

View File

@ -17,7 +17,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
const authSession = AuthenticationSession.get(req); const authSession = AuthenticationSession.get(req);
logger.debug("Verify: headers are %s", JSON.stringify(req.headers)); logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
authSession.redirect = "https://" + req.headers["host"] + req.headers["x-original-uri"]; res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
return AuthenticationValidator.validate(req) return AuthenticationValidator.validate(req)
.then(function () { .then(function () {

View File

@ -18,14 +18,14 @@ Feature: User validate first factor
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0" And I register a TOTP secret called "Sec0"
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/" When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle And I use "Sec0" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"
Then I'm redirected to "https://secret.test.local:8080/secret.html" Then I'm redirected to "https://secret.test.local:8080/secret.html"
Scenario: User fails TOTP second factor Scenario: User fails TOTP second factor
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/" When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "BADTOKEN" as TOTP token And I use "BADTOKEN" as TOTP token
And I click on "TOTP" And I click on "TOTP"

View File

@ -21,3 +21,27 @@ Feature: User is correctly redirected
When I register TOTP and login with user "harry" and password "password" When I register TOTP and login with user "harry" and password "password"
And I visit "https://secret.test.local:8080/secret.html" And I visit "https://secret.test.local:8080/secret.html"
Then I get an error 403 Then I get an error 403
Scenario: Redirection URL is propagated from restricted page to first factor
When I visit "https://public.test.local:8080/secret.html"
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
Scenario: Redirection URL is propagated from first factor to second factor
Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
When I visit "https://public.test.local:8080/secret.html"
And I login with user "john" and password "password"
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
Scenario: Redirection URL is used to send user from second factor to target page
Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
When I visit "https://public.test.local:8080/secret.html"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
And I click on "TOTP"
Then I'm redirected to "https://public.test.local:8080/secret.html"

View File

@ -12,7 +12,7 @@ Feature: Authelia keeps user sessions despite the application restart
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0" And I register a TOTP secret called "Sec0"
When the application restarts When the application restarts
And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/" And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle And I use "Sec0" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"

View File

@ -99,7 +99,7 @@ function CustomWorld() {
}; };
this.useTotpToken = function (totpSecret: string) { this.useTotpToken = function (totpSecret: string) {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 5000) return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("token")), 5000)
.then(function () { .then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("token")) return that.driver.findElement(seleniumWebdriver.By.id("token"))
.sendKeys(totpSecret); .sendKeys(totpSecret);

View File

@ -71,7 +71,7 @@ describe("test the first factor validation route", function () {
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
}); });
it("should redirect client to second factor page", function () { it("should reply with 204 if success", function () {
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
.returns(BluebirdPromise.resolve({ .returns(BluebirdPromise.resolve({
emails: emails, emails: emails,
@ -81,7 +81,8 @@ describe("test the first factor validation route", function () {
return FirstFactorPost.default(req as any, res as any) return FirstFactorPost.default(req as any, res as any)
.then(function () { .then(function () {
assert.equal("username", authSession.userid); assert.equal("username", authSession.userid);
assert.equal(Endpoints.SECOND_FACTOR_GET, res.redirect.getCall(0).args[0]); assert(res.send.calledOnce);
assert(res.status.calledWith(204));
}); });
}); });