diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml
index e57652bf0f..2dfd89c8fb 100644
--- a/scm-plugins/scm-hg-plugin/pom.xml
+++ b/scm-plugins/scm-hg-plugin/pom.xml
@@ -27,6 +27,19 @@
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.10.5
+ provided
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.10.5
+ test
+
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
index 1d227fb54e..085e1516eb 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
@@ -36,8 +36,11 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.ProvisionException;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import sonia.scm.security.CipherUtil;
import sonia.scm.web.HgUtil;
import javax.servlet.http.HttpServletRequest;
@@ -65,6 +68,8 @@ public final class HgEnvironment
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
+ private static final String SCM_XSRF = "SCM_XSRF";
+
//~--- constructors ---------------------------------------------------------
/**
@@ -115,7 +120,8 @@ public final class HgEnvironment
try {
String credentials = hookManager.getCredentials();
- environment.put(SCM_BEARER_TOKEN, credentials);
+ environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(credentials));
+ extractXsrfKey(environment, credentials);
} catch (ProvisionException e) {
LOG.debug("could not create bearer token; looks like currently we are not in a request; probably you can ignore the following exception:", e);
}
@@ -123,4 +129,16 @@ public final class HgEnvironment
environment.put(ENV_URL, hookUrl);
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
}
+
+ private static void extractXsrfKey(Map environment, String credentials) {
+ // we need to remove the signature, because we cannot access the key and otherwise the parser would fail
+ String[] tokenParts = credentials.split("\\.");
+ String tokenWithoutSignature = tokenParts[0] + "." + tokenParts[1] + ".";
+ Claims claims = (Claims) Jwts.parser().parse(tokenWithoutSignature).getBody();
+
+ Object xsrf = claims.get("xsrf");
+ if (xsrf != null) {
+ environment.put(SCM_XSRF, xsrf.toString());
+ }
+ }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
index 6815bdad96..9784f3d49d 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
@@ -200,7 +200,7 @@ public class HgHookManager
{
AccessToken accessToken = accessTokenBuilderFactory.create().build();
- return CipherUtil.getInstance().encode(accessToken.compact());
+ return accessToken.compact();
}
//~--- methods --------------------------------------------------------------
@@ -279,7 +279,7 @@ public class HgHookManager
//J-
return HttpUtil.getUriWithoutEndSeperator(
MoreObjects.firstNonNull(
- configuration.getBaseUrl(),
+ configuration.getBaseUrl(),
"http://localhost:8080/scm"
)
).concat("/hook/hg/");
diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py
index 637aa16331..ca8d7736a7 100644
--- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py
+++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py
@@ -41,6 +41,7 @@ import os, urllib, urllib2
baseUrl = os.environ['SCM_URL']
challenge = os.environ['SCM_CHALLENGE']
token = os.environ['SCM_BEARER_TOKEN']
+xsrf = os.environ['SCM_XSRF']
repositoryId = os.environ['SCM_REPOSITORY_ID']
def printMessages(ui, msgs):
@@ -59,6 +60,7 @@ def callHookUrl(ui, repo, hooktype, node):
proxy_handler = urllib2.ProxyHandler({})
opener = urllib2.build_opener(proxy_handler)
req = urllib2.Request(url, data)
+ req.add_header("X-XSRF-Token", xsrf)
conn = opener.open(req)
if 200 <= conn.code < 300:
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
@@ -101,7 +103,7 @@ def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
# older mercurial versions
if pending != None:
pending()
-
+
# newer mercurial version
# we have to make in-memory changes visible to external process
# this does not happen automatically, because mercurial treat our hooks as internal hooks
diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java
new file mode 100644
index 0000000000..2d4468122a
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgEnvironmentTest.java
@@ -0,0 +1,46 @@
+package sonia.scm.repository;
+
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class HgEnvironmentTest {
+
+ private static final String CREDENTIALS_WITH_XSRF = "eyJhbGciOiJIUzI1NiJ9.eyJ4c3JmIjoiZjlhMWRiNzQtM2UwNS00YTMwLTlkODMtNjZmNWQ1MDc3Y2FjIiwic3ViIjoic2NtYWRtaW4iLCJqdGkiOiI2d1JxTWpyelYxSCIsImlhdCI6MTU4MTU4MTI3OSwiZXhwIjoxNTgxNTg0ODc5LCJzY20tbWFuYWdlci5yZWZyZXNoRXhwaXJhdGlvbiI6MTU4MTYyNDQ3OTczMCwic2NtLW1hbmFnZXIucGFyZW50VG9rZW5JZCI6IjZ3UnFNanJ6VjFIIn0.O5MADk9scaHgYNPDFh7Nd9R2rMZyDuMs7LuC4OSA3jA";
+ private static final String CREDENTIALS_WITHOUT_XSRF = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzY21hZG1pbiIsImp0aSI6IjdvUnFNbERrRTFOIiwiaWF0IjoxNTgxNTgxNjAxLCJleHAiOjE1ODE1ODUyMDEsInNjbS1tYW5hZ2VyLnJlZnJlc2hFeHBpcmF0aW9uIjoxNTgxNjI0ODAxNjc5LCJzY20tbWFuYWdlci5wYXJlbnRUb2tlbklkIjoiN29ScU1sRGtFMU4ifQ.KaTPjT09xtIEZDBOM28pSgyYSEtVZ37gcyTp1_3sTGA";
+
+ @Mock
+ HgRepositoryHandler handler;
+ @Mock
+ HgHookManager hookManager;
+
+ @Test
+ void shouldExtractXsrfTokenWhenSet() {
+ when(hookManager.getCredentials()).thenReturn(CREDENTIALS_WITH_XSRF);
+
+ Map environment = new HashMap<>();
+ HgEnvironment.prepareEnvironment(environment, handler, hookManager);
+
+ assertThat(environment).contains(entry("SCM_XSRF", "f9a1db74-3e05-4a30-9d83-66f5d5077cac"));
+ }
+
+ @Test
+ void shouldIgnoreXsrfWhenNotSet() {
+ when(hookManager.getCredentials()).thenReturn(CREDENTIALS_WITHOUT_XSRF);
+
+ Map environment = new HashMap<>();
+ HgEnvironment.prepareEnvironment(environment, handler, hookManager);
+
+ assertThat(environment).doesNotContainKeys("SCM_XSRF");
+ }
+}