commandInterpreter = lfsAuthCommand.canHandle("git-lfs-something repo/space/X upload");
+ assertThat(commandInterpreter).isEmpty();
+ }
+
+ @Test
+ void shouldExtractRepositoryArgument() {
+ CommandInterpreter commandInterpreter = lfsAuthCommand.canHandle("git-lfs-authenticate repo/space/X\t upload").get();
+ assertThat(commandInterpreter.getParsedArgs()).containsOnly("repo/space/X");
+ }
+
+ @Test
+ void shouldCreateJsonResponse() throws IOException {
+ CommandInterpreter commandInterpreter = lfsAuthCommand.canHandle("git-lfs-authenticate repo/space/X\t upload").get();
+ CommandContext commandContext = createCommandContext();
+ commandInterpreter.getProtocolHandler().handle(commandContext, createRepositoryContext());
+ assertThat(commandContext.getOutputStream().toString())
+ .isEqualTo("{\"href\":\"http://example.com/repo/space/X.git/info/lfs/\",\"header\":{\"Authorization\":\"Bearer ACCESS_TOKEN\"},\"expires_at\":\"2007-05-03T10:15:30Z\"}");
+ }
+
+ private CommandContext createCommandContext() {
+ return new CommandContext(null, null, null, new ByteArrayOutputStream(), null);
+ }
+
+ private RepositoryContext createRepositoryContext() {
+ return new RepositoryContext(REPOSITORY, null);
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/ScmBlobLfsRepositoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/ScmBlobLfsRepositoryTest.java
new file mode 100644
index 0000000000..eefa4314c2
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/ScmBlobLfsRepositoryTest.java
@@ -0,0 +1,98 @@
+package sonia.scm.web.lfs;
+
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.junit.jupiter.api.BeforeEach;
+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.repository.Repository;
+import sonia.scm.security.AccessToken;
+import sonia.scm.store.BlobStore;
+
+import java.util.Date;
+
+import static java.time.Instant.parse;
+import static java.util.Date.from;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.jgit.lfs.lib.LongObjectId.fromString;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class ScmBlobLfsRepositoryTest {
+
+ static final Repository REPOSITORY = new Repository("1", "git", "space", "X");
+ static final Date EXPIRATION = from(parse("2007-05-03T10:15:30.00Z"));
+ static final LongObjectId OBJECT_ID = fromString("976ed944c37cc5d1606af316937edb9d286ecf6c606af316937edb9d286ecf6c");
+
+ @Mock
+ BlobStore blobStore;
+ @Mock
+ LfsAccessTokenFactory tokenFactory;
+
+ ScmBlobLfsRepository lfsRepository;
+
+ @BeforeEach
+ void initializeLfsRepository() {
+ lfsRepository = new ScmBlobLfsRepository(REPOSITORY, blobStore, tokenFactory, "http://scm.org/");
+ }
+
+ @BeforeEach
+ void initAuthorizationToken() {
+ AccessToken readToken = createToken("READ_TOKEN");
+ lenient().when(this.tokenFactory.createReadAccessToken(REPOSITORY))
+ .thenReturn(readToken);
+ AccessToken writeToken = createToken("WRITE_TOKEN");
+ lenient().when(this.tokenFactory.createWriteAccessToken(REPOSITORY))
+ .thenReturn(writeToken);
+ }
+
+ AccessToken createToken(String mockedValue) {
+ AccessToken accessToken = mock(AccessToken.class);
+ lenient().when(accessToken.getExpiration()).thenReturn(EXPIRATION);
+ lenient().when(accessToken.compact()).thenReturn(mockedValue);
+ return accessToken;
+ }
+
+ @Test
+ void shouldTakeExpirationFromToken() {
+ ExpiringAction downloadAction = lfsRepository.getDownloadAction(OBJECT_ID);
+ assertThat(downloadAction.expires_at).isEqualTo("2007-05-03T10:15:30Z");
+ }
+
+ @Test
+ void shouldContainReadTokenForDownlo() {
+ ExpiringAction downloadAction = lfsRepository.getDownloadAction(OBJECT_ID);
+ assertThat(downloadAction.header.get("Authorization")).isEqualTo("Bearer READ_TOKEN");
+ }
+
+ @Test
+ void shouldContainWriteTokenForUpload() {
+ ExpiringAction downloadAction = lfsRepository.getUploadAction(OBJECT_ID, 42L);
+ assertThat(downloadAction.header.get("Authorization")).isEqualTo("Bearer WRITE_TOKEN");
+ }
+
+ @Test
+ void shouldContainUrl() {
+ ExpiringAction downloadAction = lfsRepository.getDownloadAction(OBJECT_ID);
+ assertThat(downloadAction.href).isEqualTo("http://scm.org/976ed944c37cc5d1606af316937edb9d286ecf6c606af316937edb9d286ecf6c");
+ }
+
+ @Test
+ void shouldCreateTokenForDownloadActionOnlyOnce() {
+ lfsRepository.getDownloadAction(OBJECT_ID);
+ lfsRepository.getDownloadAction(OBJECT_ID);
+ verify(tokenFactory, times(1)).createReadAccessToken(REPOSITORY);
+ }
+
+ @Test
+ void shouldCreateTokenForUploadActionOnlyOnce() {
+ lfsRepository.getUploadAction(OBJECT_ID, 42L);
+ lfsRepository.getUploadAction(OBJECT_ID, 42L);
+ verify(tokenFactory, times(1)).createWriteAccessToken(REPOSITORY);
+ }
+}
+
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java
index 09a431ea43..f386dc2125 100644
--- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java
@@ -11,41 +11,26 @@ import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-/**
- * Created by omilke on 18.05.2017.
- */
public class LfsServletFactoryTest {
+ private static final String NAMESPACE = "space";
+ private static final String NAME = "git-lfs-demo";
+ private static final Repository REPOSITORY = new Repository("", "GIT", NAMESPACE, NAME);
+
@Test
- public void buildBaseUri() {
-
- String repositoryNamespace = "space";
- String repositoryName = "git-lfs-demo";
-
- String result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, true));
- assertThat(result, is(equalTo("http://localhost:8081/scm/repo/space/git-lfs-demo.git/info/lfs/objects/")));
-
-
- //result will be with dot-git suffix, ide
- result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, false));
+ public void shouldBuildBaseUri() {
+ String result = LfsServletFactory.buildBaseUri(REPOSITORY, requestWithUri("git-lfs-demo"));
assertThat(result, is(equalTo("http://localhost:8081/scm/repo/space/git-lfs-demo.git/info/lfs/objects/")));
}
- private HttpServletRequest RequestWithUri(String repositoryName, boolean withDotGitSuffix) {
+ private HttpServletRequest requestWithUri(String repositoryName) {
HttpServletRequest mockedRequest = mock(HttpServletRequest.class);
- final String suffix;
- if (withDotGitSuffix) {
- suffix = ".git";
- } else {
- suffix = "";
- }
-
//build from valid live request data
when(mockedRequest.getRequestURL()).thenReturn(
- new StringBuffer(String.format("http://localhost:8081/scm/repo/%s%s/info/lfs/objects/batch", repositoryName, suffix)));
- when(mockedRequest.getRequestURI()).thenReturn(String.format("/scm/repo/%s%s/info/lfs/objects/batch", repositoryName, suffix));
+ new StringBuffer(String.format("http://localhost:8081/scm/repo/%s/info/lfs/objects/batch", repositoryName)));
+ when(mockedRequest.getRequestURI()).thenReturn(String.format("/scm/repo/%s/info/lfs/objects/batch", repositoryName));
when(mockedRequest.getContextPath()).thenReturn("/scm");
return mockedRequest;
diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java
index 617950ddea..ed7093c09c 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java
@@ -1,19 +1,19 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
+ * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,17 +24,20 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* http://bitbucket.org/sdorra/scm-manager
- *
*/
package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting;
+
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
+
+import com.google.inject.OutOfScopeException;
+import com.google.inject.ProvisionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
@@ -46,9 +49,9 @@ import sonia.scm.util.HttpUtil;
* add the xsrf field, if the authentication request is issued from the web interface and xsrf protection is
* enabled. The xsrf field will be validated on every request by the {@link XsrfAccessTokenValidator}. Xsrf protection
* can be disabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}.
- *
- * @see Issue 793
+ *
* @author Sebastian Sdorra
+ * @see Issue 793
* @since 2.0.0
*/
@Extension
@@ -58,14 +61,14 @@ public class XsrfAccessTokenEnricher implements AccessTokenEnricher {
* the logger for XsrfAccessTokenEnricher
*/
private static final Logger LOG = LoggerFactory.getLogger(XsrfAccessTokenEnricher.class);
-
+
private final ScmConfiguration configuration;
private final Provider requestProvider;
/**
* Constructs a new instance.
- *
- * @param configuration scm main configuration
+ *
+ * @param configuration scm main configuration
* @param requestProvider http request provider
*/
@Inject
@@ -73,12 +76,11 @@ public class XsrfAccessTokenEnricher implements AccessTokenEnricher {
this.configuration = configuration;
this.requestProvider = requestProvider;
}
-
+
@Override
public void enrich(AccessTokenBuilder builder) {
if (configuration.isEnabledXsrfProtection()) {
- if (HttpUtil.isWUIRequest(requestProvider.get())) {
- LOG.debug("received wui token claim, enrich jwt with xsrf key");
+ if (isEnrichable()) {
builder.custom(Xsrf.TOKEN_KEY, createToken());
} else {
LOG.trace("skip xsrf enrichment, because jwt session is started from a non wui client");
@@ -87,11 +89,30 @@ public class XsrfAccessTokenEnricher implements AccessTokenEnricher {
LOG.trace("xsrf is disabled, skip xsrf enrichment");
}
}
-
+
+ private boolean isEnrichable() {
+ try {
+ HttpServletRequest request = requestProvider.get();
+ if (HttpUtil.isWUIRequest(request)) {
+ LOG.debug("received wui token claim, enrich jwt with xsrf key");
+ return true;
+ } else {
+ LOG.trace("skip xsrf enrichment, because jwt session is started from a non wui client");
+ }
+ } catch (ProvisionException ex) {
+ if (ex.getCause() instanceof OutOfScopeException) {
+ LOG.trace("skip xsrf enrichment, because no request scope is available");
+ } else {
+ throw ex;
+ }
+ }
+ return false;
+ }
+
@VisibleForTesting
String createToken() {
// TODO create interface and use a better method
return UUID.randomUUID().toString();
}
-
+
}
diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenEnricherTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenEnricherTest.java
index 37d853011d..5fbaaeb64c 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenEnricherTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenEnricherTest.java
@@ -1,19 +1,19 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
+ * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,103 +24,133 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* http://bitbucket.org/sdorra/scm-manager
- *
*/
package sonia.scm.security;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.ProvisionException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
+import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
/**
* Unit tests for {@link XsrfAccessTokenEnricher}.
- *
+ *
* @author Sebastian Sdorra
*/
-@RunWith(MockitoJUnitRunner.class)
-public class XsrfAccessTokenEnricherTest {
+@ExtendWith(MockitoExtension.class)
+class XsrfAccessTokenEnricherTest {
@Mock
private HttpServletRequest request;
@Mock
private AccessTokenBuilder builder;
-
+
private ScmConfiguration configuration;
-
+
private XsrfAccessTokenEnricher enricher;
-
- /**
- * Prepare object under test.
- */
- @Before
- public void prepareObjectUnderTest() {
+
+ @BeforeEach
+ void createConfiguration() {
configuration = new ScmConfiguration();
- enricher = new XsrfAccessTokenEnricher(configuration, () -> request) {
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testWithoutRequestScope() {
+ // prepare
+ Provider requestProvider = mock(Provider.class);
+ when(requestProvider.get()).thenThrow(new ProvisionException("failed to provision", new OutOfScopeException("no request scope is available")));
+ configuration.setEnabledXsrfProtection(true);
+ XsrfAccessTokenEnricher enricher = createEnricher(requestProvider);
+
+ // execute
+ enricher.enrich(builder);
+
+ // assert
+ verify(builder, never()).custom(Xsrf.TOKEN_KEY, "42");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testWithProvisionException() {
+ // prepare
+ Provider requestProvider = mock(Provider.class);
+ when(requestProvider.get()).thenThrow(new ProvisionException("failed to provision"));
+ configuration.setEnabledXsrfProtection(true);
+ XsrfAccessTokenEnricher enricher = createEnricher(requestProvider);
+
+ // execute
+ assertThrows(ProvisionException.class, () -> enricher.enrich(builder));
+ }
+
+ private XsrfAccessTokenEnricher createEnricher(Provider requestProvider) {
+ return new XsrfAccessTokenEnricher(configuration, requestProvider) {
@Override
String createToken() {
return "42";
}
};
}
-
- /**
- * Tests {@link XsrfAccessTokenEnricher#enrich(java.util.Map)}.
- */
- @Test
- public void testEnrich() {
- // prepare
- configuration.setEnabledXsrfProtection(true);
- when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
-
- // execute
- enricher.enrich(builder);
-
- // assert
- verify(builder).custom(Xsrf.TOKEN_KEY, "42");
- }
-
- /**
- * Tests {@link XsrfAccessTokenEnricher#enrich(java.util.Map)} with disabled xsrf protection.
- */
- @Test
- public void testEnrichWithDisabledXsrf() {
- // prepare
- configuration.setEnabledXsrfProtection(false);
- // execute
- enricher.enrich(builder);
-
- // assert
- verify(builder, never()).custom(Xsrf.TOKEN_KEY, "42");
- }
-
- /**
- * Tests {@link XsrfAccessTokenEnricher#enrich(java.util.Map)} with disabled xsrf protection.
- */
- @Test
- public void testEnrichWithNonWuiClient() {
- // prepare
- configuration.setEnabledXsrfProtection(true);
-
- // execute
- enricher.enrich(builder);
-
- // assert
- verify(builder, never()).custom(Xsrf.TOKEN_KEY, "42");
- }
+ @Nested
+ class WithRequestMock {
+ @BeforeEach
+ void setupEnricher() {
+ enricher = createEnricher(() -> request);
+ }
+
+ @Test
+ void testEnrich() {
+ // prepare
+ configuration.setEnabledXsrfProtection(true);
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+
+ // execute
+ enricher.enrich(builder);
+
+ // assert
+ verify(builder).custom(Xsrf.TOKEN_KEY, "42");
+ }
+
+ @Test
+ void testEnrichWithDisabledXsrf() {
+ // prepare
+ configuration.setEnabledXsrfProtection(false);
+
+ // execute
+ enricher.enrich(builder);
+
+ // assert
+ verify(builder, never()).custom(Xsrf.TOKEN_KEY, "42");
+ }
+
+ @Test
+ void testEnrichWithNonWuiClient() {
+ // prepare
+ configuration.setEnabledXsrfProtection(true);
+
+ // execute
+ enricher.enrich(builder);
+
+ // assert
+ verify(builder, never()).custom(Xsrf.TOKEN_KEY, "42");
+ }
+ }
}