Add support for "expires_in" field in authentication response
Per the documentation [1], the "expires_in" field is preferred over
the "expires_at" field.
Refactor the implementation to hold the instant at which the token was
issued, along with the expiry period, and calculate the "expires_at"
value on demand.
[1] https://github.com/git-lfs/git-lfs/blob/master/docs/api/authentication.md
Change-Id: I14a05e5d71254ecacf36655a341d60febed8531e
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/AuthInfo.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/AuthInfo.java
index 187ce6f..4336ed5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/AuthInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/AuthInfo.java
@@ -14,12 +14,48 @@
package com.googlesource.gerrit.plugins.lfs;
-public class AuthInfo {
- public final String authToken;
- public final String expiresAt;
+import java.time.Instant;
- public AuthInfo(String authToken, String expiresAt) {
+public class AuthInfo {
+ private final String authToken;
+ private final Instant issued;
+ private final Long expiresIn;
+
+ /**
+ * @param authToken token
+ * @param issued time at which the token was issued
+ * @param expiresIn expiry duration in seconds
+ */
+ public AuthInfo(String authToken, Instant issued, Long expiresIn) {
this.authToken = authToken;
- this.expiresAt = expiresAt;
+ this.issued = issued;
+ this.expiresIn = expiresIn;
+ }
+
+ /**
+ * Get the auth token
+ *
+ * @return the auth token
+ */
+ public String authToken() {
+ return authToken;
+ }
+
+ /**
+ * Get a formatted timestamp of the time at which this authentication expires
+ *
+ * @return timestamp
+ */
+ public String expiresAt() {
+ return LfsDateTime.format(issued.plusSeconds(expiresIn));
+ }
+
+ /**
+ * Get the time duration after which this authentication expires
+ *
+ * @return the time duration in seconds
+ */
+ public Long expiresIn() {
+ return expiresIn;
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/ExpiringAction.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/ExpiringAction.java
index 7d43d04..d3255a1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/ExpiringAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/ExpiringAction.java
@@ -21,10 +21,12 @@
public class ExpiringAction extends Response.Action {
public final String expiresAt;
+ public final Long expiresIn;
public ExpiringAction(String href, AuthInfo info) {
this.href = href;
- this.header = Collections.singletonMap(HDR_AUTHORIZATION, info.authToken);
- this.expiresAt = info.expiresAt;
+ this.header = Collections.singletonMap(HDR_AUTHORIZATION, info.authToken());
+ this.expiresAt = info.expiresAt();
+ this.expiresIn = info.expiresIn();
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
index 278447c..5f3b9c2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
@@ -16,6 +16,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
+import java.time.Instant;
import java.util.List;
import java.util.Optional;
@@ -55,27 +56,26 @@
}
public boolean verify() {
- return onTime(token.expiresAt) && verifyTokenValues();
+ return onTime(Instant.now()) && verifyTokenValues();
}
protected abstract boolean verifyTokenValues();
- static boolean onTime(String dateTime) {
- return LfsDateTime.now().compareTo(dateTime) <= 0;
+ public boolean onTime(Instant when) {
+ return when.isBefore(token.issued.plusSeconds(token.expiresIn));
}
}
- public final String expiresAt;
+ public final Instant issued;
+ public final Long expiresIn;
- protected LfsAuthToken(int expirationSeconds) {
- this(timeout(expirationSeconds));
+ protected LfsAuthToken(Instant issued, Long expiresIn) {
+ this.issued = issued;
+ this.expiresIn = expiresIn;
}
- protected LfsAuthToken(String expiresAt) {
- this.expiresAt = expiresAt;
- }
-
- static String timeout(int expirationSeconds) {
- return LfsDateTime.now(expirationSeconds);
+ protected LfsAuthToken(String issued, Long expiresIn) {
+ this.issued = Instant.parse(issued);
+ this.expiresIn = expiresIn;
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java
index ccedee5..4a1936c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java
@@ -29,10 +29,6 @@
return FORMAT.format(Instant.now());
}
- public static String now(int secondsToAdd) {
- return FORMAT.format(Instant.now().plusSeconds(secondsToAdd));
- }
-
public static String format(Instant instant) {
return FORMAT.format(instant);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsSshRequestAuthorizer.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsSshRequestAuthorizer.java
index 2637a12..12c5552 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsSshRequestAuthorizer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsSshRequestAuthorizer.java
@@ -17,6 +17,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -26,8 +27,8 @@
@Singleton
class LfsSshRequestAuthorizer {
static class SshAuthInfo extends AuthInfo {
- SshAuthInfo(String authToken, String expiresAt) {
- super(SSH_AUTH_PREFIX + authToken, expiresAt);
+ SshAuthInfo(String authToken, Instant issued, Long expiresIn) {
+ super(SSH_AUTH_PREFIX + authToken, issued, expiresIn);
}
}
@@ -36,12 +37,12 @@
static final String SSH_AUTH_PREFIX = "Ssh: ";
private final Processor processor;
- private final int expirationSeconds;
+ private final Long expiresIn;
@Inject
LfsSshRequestAuthorizer(Processor processor, LfsConfigurationFactory configFactory) {
this.processor = processor;
- int timeout = DEFAULT_SSH_TIMEOUT;
+ long timeout = DEFAULT_SSH_TIMEOUT;
try {
timeout =
configFactory
@@ -53,13 +54,13 @@
DEFAULT_SSH_TIMEOUT,
e);
}
- this.expirationSeconds = timeout;
+ this.expiresIn = timeout;
}
SshAuthInfo generateAuthInfo(CurrentUser user, String project, String operation) {
LfsSshAuthToken token =
- new LfsSshAuthToken(user.getUserName(), project, operation, expirationSeconds);
- return new SshAuthInfo(processor.serialize(token), token.expiresAt);
+ new LfsSshAuthToken(user.getUserName(), project, operation, Instant.now(), expiresIn);
+ return new SshAuthInfo(processor.serialize(token), token.issued, token.expiresIn);
}
Optional<String> getUserFromValidToken(String authToken, String project, String operation) {
@@ -89,18 +90,24 @@
values.add(token.user);
values.add(token.project);
values.add(token.operation);
- values.add(token.expiresAt);
+ values.add(LfsDateTime.format(token.issued));
+ values.add(String.valueOf(token.expiresIn));
return values;
}
@Override
protected Optional<LfsSshAuthToken> createToken(List<String> values) {
- if (values.size() != 4) {
+ if (values.size() != 5) {
return Optional.empty();
}
return Optional.of(
- new LfsSshAuthToken(values.get(0), values.get(1), values.get(2), values.get(3)));
+ new LfsSshAuthToken(
+ values.get(0),
+ values.get(1),
+ values.get(2),
+ values.get(3),
+ Long.valueOf(values.get(4))));
}
}
@@ -125,15 +132,15 @@
private final String project;
private final String operation;
- LfsSshAuthToken(String user, String project, String operation, int expirationSeconds) {
- super(expirationSeconds);
+ LfsSshAuthToken(String user, String project, String operation, Instant issued, Long expiresIn) {
+ super(issued, expiresIn);
this.user = user;
this.project = project;
this.operation = operation;
}
- LfsSshAuthToken(String user, String project, String operation, String expiresAt) {
- super(expiresAt);
+ LfsSshAuthToken(String user, String project, String operation, String issued, Long expiresIn) {
+ super(issued, expiresIn);
this.user = user;
this.project = project;
this.operation = operation;
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
index f27eb62..7e982b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizer.java
@@ -19,6 +19,8 @@
import com.googlesource.gerrit.plugins.lfs.AuthInfo;
import com.googlesource.gerrit.plugins.lfs.LfsAuthToken;
import com.googlesource.gerrit.plugins.lfs.LfsCipher;
+import com.googlesource.gerrit.plugins.lfs.LfsDateTime;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -34,9 +36,10 @@
this.processor = processor;
}
- public AuthInfo generateAuthInfo(String operation, AnyLongObjectId id, int expirationSeconds) {
- LfsFsAuthToken token = new LfsFsAuthToken(operation, id, expirationSeconds);
- return new AuthInfo(processor.serialize(token), token.expiresAt);
+ public AuthInfo generateAuthInfo(
+ String operation, AnyLongObjectId id, Instant now, Long expiresIn) {
+ LfsFsAuthToken token = new LfsFsAuthToken(operation, id, now, expiresIn);
+ return new AuthInfo(processor.serialize(token), token.issued, token.expiresIn);
}
public boolean verifyAuthInfo(String authToken, String operation, AnyLongObjectId id) {
@@ -59,18 +62,23 @@
List<String> values = new ArrayList<>(3);
values.add(token.operation);
values.add(token.id.getName());
- values.add(token.expiresAt);
+ values.add(LfsDateTime.format(token.issued));
+ values.add(String.valueOf(token.expiresIn));
return values;
}
@Override
protected Optional<LfsFsAuthToken> createToken(List<String> values) {
- if (values.size() != 3) {
+ if (values.size() != 4) {
return Optional.empty();
}
return Optional.of(
- new LfsFsAuthToken(values.get(0), LongObjectId.fromString(values.get(1)), values.get(2)));
+ new LfsFsAuthToken(
+ values.get(0),
+ LongObjectId.fromString(values.get(1)),
+ values.get(2),
+ Long.valueOf(values.get(3))));
}
}
@@ -94,14 +102,14 @@
private final String operation;
private final AnyLongObjectId id;
- LfsFsAuthToken(String operation, AnyLongObjectId id, int expirationSeconds) {
- super(expirationSeconds);
+ LfsFsAuthToken(String operation, AnyLongObjectId id, Instant issued, Long expiresIn) {
+ super(issued, expiresIn);
this.operation = operation;
this.id = id;
}
- LfsFsAuthToken(String operation, AnyLongObjectId id, String expiresAt) {
- super(expiresAt);
+ LfsFsAuthToken(String operation, AnyLongObjectId id, String issued, Long expiresIn) {
+ super(issued, expiresIn);
this.operation = operation;
this.id = id;
}
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 4d897d4..477061e 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
@@ -32,6 +32,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Instant;
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
import org.eclipse.jgit.lfs.server.Response;
import org.eclipse.jgit.lfs.server.fs.FileLfsRepository;
@@ -46,7 +47,7 @@
private final String servletUrlPattern;
private final LfsFsRequestAuthorizer authorizer;
- private final int expirationSeconds;
+ private final Long expiresIn;
@Inject
LocalLargeFileRepository(
@@ -61,10 +62,11 @@
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);
+ this.expiresIn =
+ (long)
+ configFactory
+ .getGlobalConfig()
+ .getInt(backend.type.name(), backend.name, "expirationSeconds", DEFAULT_TIMEOUT);
}
public String getServletUrlPattern() {
@@ -74,14 +76,14 @@
@Override
public Response.Action getDownloadAction(AnyLongObjectId id) {
Response.Action action = super.getDownloadAction(id);
- AuthInfo authInfo = authorizer.generateAuthInfo(DOWNLOAD, id, expirationSeconds);
+ AuthInfo authInfo = authorizer.generateAuthInfo(DOWNLOAD, id, Instant.now(), expiresIn);
return new ExpiringAction(action.href, authInfo);
}
@Override
public Response.Action getUploadAction(AnyLongObjectId id, long size) {
Response.Action action = super.getUploadAction(id, size);
- AuthInfo authInfo = authorizer.generateAuthInfo(UPLOAD, id, expirationSeconds);
+ AuthInfo authInfo = authorizer.generateAuthInfo(UPLOAD, id, Instant.now(), expiresIn);
return new ExpiringAction(action.href, authInfo);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
index 01a4c72..f41307b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
@@ -15,7 +15,6 @@
package com.googlesource.gerrit.plugins.lfs;
import static com.google.common.truth.Truth.assertThat;
-import static com.googlesource.gerrit.plugins.lfs.LfsAuthToken.Verifier.onTime;
import java.time.Instant;
import java.util.ArrayList;
@@ -27,55 +26,41 @@
private final LfsCipher cipher = new LfsCipher();
@Test
- public void testExpiredTime() throws Exception {
- // test that even 1ms expiration is enough
- assertThat(onTime(LfsDateTime.format(now().minusMillis(1)))).isFalse();
- }
-
- @Test
- public void testOnTime() throws Exception {
- // if there is at least 1ms before there is no timeout
- assertThat(onTime(LfsDateTime.format(now().plusMillis(1)))).isTrue();
- }
-
- @Test
public void testTokenSerializationDeserialization() throws Exception {
TestTokenProessor processor = new TestTokenProessor(cipher);
- TestToken token = new TestToken(0);
+ TestToken token = new TestToken(Instant.now(), 0L);
String serialized = processor.serialize(token);
assertThat(serialized).isNotEmpty();
Optional<TestToken> deserialized = processor.deserialize(serialized);
assertThat(deserialized.isPresent()).isTrue();
- assertThat(token.expiresAt).isEqualTo(deserialized.get().expiresAt);
+ assertThat(token.issued).isEqualTo(deserialized.get().issued);
}
@Test
public void testTokenOnTime() throws Exception {
- TestToken token = new TestToken(1);
+ Instant when = Instant.now();
+ TestToken token = new TestToken(when, 1L);
TestTokenVerifier verifier = new TestTokenVerifier(token);
- assertThat(verifier.verify()).isTrue();
+ assertThat(verifier.onTime(when.plusMillis(999))).isTrue();
}
@Test
public void testTokenExpired() throws Exception {
- TestToken token = new TestToken(-1);
+ Instant when = Instant.now();
+ TestToken token = new TestToken(when, 1L);
TestTokenVerifier verifier = new TestTokenVerifier(token);
- assertThat(verifier.verify()).isFalse();
- }
-
- private Instant now() {
- return Instant.now();
+ assertThat(verifier.onTime(when.plusMillis(1001))).isFalse();
}
private class TestToken extends LfsAuthToken {
- TestToken(int expirationSeconds) {
- super(expirationSeconds);
+ TestToken(Instant now, Long expiresIn) {
+ super(now, expiresIn);
}
- TestToken(String expiresAt) {
- super(expiresAt);
+ TestToken(String expiresAt, Long expiresIn) {
+ super(expiresAt, expiresIn);
}
}
@@ -87,13 +72,14 @@
@Override
protected List<String> getValues(TestToken token) {
List<String> values = new ArrayList<>(2);
- values.add(token.expiresAt);
+ values.add(LfsDateTime.format(token.issued));
+ values.add(String.valueOf(token.expiresIn));
return values;
}
@Override
protected Optional<TestToken> createToken(List<String> values) {
- return Optional.of(new TestToken(values.get(0)));
+ return Optional.of(new TestToken(values.get(0), Long.valueOf(values.get(1))));
}
}
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
index 38c310f..8cd7225 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/lfs/fs/LfsFsRequestAuthorizerTest.java
@@ -20,6 +20,7 @@
import com.googlesource.gerrit.plugins.lfs.AuthInfo;
import com.googlesource.gerrit.plugins.lfs.LfsCipher;
import com.googlesource.gerrit.plugins.lfs.fs.LfsFsRequestAuthorizer.Processor;
+import java.time.Instant;
import org.eclipse.jgit.lfs.lib.LongObjectId;
import org.junit.Test;
@@ -29,22 +30,22 @@
@Test
public void testVerifyAuthInfo() throws Exception {
- AuthInfo info = auth.generateAuthInfo("o", zeroId(), 1);
- assertThat(auth.verifyAuthInfo(info.authToken, "o", zeroId())).isTrue();
+ AuthInfo info = auth.generateAuthInfo("o", zeroId(), Instant.now(), 1L);
+ assertThat(auth.verifyAuthInfo(info.authToken(), "o", zeroId())).isTrue();
}
@Test
public void testVerifyAgainstDifferentOperation() throws Exception {
- AuthInfo info = auth.generateAuthInfo("o", zeroId(), 1);
- assertThat(auth.verifyAuthInfo(info.authToken, "p", zeroId())).isFalse();
+ AuthInfo info = auth.generateAuthInfo("o", zeroId(), Instant.now(), 1L);
+ assertThat(auth.verifyAuthInfo(info.authToken(), "p", zeroId())).isFalse();
}
@Test
public void testVerifyAgainstDifferentObjectId() throws Exception {
- AuthInfo info = auth.generateAuthInfo("o", zeroId(), 1);
+ AuthInfo info = auth.generateAuthInfo("o", zeroId(), Instant.now(), 1L);
assertThat(
auth.verifyAuthInfo(
- info.authToken,
+ info.authToken(),
"o",
LongObjectId.fromString(
"123456789012345678901234567890" + "123456789012345678901234567890" + "1234")))