Merge "Merge branch 'stable-2.13'"
diff --git a/BUCK b/BUCK
index 81ea2f6..409fd6d 100644
--- a/BUCK
+++ b/BUCK
@@ -13,6 +13,9 @@
':jgit-lfs',
':jgit-lfs-server',
],
+ provided_deps = [
+ '//lib/httpcomponents:httpcore',
+ ],
manifest_entries = [
'Gerrit-PluginName: lfs',
'Gerrit-Module: com.googlesource.gerrit.plugins.lfs.Module',
@@ -61,3 +64,14 @@
'plugin.properties',
],
)
+
+java_test(
+ name = 'lfs_tests',
+ srcs = glob(['src/test/java/**/*.java']),
+ labels = ['lfs'],
+ source_under_test = [':lfs__plugin'],
+ deps = GERRIT_PLUGIN_API + GERRIT_TESTS + [
+ ':lfs__plugin',
+ '//plugins/lfs:jgit-lfs',
+ ],
+)
\ No newline at end of file
diff --git a/lib/httpcomponents/BUCK b/lib/httpcomponents/BUCK
new file mode 100644
index 0000000..60f1699
--- /dev/null
+++ b/lib/httpcomponents/BUCK
@@ -0,0 +1,10 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+# httpcore version should match version used in Gerrit
+maven_jar(
+ name = 'httpcore',
+ id = 'org.apache.httpcomponents:httpcore:4.4.1',
+ bin_sha1 = 'f5aa318bda4c6c8d688c9d00b90681dcd82ce636',
+ src_sha1 = '9700be0d0a331691654a8e901943c9a74e33c5fc',
+ license = 'Apache2.0',
+)
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
index 7997466..c107418 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
@@ -22,16 +22,18 @@
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.lfs.fs.LfsFsContentServlet;
import com.googlesource.gerrit.plugins.lfs.fs.LocalLargeFileRepository;
import com.googlesource.gerrit.plugins.lfs.s3.S3LargeFileRepository;
+import org.eclipse.jgit.lfs.server.fs.LfsFsContentServlet;
+
import java.util.Map;
public class HttpModule extends HttpPluginModule {
private final LocalLargeFileRepository.Factory fsRepoFactory;
private final S3LargeFileRepository.Factory s3RepoFactory;
private final LfsRepositoriesCache cache;
+ private final LfsFsContentServlet.Factory fsServletFactory;
private final LfsBackend defaultBackend;
private final Map<String, LfsBackend> backends;
@@ -39,10 +41,12 @@
HttpModule(LocalLargeFileRepository.Factory fsRepoFactory,
S3LargeFileRepository.Factory s3RepoFactory,
LfsRepositoriesCache cache,
+ LfsFsContentServlet.Factory fsServletFactory,
LfsConfigurationFactory configFactory) {
this.fsRepoFactory = fsRepoFactory;
this.s3RepoFactory = s3RepoFactory;
this.cache = cache;
+ this.fsServletFactory = fsServletFactory;
LfsGlobalConfig config = configFactory.getGlobalConfig();
this.defaultBackend = config.getDefaultBackend();
@@ -88,6 +92,6 @@
fsRepoFactory.create(backend);
cache.put(backend, repository);
serve(repository.getServletUrlPattern())
- .with(new LfsFsContentServlet(repository));
+ .with(fsServletFactory.create(repository));
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/Module.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/Module.java
index 3b064df..bd7519a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/Module.java
@@ -22,6 +22,8 @@
import com.googlesource.gerrit.plugins.lfs.fs.LocalLargeFileRepository;
import com.googlesource.gerrit.plugins.lfs.s3.S3LargeFileRepository;
+import org.eclipse.jgit.lfs.server.fs.LfsFsContentServlet;
+
public class Module extends FactoryModule {
@Override
@@ -37,5 +39,6 @@
factory(S3LargeFileRepository.Factory.class);
factory(LocalLargeFileRepository.Factory.class);
+ factory(LfsFsContentServlet.Factory.class);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsContentServlet.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsContentServlet.java
deleted file mode 100644
index bc9fc05..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsContentServlet.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.lfs.fs;
-
-import org.eclipse.jgit.lfs.server.fs.FileLfsServlet;
-
-public class LfsFsContentServlet extends FileLfsServlet {
- private static final long serialVersionUID = 1L;
-
- public LfsFsContentServlet(LocalLargeFileRepository largeFileRepository) {
- super(largeFileRepository, 0);
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizer.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizer.java
new file mode 100644
index 0000000..8b67591
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizer.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.lfs.fs;
+
+import com.google.common.base.Strings;
+import com.google.common.primitives.Bytes;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.util.Base64;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+@Singleton
+public class LfsFsRequestAuthorizer {
+ private static final Logger log = LoggerFactory.getLogger(LfsFsRequestAuthorizer.class);
+ private static final int IV_LENGTH = 16;
+ private static final String ALGORITHM = "AES";
+ static final DateTimeFormatter DATE_TIME =
+ DateTimeFormat.forPattern("YYYYMMDDHHmmss");
+
+ private final SecureRandom rndm;
+ private final SecretKey key;
+
+ @Inject
+ LfsFsRequestAuthorizer() {
+ this.rndm = new SecureRandom();
+ this.key = generateKey();
+ }
+
+ public String generateToken(String operation, AnyLongObjectId id,
+ int expirationSeconds) {
+ try {
+ byte[] initVector = new byte[IV_LENGTH];
+ rndm.nextBytes(initVector);
+ Cipher cipher = cipher(initVector, Cipher.ENCRYPT_MODE);
+ return Base64.encodeBytes(Bytes.concat(initVector,
+ cipher.doFinal(String.format("%s-%s-%s", operation,
+ id.name(), timeout(expirationSeconds))
+ .getBytes(StandardCharsets.UTF_8))));
+ } catch (GeneralSecurityException e) {
+ log.error("Token generation failed with error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean verifyAgainstToken(String token, String operation,
+ AnyLongObjectId id) {
+ if (Strings.isNullOrEmpty(token)) {
+ return false;
+ }
+
+ byte[] bytes = Base64.decode(token);
+ byte[] initVector = Arrays.copyOf(bytes, IV_LENGTH);
+ try {
+ Cipher cipher = cipher(initVector, Cipher.DECRYPT_MODE);
+ String data = new String(
+ cipher.doFinal(Arrays.copyOfRange(bytes, IV_LENGTH, bytes.length)),
+ StandardCharsets.UTF_8);
+ String oid = id.name();
+ String prefix = String.format("%s-%s-", operation, oid);
+ return data.startsWith(prefix)
+ && onTime(data.substring(prefix.length()), operation, oid);
+ } catch (GeneralSecurityException e) {
+ log.error("Exception was thrown during token verification", e);
+ }
+
+ return false;
+ }
+
+ boolean onTime(String dateTime, String operation, String id) {
+ String now = DATE_TIME.print(now());
+ if (now.compareTo(dateTime) > 0) {
+ log.info("Operation {} on id {} timed out", operation, id);
+ return false;
+ }
+
+ return true;
+ }
+
+ private String timeout(int expirationSeconds) {
+ return DATE_TIME.print(now().plusSeconds(expirationSeconds));
+ }
+
+ private DateTime now() {
+ return DateTime.now().toDateTime(DateTimeZone.UTC);
+ }
+
+ private Cipher cipher(byte[] initVector, int mode) throws NoSuchAlgorithmException,
+ NoSuchPaddingException, InvalidParameterSpecException,
+ InvalidKeyException, InvalidAlgorithmParameterException {
+ IvParameterSpec spec = new IvParameterSpec(initVector);
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
+ AlgorithmParameters params = AlgorithmParameters.getInstance(ALGORITHM);
+ params.init(spec);
+ cipher.init(mode, key, params);
+ return cipher;
+ }
+
+ private SecretKey generateKey() {
+ try {
+ KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
+ generator.init(128, rndm);
+ return generator.generateKey();
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Generating key failed with error", e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LocalLargeFileRepository.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LocalLargeFileRepository.java
index 83251a4..a810943 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LocalLargeFileRepository.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LocalLargeFileRepository.java
@@ -15,6 +15,7 @@
package com.googlesource.gerrit.plugins.lfs.fs;
import static com.googlesource.gerrit.plugins.lfs.LfsBackend.DEFAULT;
+import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
@@ -26,12 +27,15 @@
import com.googlesource.gerrit.plugins.lfs.LfsConfigurationFactory;
import com.googlesource.gerrit.plugins.lfs.LfsGlobalConfig;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.server.Response;
import org.eclipse.jgit.lfs.server.fs.FileLfsRepository;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Collections;
public class LocalLargeFileRepository extends FileLfsRepository {
public interface Factory {
@@ -39,24 +43,50 @@
}
public static final String CONTENT_PATH = "content";
+ public static final String UPLOAD = "upload";
+ public static final String DOWNLOAD = "download";
+ private static final int DEFAULT_TIMEOUT = 10; //in seconds
private final String servletUrlPattern;
+ private final LfsFsRequestAuthorizer authorizer;
+ private final int expirationSeconds;
@Inject
LocalLargeFileRepository(LfsConfigurationFactory configFactory,
+ LfsFsRequestAuthorizer authorizer,
@PluginCanonicalWebUrl String url,
@PluginData Path defaultDataDir,
@Assisted LfsBackend backend) throws IOException {
super(getContentUrl(url, backend),
getOrCreateDataDir(configFactory.getGlobalConfig(),
backend, defaultDataDir));
+ this.authorizer = authorizer;
this.servletUrlPattern = "/" + getContentPath(backend) + "*";
+ this.expirationSeconds = configFactory.getGlobalConfig()
+ .getInt(backend.type.name(), backend.name, "expirationSeconds",
+ DEFAULT_TIMEOUT);
}
public String getServletUrlPattern() {
return servletUrlPattern;
}
+ @Override
+ public Response.Action getDownloadAction(AnyLongObjectId id) {
+ Response.Action action = super.getDownloadAction(id);
+ action.header = Collections.singletonMap(HDR_AUTHORIZATION,
+ authorizer.generateToken(DOWNLOAD, id, expirationSeconds));
+ return action;
+ }
+
+ @Override
+ public Response.Action getUploadAction(AnyLongObjectId id, long size) {
+ Response.Action action = super.getUploadAction(id, size);
+ action.header = Collections.singletonMap(HDR_AUTHORIZATION,
+ authorizer.generateToken(UPLOAD, id, expirationSeconds));
+ return action;
+ }
+
private static String getContentUrl(String url, LfsBackend backend) {
// for default FS we still need to define namespace as otherwise it would
// interfere with rest of FS backends
diff --git a/src/main/java/org/eclipse/jgit/lfs/server/fs/LfsFsContentServlet.java b/src/main/java/org/eclipse/jgit/lfs/server/fs/LfsFsContentServlet.java
new file mode 100644
index 0000000..b523795
--- /dev/null
+++ b/src/main/java/org/eclipse/jgit/lfs/server/fs/LfsFsContentServlet.java
@@ -0,0 +1,128 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.eclipse.jgit.lfs.server.fs;
+// TODO move file back to com.googlesource.gerrit.plugin.lfs.fs package when
+// https://git.eclipse.org/r/#/c/84933/ is picked up by gerrit
+
+import static com.googlesource.gerrit.plugins.lfs.fs.LocalLargeFileRepository.DOWNLOAD;
+import static com.googlesource.gerrit.plugins.lfs.fs.LocalLargeFileRepository.UPLOAD;
+import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import com.googlesource.gerrit.plugins.lfs.fs.LfsFsRequestAuthorizer;
+import com.googlesource.gerrit.plugins.lfs.fs.LocalLargeFileRepository;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.eclipse.jgit.lfs.server.internal.LfsServerText;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class LfsFsContentServlet extends FileLfsServlet {
+ public interface Factory {
+ LfsFsContentServlet create(LocalLargeFileRepository largeFileRepository);
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private final LfsFsRequestAuthorizer authorizer;
+ private final LocalLargeFileRepository repository;
+ private final long timeout;
+
+ @Inject
+ public LfsFsContentServlet(LfsFsRequestAuthorizer authorizer,
+ @Assisted LocalLargeFileRepository repository) {
+ super(repository, 0);
+ this.authorizer = authorizer;
+ this.repository = repository;
+ this.timeout = 0;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ AnyLongObjectId obj = getObjectToTransfer(req, rsp);
+ if (obj == null) {
+ return;
+ }
+
+ if (repository.getSize(obj) == -1) {
+ sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat
+ .format(LfsServerText.get().objectNotFound, obj.getName()));
+ return;
+ }
+
+ if (!authorizer.verifyAgainstToken(req.getHeader(HDR_AUTHORIZATION),
+ DOWNLOAD, obj)) {
+ sendError(rsp, HttpStatus.SC_UNAUTHORIZED, MessageFormat.format(
+ LfsServerText.get().failedToCalcSignature, "Invalid authorization token"));
+ return;
+ }
+
+ AsyncContext context = req.startAsync();
+ context.setTimeout(timeout);
+ rsp.getOutputStream().setWriteListener(
+ new ObjectDownloadListener(repository, context, rsp, obj));
+ }
+
+ @Override
+ protected void doPut(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ AnyLongObjectId id = getObjectToTransfer(req, rsp);
+ if (id == null) {
+ return;
+ }
+
+ if (!authorizer.verifyAgainstToken(
+ req.getHeader(HDR_AUTHORIZATION), UPLOAD, id)) {
+ sendError(rsp, HttpStatus.SC_UNAUTHORIZED,
+ MessageFormat.format(LfsServerText.get().failedToCalcSignature,
+ "Invalid authorization token"));
+ return;
+ }
+
+ AsyncContext context = req.startAsync();
+ context.setTimeout(timeout);
+ req.getInputStream().setReadListener(
+ new ObjectUploadListener(repository, context, req, rsp, id));
+ }
+
+ private AnyLongObjectId getObjectToTransfer(HttpServletRequest req,
+ HttpServletResponse rsp) throws IOException {
+ String info = req.getPathInfo();
+ if (info.length() != 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH) {
+ sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY,
+ MessageFormat.format(LfsServerText.get().invalidPathInfo, info));
+ return null;
+ }
+ try {
+ return LongObjectId.fromString(info.substring(1, 65));
+ } catch (InvalidLongObjectIdException e) {
+ sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, e.getMessage());
+ return null;
+ }
+ }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 602d566..7cf8603 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -107,6 +107,14 @@
: The directory in which to store data files. If not specified, defaults to
the plugin's data folder: `$GERRIT_SITE/data/@PLUGIN@`.
+fs.expirationSeconds
+: Validity, in seconds, of authentication token for signed requests.
+Gerrit's LFS protocol handler signs requests to be issued by the git-lfs
+extension. This way the git-lfs extension doesn't need any credentials to
+access objects in the FS bucket. Validity of these request signatures expires
+after this period.
+: Default is `10` seconds.
+
### <a id="lfs-s3-backend"></a>Section `s3` - default S3 backend
The following configuration options are only used when the backend is `s3`.
@@ -124,7 +132,7 @@
[Amazon S3 storage class] used for storing large objects.
: Default is `REDUCED_REDUNDANCY`
-s3.expiration
+s3.expirationSeconds
: Expiration in seconds of validity of signed requests. Gerrit's LFS protocol
handler signs requests to be issued by the git-lfs extension with the configured
`accessKey` and `secretKey`. This way the git-lfs extension doesn't need
diff --git a/src/test/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizerTest.java b/src/test/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizerTest.java
new file mode 100644
index 0000000..8cab1a3
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizerTest.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.lfs.fs;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.lfs.fs.LfsFsRequestAuthorizer.DATE_TIME;
+import static org.eclipse.jgit.lfs.lib.LongObjectId.zeroId;
+
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.junit.Test;
+
+public class LfsFsRequestAuthorizerTest {
+ private final LfsFsRequestAuthorizer auth = new LfsFsRequestAuthorizer();
+
+ @Test
+ public void testExpiredTime() throws Exception {
+ DateTime now = DateTime.now().toDateTime(DateTimeZone.UTC);
+ // test that even 1s expiration is enough
+ assertThat(auth.onTime(DATE_TIME.print(now.minusSeconds(1)), "o", "id"))
+ .isFalse();
+ }
+
+ @Test
+ public void testOnTime() throws Exception {
+ DateTime now = DateTime.now().toDateTime(DateTimeZone.UTC);
+ // if there is at least 1s before there is no timeout
+ assertThat(auth.onTime(DATE_TIME.print(now.plusSeconds(1)), "o", "id"))
+ .isTrue();
+ }
+
+ @Test
+ public void testVerifyAgainstMissingToken() throws Exception {
+ assertThat(auth.verifyAgainstToken("", "o", zeroId())).isFalse();
+ assertThat(auth.verifyAgainstToken(null, "o", zeroId())).isFalse();
+ }
+
+ @Test
+ public void testVerifyAgainstToken() throws Exception {
+ String token = auth.generateToken("o", zeroId(), 1);
+ assertThat(auth.verifyAgainstToken(token, "o", zeroId())).isTrue();
+ }
+
+ @Test
+ public void testVerifyAgainstInvalidToken() throws Exception {
+ String token = auth.generateToken("o", zeroId(), 1);
+ // replace 1st and 2nd token letters with each other
+ assertThat(auth.verifyAgainstToken(
+ token.substring(1, 2) + token.substring(0, 1) + token.substring(2), "o",
+ zeroId())).isFalse();
+ }
+
+ @Test
+ public void testVerifyAgainstDifferentOperation() throws Exception {
+ String token = auth.generateToken("o", zeroId(), 1);
+ assertThat(auth.verifyAgainstToken(token, "p", zeroId())).isFalse();
+ }
+
+ @Test
+ public void testVerifyAgainstDifferentObjectId() throws Exception {
+ String token = auth.generateToken("o", zeroId(), 1);
+ assertThat(auth.verifyAgainstToken(token, "o",
+ LongObjectId.fromString(
+ "123456789012345678901234567890"
+ + "123456789012345678901234567890"
+ + "1234"))).isFalse();
+ }
+
+ @Test
+ public void testVerifyAgainstExpiredToken() throws Exception {
+ // generate already expired token
+ String token = auth.generateToken("o", zeroId(), -1);
+ assertThat(auth.verifyAgainstToken(token, "o", zeroId())).isFalse();
+ }
+}