diff --git a/server/src/lib/routes/verify/CheckAuthorizations.ts b/server/src/lib/routes/verify/CheckAuthorizations.ts index e9b369a3..5f667f83 100644 --- a/server/src/lib/routes/verify/CheckAuthorizations.ts +++ b/server/src/lib/routes/verify/CheckAuthorizations.ts @@ -26,13 +26,13 @@ export default function ( authorizer: IAuthorizer, domain: string, resource: string, user: string, groups: string[], ip: string, - authenticationLevel: AuthenticationLevel): AuthorizationLevel { + authenticationLevel: AuthenticationLevel): void { const authorizationLevel = authorizer .authorization({domain, resource}, {user, groups}, ip); if (authorizationLevel == AuthorizationLevel.BYPASS) { - return authorizationLevel; + return; } else if (user && authorizationLevel == AuthorizationLevel.DENY) { throw new Exceptions.NotAuthorizedError( @@ -42,5 +42,4 @@ export default function ( throw new Exceptions.NotAuthenticatedError(Util.format( "User '%s' is not sufficiently authorized to access %s%s.", (user) ? user : "unknown", domain, resource)); } - return authorizationLevel; } \ No newline at end of file diff --git a/server/src/lib/routes/verify/CheckInactivity.spec.ts b/server/src/lib/routes/verify/CheckInactivity.spec.ts index 29656991..85c2b2c0 100644 --- a/server/src/lib/routes/verify/CheckInactivity.spec.ts +++ b/server/src/lib/routes/verify/CheckInactivity.spec.ts @@ -6,6 +6,7 @@ import CheckInactivity from "./CheckInactivity"; import { AuthenticationSession } from "../../../../types/AuthenticationSession"; import { Configuration } from "../../configuration/schema/Configuration"; import { RequestLoggerStub } from "../../logging/RequestLoggerStub.spec"; +import { Level } from "../../authentication/Level"; describe('routes/verify/VerifyInactivity', function() { @@ -16,7 +17,9 @@ describe('routes/verify/VerifyInactivity', function() { beforeEach(function() { req = ExpressMock.RequestMock(); - authSession = {} as any; + authSession = { + authentication_level: Level.TWO_FACTOR, + } as any; configuration = { session: { domain: 'example.com', @@ -32,6 +35,11 @@ describe('routes/verify/VerifyInactivity', function() { logger = new RequestLoggerStub(); }); + it('should not throw if user is not authenticated', function() { + authSession.authentication_level = Level.NOT_AUTHENTICATED; + CheckInactivity(req, authSession, configuration, logger); + }); + it('should not throw if inactivity timeout is disabled', function() { delete configuration.session.inactivity; CheckInactivity(req, authSession, configuration, logger); diff --git a/server/src/lib/routes/verify/CheckInactivity.ts b/server/src/lib/routes/verify/CheckInactivity.ts index db61a6fb..4e0b2444 100644 --- a/server/src/lib/routes/verify/CheckInactivity.ts +++ b/server/src/lib/routes/verify/CheckInactivity.ts @@ -3,11 +3,17 @@ import { AuthenticationSession } from "AuthenticationSession"; import { Configuration } from "../../configuration/schema/Configuration"; import { IRequestLogger } from "../../logging/IRequestLogger"; import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler"; +import { Level } from "../../authentication/Level"; export default function(req: Express.Request, authSession: AuthenticationSession, configuration: Configuration, logger: IRequestLogger): void { + // If the user is not authenticated, we don't check inactivity. + if (authSession.authentication_level == Level.NOT_AUTHENTICATED) { + return; + } + // If inactivity is not specified, then inactivity timeout does not apply if (!configuration.session.inactivity || authSession.keep_me_logged_in) { return; diff --git a/server/src/lib/routes/verify/GetBasicAuth.ts b/server/src/lib/routes/verify/GetBasicAuth.ts index b3f548f1..d94868e2 100644 --- a/server/src/lib/routes/verify/GetBasicAuth.ts +++ b/server/src/lib/routes/verify/GetBasicAuth.ts @@ -40,10 +40,8 @@ export default async function(req: Express.Request, res: Express.Response, const uri = GetHeader(req, HEADER_X_ORIGINAL_URL); const urlDecomposition = URLDecomposer.fromUrl(uri); - const authorizationLevel = CheckAuthorizations(vars.authorizer, urlDecomposition.domain, urlDecomposition.path, - username, groupsAndEmails.groups, req.ip, Level.ONE_FACTOR); - if (authorizationLevel > AuthorizationLevel.BYPASS) { - setUserAndGroupsHeaders(res, username, groupsAndEmails.groups); - } + CheckAuthorizations(vars.authorizer, urlDecomposition.domain, urlDecomposition.path, + username, groupsAndEmails.groups, req.ip, Level.ONE_FACTOR); + setUserAndGroupsHeaders(res, username, groupsAndEmails.groups); } \ No newline at end of file diff --git a/server/src/lib/routes/verify/GetSessionCookie.ts b/server/src/lib/routes/verify/GetSessionCookie.ts index cb6b7787..d7ebb0ef 100644 --- a/server/src/lib/routes/verify/GetSessionCookie.ts +++ b/server/src/lib/routes/verify/GetSessionCookie.ts @@ -7,7 +7,6 @@ import GetHeader from "../../utils/GetHeader"; import { HEADER_X_ORIGINAL_URL, } from "../../../../../shared/constants"; -import { Level as AuthorizationLevel } from "../../authorization/Level"; import setUserAndGroupsHeaders from "./SetUserAndGroupsHeaders"; import CheckAuthorizations from "./CheckAuthorizations"; import CheckInactivity from "./CheckInactivity"; @@ -33,9 +32,8 @@ export default async function (req: Express.Request, res: Express.Response, vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s, ip=%s", d.domain, d.path, (username) ? username : "unknown", (groups instanceof Array && groups.length > 0) ? groups.join(",") : "unknown", req.ip); - const authorizationLevel = CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups, req.ip, - authSession.authentication_level); + CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups, req.ip, authSession.authentication_level); CheckInactivity(req, authSession, vars.config, vars.logger); setUserAndGroupsHeaders(res, username, groups); } \ No newline at end of file diff --git a/test/helpers/assertions/VerifyForwardedHeaderDoesNotExist.ts b/test/helpers/assertions/VerifyForwardedHeaderDoesNotExist.ts new file mode 100644 index 00000000..179e758a --- /dev/null +++ b/test/helpers/assertions/VerifyForwardedHeaderDoesNotExist.ts @@ -0,0 +1,13 @@ +import SeleniumWebDriver, { WebDriver } from "selenium-webdriver"; +import Util from "util"; + +export default async function(driver: WebDriver, header: string, timeout: number = 5000) { + const el = await driver.wait(SeleniumWebDriver.until.elementLocated( + SeleniumWebDriver.By.tagName("body")), timeout); + const text = await el.getText(); + + const expectedLine = Util.format("\"%s\": ", header); + if (text.indexOf(expectedLine) >= 0) { + throw new Error("Header found."); + } +} \ No newline at end of file diff --git a/test/suites/basic-bypass-no-redirect/environment.ts b/test/suites/basic-bypass-no-redirect/environment.ts index d5c67445..e37101e4 100644 --- a/test/suites/basic-bypass-no-redirect/environment.ts +++ b/test/suites/basic-bypass-no-redirect/environment.ts @@ -11,6 +11,7 @@ const dockerEnv = new DockerEnvironment([ 'docker-compose.yml', 'example/compose/nginx/backend/docker-compose.yml', 'example/compose/nginx/portal/docker-compose.yml', + 'example/compose/httpbin/docker-compose.yml', 'example/compose/smtp/docker-compose.yml', 'example/compose/duo-api/docker-compose.yml', ]) diff --git a/test/suites/basic-bypass-no-redirect/scenarii/CustomHeadersForwarded.ts b/test/suites/basic-bypass-no-redirect/scenarii/CustomHeadersForwarded.ts new file mode 100644 index 00000000..1d2b923e --- /dev/null +++ b/test/suites/basic-bypass-no-redirect/scenarii/CustomHeadersForwarded.ts @@ -0,0 +1,52 @@ +import Logout from "../../../helpers/Logout"; +import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver"; +import VerifyForwardedHeaderIs from "../../../helpers/assertions/VerifyForwardedHeaderIs"; +import LoginOneFactor from "../../../helpers/behaviors/LoginOneFactor"; +import VisitPageAndWaitUrlIs from "../../../helpers/behaviors/VisitPageAndWaitUrlIs"; +import VerifyButtonDoesNotExist from "../../../helpers/assertions/VerifyButtonDoesNotExist"; + +export default function() { + describe("Custom-Forwarded-User and Custom-Forwarded-Groups are correctly forwarded when available", function() { + this.timeout(100000); + + describe("Headers are not forwarded for anonymous user", function() { + before(async function() { + this.driver = await StartDriver(); + await VisitPageAndWaitUrlIs(this.driver, "https://public.example.com:8080/headers"); + }); + + after(async function() { + await Logout(this.driver); + await StopDriver(this.driver); + }); + + it("should check header 'Custom-Forwarded-User' does not exist", async function() { + await VerifyButtonDoesNotExist(this.driver, 'Custom-Forwarded-User'); + }); + + it("should check header 'Custom-Forwarded-Groups' does not exist", async function() { + await VerifyButtonDoesNotExist(this.driver, 'Custom-Forwarded-Groups'); + }); + }); + + describe("Headers are forwarded for authenticated user", function() { + before(async function() { + this.driver = await StartDriver(); + await LoginOneFactor(this.driver, "john", "password", "https://public.example.com:8080/headers"); + }); + + after(async function() { + await Logout(this.driver); + await StopDriver(this.driver); + }); + + it("should see header 'Custom-Forwarded-User' set to 'john'", async function() { + await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-User', 'john'); + }); + + it("should see header 'Custom-Forwarded-Groups' set to 'admins,dev'", async function() { + await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-Groups', 'admins,dev'); + }); + }); + }); +} \ No newline at end of file diff --git a/test/suites/basic-bypass-no-redirect/test.ts b/test/suites/basic-bypass-no-redirect/test.ts index aeb4ac12..09826914 100644 --- a/test/suites/basic-bypass-no-redirect/test.ts +++ b/test/suites/basic-bypass-no-redirect/test.ts @@ -2,6 +2,9 @@ import AutheliaSuite from "../../helpers/context/AutheliaSuite"; import { exec } from '../../helpers/utils/exec'; import BypassPolicy from "./scenarii/BypassPolicy"; import NoDefaultRedirectionUrl from "./scenarii/NoDefaultRedirectionUrl"; +import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded"; + +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0 as any; AutheliaSuite(__dirname, function() { this.timeout(10000); @@ -12,4 +15,5 @@ AutheliaSuite(__dirname, function() { describe('Bypass policy', BypassPolicy); describe("No default redirection", NoDefaultRedirectionUrl); + describe("Custom headers forwarded on bypass", CustomHeadersForwarded); }); \ No newline at end of file