From c067ce1a5c9d77b523ffd2fd2b018fa7a30997f1 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Sat, 19 Oct 2019 13:47:59 +0200 Subject: [PATCH] Extract token generation --- .../sonia/scm/web/lfs/LFSAuthCommand.java | 73 +++++++++++-------- .../scm/web/lfs/LfsAccessTokenFactory.java | 43 +++++++++++ .../scm/web/lfs/ScmBlobLfsRepository.java | 44 +++++------ .../web/lfs/servlet/LfsServletFactory.java | 14 ++-- 4 files changed, 108 insertions(+), 66 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsAccessTokenFactory.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java index 9bf3d3a4f9..860c7495e8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java @@ -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,36 +37,49 @@ public class LFSAuthCommand implements CommandInterpreterFactory { @Override public Optional canHandle(String command) { - return command.startsWith("git-lfs-authenticate") ? Optional.of(new CommandInterpreter() { - @Override - public String[] getParsedArgs() { - // we are interested only in the 'repo' argument, so we discard the rest - return new String[] {command.split("\\s+")[1]}; - } + if (command.startsWith("git-lfs-authenticate")) { + return Optional.of(new LfsAuthCommandInterpreter(command)); + } else { + return Optional.empty(); + } + } - @Override - public ScmCommandProtocol getProtocolHandler() { - return (context, repositoryContext) -> { - ExpiringAction response = createResponse(repositoryContext); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - objectMapper.writeValue(buffer, response); - context.getOutputStream().write(buffer.toString().getBytes()); - }; - } + private class LfsAuthCommandInterpreter implements CommandInterpreter { - private ExpiringAction createResponse(RepositoryContext repositoryContext) { - AccessToken accessToken = tokenBuilderFactory.create().expiresIn(5, TimeUnit.MINUTES).build(); + private final String command; - Repository repository = repositoryContext.getRepository(); - String url = format("%s/repo/%s/%s.git/info/lfs/", baseUrl, repository.getNamespace(), repository.getName()); + public LfsAuthCommandInterpreter(String command) { + this.command = command; + } - return new ExpiringAction(url, accessToken); - } + @Override + public String[] getParsedArgs() { + // we are interested only in the 'repo' argument, so we discard the rest + return new String[]{command.split("\\s+")[1]}; + } - @Override - public RepositoryContextResolver getRepositoryContextResolver() { - return gitRepositoryContextResolver; - } - }) : Optional.empty(); + @Override + public ScmCommandProtocol getProtocolHandler() { + return (context, repositoryContext) -> { + ExpiringAction response = createResponse(repositoryContext); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + objectMapper.writeValue(buffer, response); + context.getOutputStream().write(buffer.toString().getBytes()); + }; + } + + private ExpiringAction createResponse(RepositoryContext repositoryContext) { + 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); + } + + @Override + public RepositoryContextResolver getRepositoryContextResolver() { + return gitRepositoryContextResolver; + } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsAccessTokenFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsAccessTokenFactory.java new file mode 100644 index 0000000000..d7c7b70234 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsAccessTokenFactory.java @@ -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(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java index ee1fb15d94..2ce819e766 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java @@ -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; - } - } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java index bfc8a12ba8..cc79d8e5c4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java @@ -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); }