From d43080a3eafc42bf499ecde241351a4e11705f51 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Sat, 19 Oct 2019 13:24:58 +0200 Subject: [PATCH] Extract common functionality --- .../sonia/scm/web/lfs/ExpiringAction.java | 19 +++++ .../scm/{ => web/lfs}/LFSAuthCommand.java | 74 +++++-------------- .../scm/web/lfs/ScmBlobLfsRepository.java | 33 +++------ 3 files changed, 48 insertions(+), 78 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ExpiringAction.java rename scm-plugins/scm-git-plugin/src/main/java/sonia/scm/{ => web/lfs}/LFSAuthCommand.java (52%) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ExpiringAction.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ExpiringAction.java new file mode 100644 index 0000000000..b68640f24b --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ExpiringAction.java @@ -0,0 +1,19 @@ +package sonia.scm.web.lfs; + +import org.eclipse.jgit.lfs.server.Response; +import sonia.scm.security.AccessToken; + +import java.text.SimpleDateFormat; +import java.util.Collections; + +@SuppressWarnings({"squid:S00116"}) +// This class is used for json serialization, only +class ExpiringAction extends Response.Action { + public final String expires_at; + + ExpiringAction(String href, AccessToken accessToken) { + this.expires_at = new SimpleDateFormat("yyyy-MM-dd'T'HH:MM:ss'Z'").format(accessToken.getExpiration()); + this.href = href; + this.header = Collections.singletonMap("Authorization", "Bearer " + accessToken.compact()); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/LFSAuthCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java similarity index 52% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/LFSAuthCommand.java rename to scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java index 17a05ebe60..9bf3d3a4f9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/LFSAuthCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LFSAuthCommand.java @@ -1,11 +1,11 @@ -package sonia.scm; +package sonia.scm.web.lfs; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import sonia.scm.config.ScmConfiguration; import sonia.scm.plugin.Extension; import sonia.scm.protocolcommand.CommandInterpreter; import sonia.scm.protocolcommand.CommandInterpreterFactory; +import sonia.scm.protocolcommand.RepositoryContext; import sonia.scm.protocolcommand.RepositoryContextResolver; import sonia.scm.protocolcommand.ScmCommandProtocol; import sonia.scm.protocolcommand.git.GitRepositoryContextResolver; @@ -14,12 +14,7 @@ import sonia.scm.security.AccessToken; import sonia.scm.security.AccessTokenBuilderFactory; import javax.inject.Inject; -import javax.xml.bind.annotation.XmlElement; import java.io.ByteArrayOutputStream; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -30,13 +25,16 @@ public class LFSAuthCommand implements CommandInterpreterFactory { private final AccessTokenBuilderFactory tokenBuilderFactory; private final GitRepositoryContextResolver gitRepositoryContextResolver; - private final ScmConfiguration configuration; + private final ObjectMapper objectMapper; + private final String baseUrl; @Inject public LFSAuthCommand(AccessTokenBuilderFactory tokenBuilderFactory, GitRepositoryContextResolver gitRepositoryContextResolver, ScmConfiguration configuration) { this.tokenBuilderFactory = tokenBuilderFactory; this.gitRepositoryContextResolver = gitRepositoryContextResolver; - this.configuration = configuration; + + objectMapper = new ObjectMapper(); + baseUrl = configuration.getBaseUrl(); } @Override @@ -44,69 +42,33 @@ public class LFSAuthCommand implements CommandInterpreterFactory { 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]}; } @Override public ScmCommandProtocol getProtocolHandler() { return (context, repositoryContext) -> { - AccessToken accessToken = tokenBuilderFactory.create().expiresIn(5, TimeUnit.MINUTES).build(); - - Repository repository = repositoryContext.getRepository(); - String url = format("%s/repo/%s/%s.git/info/lfs/", configuration.getBaseUrl(), repository.getNamespace(), repository.getName()); - - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JaxbAnnotationModule()); - objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:MM:ss'Z'")); - LfsAuthResponse response = new LfsAuthResponse(url, new LfsAuthHeader(accessToken.compact()), Instant.now().plus(5, ChronoUnit.MINUTES)); + ExpiringAction response = createResponse(repositoryContext); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); objectMapper.writeValue(buffer, response); context.getOutputStream().write(buffer.toString().getBytes()); }; } + 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()); + + return new ExpiringAction(url, accessToken); + } + @Override public RepositoryContextResolver getRepositoryContextResolver() { return gitRepositoryContextResolver; } }) : Optional.empty(); } - - private class LfsAuthResponse { - private final String href; - private final LfsAuthHeader header; - @XmlElement(name = "expires_at") - private final Date expiresAt; - - public LfsAuthResponse(String href, LfsAuthHeader header, Instant expiresAt) { - this.href = href; - this.header = header; - this.expiresAt = Date.from(expiresAt); - } - - public String getHref() { - return href; - } - - public LfsAuthHeader getHeader() { - return header; - } - - public Date getExpiresAt() { - return expiresAt; - } - } - - private class LfsAuthHeader { - @XmlElement(name = "Authorization") - private final String authorization; - - public LfsAuthHeader(String authorization) { - this.authorization = authorization; - } - - public String getAuthorization() { - return "Bearer " + authorization; - } - } } 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 a67953ee11..ee1fb15d94 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 @@ -12,11 +12,6 @@ import sonia.scm.store.Blob; import sonia.scm.store.BlobStore; import java.io.IOException; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.concurrent.TimeUnit; /** @@ -38,12 +33,14 @@ public class ScmBlobLfsRepository implements LargeFileRepository { private final String baseUri; private final Repository repository; + private AccessToken accessToken; + /** * Creates a {@link ScmBlobLfsRepository} for the provided repository. * - * @param repository + * @param repository The current scm repository this LFS repository is used for. * @param blobStore The SCM Blobstore used for this @{@link LargeFileRepository}. - * @param tokenBuilderFactory + * @param tokenBuilderFactory The token builder used to create short lived access tokens. * @param baseUri This URI is used to determine the actual URI for Upload / Download. Must be full URI (or */ @@ -97,26 +94,18 @@ public class ScmBlobLfsRepository implements LargeFileRepository { //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. - ExpiringAction a = new ExpiringAction(); - a.href = baseUri + id.getName(); + return new ExpiringAction(baseUri + id.getName(), getAccessToken(scope)); + } - AccessToken accessToken = - tokenBuilderFactory + private AccessToken getAccessToken(Scope scope) { + if (accessToken == null) { + accessToken = tokenBuilderFactory .create() .expiresIn(5, TimeUnit.MINUTES) .scope(scope) .build(); - a.header = new HashMap<>(); - a.header.put("Authorization", "Bearer " + accessToken.compact()); - Instant expire = Instant.now().plus(5, ChronoUnit.MINUTES); - a.expires_at = new SimpleDateFormat("yyyy-MM-dd'T'HH:MM:ss'Z'").format(Date.from(expire)); - - return a; + } + return accessToken; } - @SuppressWarnings({"squid:ClassVariableVisibilityCheck", "squid:S00116"}) - // This class is used for json serialization, only - private static class ExpiringAction extends Response.Action { - public String expires_at; - } }