mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-06 21:45:43 +01:00
merge with default branch
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -5,8 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Added footer extension points for links and avatar
|
- Added footer extension points for links and avatar
|
||||||
|
- Create OpenAPI specification durring build
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- New footer design
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Modification for mercurial repositories with enabled XSRF protection
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Enunciate rest documentation
|
||||||
|
|
||||||
## 2.0.0-rc4 - 2020-02-14
|
## 2.0.0-rc4 - 2020-02-14
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM openjdk:8u171-alpine3.8
|
FROM openjdk:8u212-alpine3.9
|
||||||
|
|
||||||
ENV SCM_HOME=/var/lib/scm
|
ENV SCM_HOME=/var/lib/scm
|
||||||
|
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -450,7 +450,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>sonia.scm.maven</groupId>
|
<groupId>sonia.scm.maven</groupId>
|
||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<version>1.0.0-rc3</version>
|
<version>1.0.0-rc4</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -855,8 +855,8 @@
|
|||||||
<guava.version>26.0-jre</guava.version>
|
<guava.version>26.0-jre</guava.version>
|
||||||
|
|
||||||
<!-- frontend -->
|
<!-- frontend -->
|
||||||
<nodejs.version>10.16.0</nodejs.version>
|
<nodejs.version>12.16.1</nodejs.version>
|
||||||
<yarn.version>1.16.0</yarn.version>
|
<yarn.version>1.22.0</yarn.version>
|
||||||
|
|
||||||
<!-- build properties -->
|
<!-- build properties -->
|
||||||
<project.build.javaLevel>8</project.build.javaLevel>
|
<project.build.javaLevel>8</project.build.javaLevel>
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ package sonia.scm.security;
|
|||||||
*/
|
*/
|
||||||
public final class Xsrf {
|
public final class Xsrf {
|
||||||
|
|
||||||
static final String HEADER_KEY = "X-XSRF-Token";
|
public static final String HEADER_KEY = "X-XSRF-Token";
|
||||||
|
|
||||||
static final String TOKEN_KEY = "xsrf";
|
public static final String TOKEN_KEY = "xsrf";
|
||||||
|
|
||||||
private Xsrf() {
|
private Xsrf() {
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ package sonia.scm.repository;
|
|||||||
import com.google.inject.ProvisionException;
|
import com.google.inject.ProvisionException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.security.AccessToken;
|
||||||
|
import sonia.scm.security.CipherUtil;
|
||||||
|
import sonia.scm.security.Xsrf;
|
||||||
import sonia.scm.web.HgUtil;
|
import sonia.scm.web.HgUtil;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||||
|
|
||||||
|
private static final String SCM_XSRF = "SCM_XSRF";
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,8 +119,9 @@ public final class HgEnvironment
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String credentials = hookManager.getCredentials();
|
AccessToken accessToken = hookManager.getAccessToken();
|
||||||
environment.put(SCM_BEARER_TOKEN, credentials);
|
environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact()));
|
||||||
|
extractXsrfKey(environment, accessToken);
|
||||||
} catch (ProvisionException e) {
|
} 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);
|
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,8 @@ public final class HgEnvironment
|
|||||||
environment.put(ENV_URL, hookUrl);
|
environment.put(ENV_URL, hookUrl);
|
||||||
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void extractXsrfKey(Map<String, String> environment, AccessToken accessToken) {
|
||||||
|
environment.put(SCM_XSRF, accessToken.<String>getCustom(Xsrf.TOKEN_KEY).orElse("-"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import sonia.scm.config.ScmConfigurationChangedEvent;
|
|||||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||||
import sonia.scm.security.AccessToken;
|
import sonia.scm.security.AccessToken;
|
||||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||||
import sonia.scm.security.CipherUtil;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
@@ -196,11 +195,9 @@ public class HgHookManager
|
|||||||
return this.challenge.equals(challenge);
|
return this.challenge.equals(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCredentials()
|
public AccessToken getAccessToken()
|
||||||
{
|
{
|
||||||
AccessToken accessToken = accessTokenBuilderFactory.create().build();
|
return accessTokenBuilderFactory.create().build();
|
||||||
|
|
||||||
return CipherUtil.getInstance().encode(accessToken.compact());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import os, urllib, urllib2
|
|||||||
baseUrl = os.environ['SCM_URL']
|
baseUrl = os.environ['SCM_URL']
|
||||||
challenge = os.environ['SCM_CHALLENGE']
|
challenge = os.environ['SCM_CHALLENGE']
|
||||||
token = os.environ['SCM_BEARER_TOKEN']
|
token = os.environ['SCM_BEARER_TOKEN']
|
||||||
|
xsrf = os.environ['SCM_XSRF']
|
||||||
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
||||||
|
|
||||||
def printMessages(ui, msgs):
|
def printMessages(ui, msgs):
|
||||||
@@ -59,6 +60,7 @@ def callHookUrl(ui, repo, hooktype, node):
|
|||||||
proxy_handler = urllib2.ProxyHandler({})
|
proxy_handler = urllib2.ProxyHandler({})
|
||||||
opener = urllib2.build_opener(proxy_handler)
|
opener = urllib2.build_opener(proxy_handler)
|
||||||
req = urllib2.Request(url, data)
|
req = urllib2.Request(url, data)
|
||||||
|
req.add_header("X-XSRF-Token", xsrf)
|
||||||
conn = opener.open(req)
|
conn = opener.open(req)
|
||||||
if 200 <= conn.code < 300:
|
if 200 <= conn.code < 300:
|
||||||
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
|
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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 sonia.scm.security.AccessToken;
|
||||||
|
import sonia.scm.security.Xsrf;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class HgEnvironmentTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
HgRepositoryHandler handler;
|
||||||
|
@Mock
|
||||||
|
HgHookManager hookManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExtractXsrfTokenWhenSet() {
|
||||||
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
|
when(accessToken.compact()).thenReturn("");
|
||||||
|
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(of("XSRF Token"));
|
||||||
|
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||||
|
|
||||||
|
Map<String, String> environment = new HashMap<>();
|
||||||
|
HgEnvironment.prepareEnvironment(environment, handler, hookManager);
|
||||||
|
|
||||||
|
assertThat(environment).contains(entry("SCM_XSRF", "XSRF Token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIgnoreXsrfWhenNotSetButStillContainDummy() {
|
||||||
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
|
when(accessToken.compact()).thenReturn("");
|
||||||
|
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(empty());
|
||||||
|
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||||
|
|
||||||
|
Map<String, String> environment = new HashMap<>();
|
||||||
|
HgEnvironment.prepareEnvironment(environment, handler, hookManager);
|
||||||
|
|
||||||
|
assertThat(environment).containsKeys("SCM_XSRF");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ package sonia.scm.repository;
|
|||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.TempDirRepositoryLocationResolver;
|
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||||
|
import sonia.scm.security.AccessToken;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -107,7 +108,6 @@ public final class HgTestUtil
|
|||||||
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
||||||
HgRepositoryHandler handler =
|
HgRepositoryHandler handler =
|
||||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
||||||
Path repoDir = directory.toPath();
|
|
||||||
handler.init(context);
|
handler.init(context);
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
@@ -128,7 +128,9 @@ public final class HgTestUtil
|
|||||||
"http://localhost:8081/scm/hook/hg/");
|
"http://localhost:8081/scm/hook/hg/");
|
||||||
when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn(
|
when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn(
|
||||||
"http://localhost:8081/scm/hook/hg/");
|
"http://localhost:8081/scm/hook/hg/");
|
||||||
when(hookManager.getCredentials()).thenReturn("");
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
|
when(accessToken.compact()).thenReturn("");
|
||||||
|
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||||
|
|
||||||
return hookManager;
|
return hookManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const byKey = (key: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isUndefined(b, key)) {
|
if (isUndefined(b, key)) {
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a[key] < b[key]) {
|
if (a[key] < b[key]) {
|
||||||
@@ -35,7 +35,7 @@ export const byValueLength = (key: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isUndefined(b, key)) {
|
if (isUndefined(b, key)) {
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a[key].length < b[key].length) {
|
if (a[key].length < b[key].length) {
|
||||||
@@ -55,7 +55,7 @@ export const byNestedKeys = (key: string, nestedKey: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isUndefined(b, key, nestedKey)) {
|
if (isUndefined(b, key, nestedKey)) {
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a[key][nestedKey] < b[key][nestedKey]) {
|
if (a[key][nestedKey] < b[key][nestedKey]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user