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"); + } +}