From 234d98aee7364defa8db9e634627c46075cc8913 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 12 Nov 2019 13:49:37 +0100 Subject: [PATCH 1/4] Don't use anonymous access after access token expires --- .../main/java/sonia/scm/web/filter/AuthenticationFilter.java | 2 +- scm-ui/ui-components/src/apiclient.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index 87209ce409..3b64e6b5ac 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -127,7 +127,7 @@ public class AuthenticationFilter extends HttpFilter logger.trace("user is already authenticated"); processChain(request, response, chain, subject); } - else if (isAnonymousAccessEnabled()) + else if (isAnonymousAccessEnabled() && !HttpUtil.isWUIRequest(request)) { logger.trace("anonymous access granted"); subject.login(new AnonymousToken()); diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 396200f1c1..8af093fac7 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -7,7 +7,8 @@ const applyFetchOptions: (p: RequestInit) => RequestInit = o => { o.headers = { Cache: "no-cache", // identify the request as ajax request - "X-Requested-With": "XMLHttpRequest" + "X-Requested-With": "XMLHttpRequest", + "X-SCM-Client": "WUI" }; return o; }; From 2c85a137a51e6330bb2a68686091d29a16fa808a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 18 Nov 2019 11:45:48 +0100 Subject: [PATCH 2/4] implement client side xsrf protection --- scm-ui/ui-components/src/apiclient.test.ts | 21 ++++++++++++- scm-ui/ui-components/src/apiclient.ts | 36 ++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-components/src/apiclient.test.ts b/scm-ui/ui-components/src/apiclient.test.ts index 93f535728f..de9cf77dc3 100644 --- a/scm-ui/ui-components/src/apiclient.test.ts +++ b/scm-ui/ui-components/src/apiclient.test.ts @@ -1,4 +1,4 @@ -import { apiClient, createUrl } from "./apiclient"; +import { apiClient, createUrl, extractXsrfToken, extractXsrfTokenFromCookie } from "./apiclient"; import fetchMock from "fetch-mock"; import { BackendError } from "./errors"; @@ -70,3 +70,22 @@ describe("error handling tests", () => { }); }); }); + +describe("extract xsrf token", () => { + it("should return undefined if no cookie exists", () => { + const token = extractXsrfTokenFromCookie(undefined); + expect(token).toBeUndefined(); + }); + + it("should return undefined without X-Bearer-Token exists", () => { + const token = extractXsrfTokenFromCookie("a=b; c=d; e=f"); + expect(token).toBeUndefined(); + }); + + it("should return xsrf token", () => { + const cookie = + "a=b; X-Bearer-Token=eyJhbGciOiJIUzI1NiJ9.eyJ4c3JmIjoiYjE0NDRmNWEtOWI5Mi00ZDA0LWFkMzMtMTAxYjY3MWQ1YTc0Iiwic3ViIjoic2NtYWRtaW4iLCJqdGkiOiI2RFJpQVphNWwxIiwiaWF0IjoxNTc0MDcyNDQ4LCJleHAiOjE1NzQwNzYwNDgsInNjbS1tYW5hZ2VyLnJlZnJlc2hFeHBpcmF0aW9uIjoxNTc0MTE1NjQ4OTU5LCJzY20tbWFuYWdlci5wYXJlbnRUb2tlbklkIjoiNkRSaUFaYTVsMSJ9.VUJtKeWUn3xtHCEbG51r7ceXZ8CF3cmN8J-eb9EDY_U; c=d"; + const token = extractXsrfTokenFromCookie(cookie); + expect(token).toBe("b1444f5a-9b92-4d04-ad33-101b671d5a74"); + }); +}); diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 8af093fac7..560b7c5722 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -2,14 +2,46 @@ import { contextPath } from "./urls"; import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError } from "./errors"; import { BackendErrorContent } from "./errors"; +const extractXsrfTokenFromJwt = (jwt: string) => { + const parts = jwt.split("."); + if (parts.length === 3) { + return JSON.parse(atob(parts[1])).xsrf; + } +}; + +// @VisibleForTesting +export const extractXsrfTokenFromCookie = (cookieString?: string) => { + if (cookieString) { + const cookies = cookieString.split(";"); + for (const c of cookies) { + const parts = c.trim().split("="); + if (parts[0] === "X-Bearer-Token") { + return extractXsrfTokenFromJwt(parts[1]); + } + } + } +}; + +const extractXsrfToken = () => { + return extractXsrfTokenFromCookie(document.cookie); +}; + const applyFetchOptions: (p: RequestInit) => RequestInit = o => { - o.credentials = "same-origin"; - o.headers = { + const headers: { [key: string]: string } = { Cache: "no-cache", // identify the request as ajax request "X-Requested-With": "XMLHttpRequest", + // identify the web interface "X-SCM-Client": "WUI" }; + + const xsrf = extractXsrfToken(); + if (xsrf) { + headers["X-XSRF-Token"] = xsrf; + } + + o.credentials = "same-origin"; + o.headers = headers; return o; }; From fdfb033f6a2cd8984022a81bf71eda60b3f4195d Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 18 Nov 2019 13:25:08 +0100 Subject: [PATCH 3/4] Fix import --- scm-ui/ui-components/src/apiclient.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/ui-components/src/apiclient.test.ts b/scm-ui/ui-components/src/apiclient.test.ts index de9cf77dc3..871d8089b7 100644 --- a/scm-ui/ui-components/src/apiclient.test.ts +++ b/scm-ui/ui-components/src/apiclient.test.ts @@ -1,4 +1,4 @@ -import { apiClient, createUrl, extractXsrfToken, extractXsrfTokenFromCookie } from "./apiclient"; +import { apiClient, createUrl, extractXsrfTokenFromCookie } from "./apiclient"; import fetchMock from "fetch-mock"; import { BackendError } from "./errors"; From bf98c5905002b889e2afba483467be669965732f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 18 Nov 2019 12:42:05 +0000 Subject: [PATCH 4/4] Close branch bugfix/anon_after_session_expired