Extract token generation

This commit is contained in:
Rene Pfeuffer
2019-10-19 13:47:59 +02:00
parent d43080a3ea
commit c067ce1a5c
4 changed files with 108 additions and 66 deletions

View File

@@ -11,26 +11,24 @@ import sonia.scm.protocolcommand.ScmCommandProtocol;
import sonia.scm.protocolcommand.git.GitRepositoryContextResolver;
import sonia.scm.repository.Repository;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import javax.inject.Inject;
import java.io.ByteArrayOutputStream;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static java.lang.String.format;
@Extension
public class LFSAuthCommand implements CommandInterpreterFactory {
private final AccessTokenBuilderFactory tokenBuilderFactory;
private final LfsAccessTokenFactory tokenFactory;
private final GitRepositoryContextResolver gitRepositoryContextResolver;
private final ObjectMapper objectMapper;
private final String baseUrl;
@Inject
public LFSAuthCommand(AccessTokenBuilderFactory tokenBuilderFactory, GitRepositoryContextResolver gitRepositoryContextResolver, ScmConfiguration configuration) {
this.tokenBuilderFactory = tokenBuilderFactory;
public LFSAuthCommand(LfsAccessTokenFactory tokenFactory, GitRepositoryContextResolver gitRepositoryContextResolver, ScmConfiguration configuration) {
this.tokenFactory = tokenFactory;
this.gitRepositoryContextResolver = gitRepositoryContextResolver;
objectMapper = new ObjectMapper();
@@ -39,11 +37,25 @@ public class LFSAuthCommand implements CommandInterpreterFactory {
@Override
public Optional<CommandInterpreter> canHandle(String command) {
return command.startsWith("git-lfs-authenticate") ? Optional.of(new CommandInterpreter() {
if (command.startsWith("git-lfs-authenticate")) {
return Optional.of(new LfsAuthCommandInterpreter(command));
} else {
return Optional.empty();
}
}
private class LfsAuthCommandInterpreter implements CommandInterpreter {
private final String command;
public LfsAuthCommandInterpreter(String command) {
this.command = command;
}
@Override
public String[] getParsedArgs() {
// we are interested only in the 'repo' argument, so we discard the rest
return new String[] {command.split("\\s+")[1]};
return new String[]{command.split("\\s+")[1]};
}
@Override
@@ -57,10 +69,10 @@ public class LFSAuthCommand implements CommandInterpreterFactory {
}
private ExpiringAction createResponse(RepositoryContext repositoryContext) {
AccessToken accessToken = tokenBuilderFactory.create().expiresIn(5, TimeUnit.MINUTES).build();
Repository repository = repositoryContext.getRepository();
String url = format("%s/repo/%s/%s.git/info/lfs/", baseUrl, repository.getNamespace(), repository.getName());
AccessToken accessToken = tokenFactory.getReadAccessToken(repository);
return new ExpiringAction(url, accessToken);
}
@@ -69,6 +81,5 @@ public class LFSAuthCommand implements CommandInterpreterFactory {
public RepositoryContextResolver getRepositoryContextResolver() {
return gitRepositoryContextResolver;
}
}) : Optional.empty();
}
}

View File

@@ -0,0 +1,43 @@
package sonia.scm.web.lfs;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.Scope;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
public class LfsAccessTokenFactory {
private final AccessTokenBuilderFactory tokenBuilderFactory;
@Inject
LfsAccessTokenFactory(AccessTokenBuilderFactory tokenBuilderFactory) {
this.tokenBuilderFactory = tokenBuilderFactory;
}
AccessToken getReadAccessToken(Repository repository) {
return createToken(
Scope.valueOf(
RepositoryPermissions.read(repository).asShiroString(),
RepositoryPermissions.pull(repository).asShiroString()));
}
AccessToken getWriteAccessToken(Repository repository) {
return createToken(
Scope.valueOf(
RepositoryPermissions.read(repository).asShiroString(),
RepositoryPermissions.pull(repository).asShiroString(),
RepositoryPermissions.push(repository).asShiroString()));
}
private AccessToken createToken(Scope scope) {
return tokenBuilderFactory
.create()
.expiresIn(5, TimeUnit.MINUTES)
.scope(scope)
.build();
}
}

View File

@@ -4,15 +4,11 @@ import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
import org.eclipse.jgit.lfs.server.LargeFileRepository;
import org.eclipse.jgit.lfs.server.Response;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.Scope;
import sonia.scm.store.Blob;
import sonia.scm.store.BlobStore;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* This LargeFileRepository is used for jGit-Servlet implementation. Under the jgit LFS Servlet hood, the
@@ -24,7 +20,7 @@ import java.util.concurrent.TimeUnit;
public class ScmBlobLfsRepository implements LargeFileRepository {
private final BlobStore blobStore;
private final AccessTokenBuilderFactory tokenBuilderFactory;
private final LfsAccessTokenFactory tokenFactory;
/**
* This URI is used to determine the actual URI for Upload / Download. Must be full URI (or rewritable by reverse
@@ -33,6 +29,10 @@ public class ScmBlobLfsRepository implements LargeFileRepository {
private final String baseUri;
private final Repository repository;
/**
* A {@link ScmBlobLfsRepository} is created for either download or upload, not both. Therefore we can cache the
* access token and do not have to create them anew for each action.
*/
private AccessToken accessToken;
/**
@@ -40,27 +40,31 @@ public class ScmBlobLfsRepository implements LargeFileRepository {
*
* @param repository The current scm repository this LFS repository is used for.
* @param blobStore The SCM Blobstore used for this @{@link LargeFileRepository}.
* @param tokenBuilderFactory The token builder used to create short lived access tokens.
* @param tokenFactory The token builder for subsequent LFS requests.
* @param baseUri This URI is used to determine the actual URI for Upload / Download. Must be full URI (or
*/
public ScmBlobLfsRepository(Repository repository, BlobStore blobStore, AccessTokenBuilderFactory tokenBuilderFactory, String baseUri) {
public ScmBlobLfsRepository(Repository repository, BlobStore blobStore, LfsAccessTokenFactory tokenFactory, String baseUri) {
this.repository = repository;
this.blobStore = blobStore;
this.tokenBuilderFactory = tokenBuilderFactory;
this.tokenFactory = tokenFactory;
this.baseUri = baseUri;
}
@Override
public Response.Action getDownloadAction(AnyLongObjectId id) {
return getAction(id, Scope.valueOf(RepositoryPermissions.read(repository).asShiroString(), RepositoryPermissions.pull(repository).asShiroString()));
if (accessToken == null) {
accessToken = tokenFactory.getReadAccessToken(repository);
}
return getAction(id, accessToken);
}
@Override
public Response.Action getUploadAction(AnyLongObjectId id, long size) {
return getAction(id, Scope.valueOf(RepositoryPermissions.read(repository).asShiroString(), RepositoryPermissions.pull(repository).asShiroString(), RepositoryPermissions.push(repository).asShiroString()));
if (accessToken == null) {
accessToken = tokenFactory.getWriteAccessToken(repository);
}
return getAction(id, accessToken);
}
@Override
@@ -89,23 +93,11 @@ public class ScmBlobLfsRepository implements LargeFileRepository {
/**
* Constructs the Download / Upload actions to be supplied to the client.
*/
private Response.Action getAction(AnyLongObjectId id, Scope scope) {
private Response.Action getAction(AnyLongObjectId id, AccessToken token) {
//LFS protocol has to provide the information on where to put or get the actual content, i. e.
//the actual URI for up- and download.
return new ExpiringAction(baseUri + id.getName(), getAccessToken(scope));
return new ExpiringAction(baseUri + id.getName(), token);
}
private AccessToken getAccessToken(Scope scope) {
if (accessToken == null) {
accessToken = tokenBuilderFactory
.create()
.expiresIn(5, TimeUnit.MINUTES)
.scope(scope)
.build();
}
return accessToken;
}
}

View File

@@ -4,12 +4,10 @@ import com.google.common.annotations.VisibleForTesting;
import org.eclipse.jgit.lfs.server.LargeFileRepository;
import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
import org.eclipse.jgit.lfs.server.fs.FileLfsServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.store.BlobStore;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.lfs.LfsAccessTokenFactory;
import sonia.scm.web.lfs.LfsBlobStoreFactory;
import sonia.scm.web.lfs.ScmBlobLfsRepository;
@@ -28,15 +26,13 @@ import javax.servlet.http.HttpServletRequest;
@Singleton
public class LfsServletFactory {
private static final Logger logger = LoggerFactory.getLogger(LfsServletFactory.class);
private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final AccessTokenBuilderFactory tokenBuilderFactory;
private final LfsAccessTokenFactory tokenFactory;
@Inject
public LfsServletFactory(LfsBlobStoreFactory lfsBlobStoreFactory, AccessTokenBuilderFactory tokenBuilderFactory) {
public LfsServletFactory(LfsBlobStoreFactory lfsBlobStoreFactory, LfsAccessTokenFactory tokenFactory) {
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.tokenBuilderFactory = tokenBuilderFactory;
this.tokenFactory = tokenFactory;
}
/**
@@ -50,7 +46,7 @@ public class LfsServletFactory {
BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
String baseUri = buildBaseUri(repository, request);
LargeFileRepository largeFileRepository = new ScmBlobLfsRepository(repository, blobStore, tokenBuilderFactory, baseUri);
LargeFileRepository largeFileRepository = new ScmBlobLfsRepository(repository, blobStore, tokenFactory, baseUri);
return new ScmLfsProtocolServlet(largeFileRepository);
}