add push permissions to read access token

This is required because the read access token is used to obtain the write access token.
This commit is contained in:
Sebastian Sdorra
2019-10-22 10:13:46 +02:00
parent 5185a68eeb
commit e885d130be
4 changed files with 57 additions and 18 deletions

View File

@@ -2,6 +2,8 @@ package sonia.scm.web.lfs;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.plugin.Extension;
import sonia.scm.protocolcommand.CommandInterpreter;
@@ -22,6 +24,8 @@ import static java.lang.String.format;
@Extension
public class LFSAuthCommand implements CommandInterpreterFactory {
private static final Logger LOG = LoggerFactory.getLogger(LFSAuthCommand.class);
private static final String LFS_INFO_URL_PATTERN = "%s/repo/%s/%s.git/info/lfs/";
private final LfsAccessTokenFactory tokenFactory;
@@ -41,6 +45,7 @@ public class LFSAuthCommand implements CommandInterpreterFactory {
@Override
public Optional<CommandInterpreter> canHandle(String command) {
if (command.startsWith("git-lfs-authenticate")) {
LOG.trace("create command for input: {}", command);
return Optional.of(new LfsAuthCommandInterpreter(command));
} else {
return Optional.empty();
@@ -65,6 +70,8 @@ public class LFSAuthCommand implements CommandInterpreterFactory {
public ScmCommandProtocol getProtocolHandler() {
return (context, repositoryContext) -> {
ExpiringAction response = createResponseObject(repositoryContext);
// we buffer the response and write it with a single write,
// because otherwise the ssh connection is not closed
String buffer = serializeResponse(response);
context.getOutputStream().write(buffer.getBytes(Charsets.UTF_8));
};

View File

@@ -1,16 +1,24 @@
package sonia.scm.web.lfs;
import com.github.sdorra.ssp.PermissionCheck;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.Permission;
import sonia.scm.security.Scope;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class LfsAccessTokenFactory {
private static final Logger LOG = LoggerFactory.getLogger(LfsAccessTokenFactory.class);
private final AccessTokenBuilderFactory tokenBuilderFactory;
@Inject
@@ -19,29 +27,44 @@ public class LfsAccessTokenFactory {
}
AccessToken createReadAccessToken(Repository repository) {
RepositoryPermissions.pull(repository).check();
RepositoryPermissions.read(repository).check();
return createToken(
Scope.valueOf(
RepositoryPermissions.read(repository).asShiroString(),
RepositoryPermissions.pull(repository).asShiroString()));
PermissionCheck read = RepositoryPermissions.read(repository);
read.check();
PermissionCheck pull = RepositoryPermissions.pull(repository);
pull.check();
List<String> permissions = new ArrayList<>();
permissions.add(read.asShiroString());
permissions.add(pull.asShiroString());
PermissionCheck push = RepositoryPermissions.push(repository);
if (push.isPermitted()) {
// we have to add push permissions,
// because this token is also used to obtain the write access token
permissions.add(push.asShiroString());
}
return createToken(Scope.valueOf(permissions));
}
AccessToken createWriteAccessToken(Repository repository) {
RepositoryPermissions.read(repository).check();
RepositoryPermissions.pull(repository).check();
RepositoryPermissions.push(repository).check();
return createToken(
Scope.valueOf(
RepositoryPermissions.read(repository).asShiroString(),
RepositoryPermissions.pull(repository).asShiroString(),
RepositoryPermissions.push(repository).asShiroString()));
PermissionCheck read = RepositoryPermissions.read(repository);
read.check();
PermissionCheck pull = RepositoryPermissions.pull(repository);
pull.check();
PermissionCheck push = RepositoryPermissions.push(repository);
push.check();
return createToken(Scope.valueOf(read.asShiroString(), pull.asShiroString(), push.asShiroString()));
}
private AccessToken createToken(Scope scope) {
LOG.trace("create access token with scope: {}", scope);
return tokenBuilderFactory
.create()
.expiresIn(1, TimeUnit.MINUTES)
.expiresIn(5, TimeUnit.MINUTES)
.scope(scope)
.build();
}

View File

@@ -2,14 +2,13 @@ package sonia.scm.web.lfs;
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
import org.eclipse.jgit.lfs.server.LargeFileRepository;
import org.eclipse.jgit.lfs.server.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.security.AccessToken;
import sonia.scm.store.Blob;
import sonia.scm.store.BlobStore;
import java.io.IOException;
/**
* This LargeFileRepository is used for jGit-Servlet implementation. Under the jgit LFS Servlet hood, the
* SCM-Repository API is used to implement the Repository.
@@ -19,6 +18,8 @@ import java.io.IOException;
*/
public class ScmBlobLfsRepository implements LargeFileRepository {
private static final Logger LOG = LoggerFactory.getLogger(ScmBlobLfsRepository.class);
private final BlobStore blobStore;
private final LfsAccessTokenFactory tokenFactory;
@@ -54,6 +55,7 @@ public class ScmBlobLfsRepository implements LargeFileRepository {
@Override
public ExpiringAction getDownloadAction(AnyLongObjectId id) {
if (accessToken == null) {
LOG.trace("create access token to download lfs object {} from repository {}", id, repository.getNamespaceAndName());
accessToken = tokenFactory.createReadAccessToken(repository);
}
return getAction(id, accessToken);
@@ -62,6 +64,7 @@ public class ScmBlobLfsRepository implements LargeFileRepository {
@Override
public ExpiringAction getUploadAction(AnyLongObjectId id, long size) {
if (accessToken == null) {
LOG.trace("create access token to upload lfs object {} to repository {}", id, repository.getNamespaceAndName());
accessToken = tokenFactory.createWriteAccessToken(repository);
}
return getAction(id, accessToken);

View File

@@ -4,6 +4,8 @@ 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.store.BlobStore;
import sonia.scm.util.HttpUtil;
@@ -26,6 +28,8 @@ import javax.servlet.http.HttpServletRequest;
@Singleton
public class LfsServletFactory {
private static final Logger LOG = LoggerFactory.getLogger(LfsServletFactory.class);
private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final LfsAccessTokenFactory tokenFactory;
@@ -43,6 +47,7 @@ public class LfsServletFactory {
* @return The {@link LfsProtocolServlet} to provide the LFS Batch API for a SCM Repository.
*/
public LfsProtocolServlet createProtocolServletFor(Repository repository, HttpServletRequest request) {
LOG.trace("create lfs protocol servlet for repository {}", repository.getNamespaceAndName());
BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
String baseUri = buildBaseUri(repository, request);
@@ -58,6 +63,7 @@ public class LfsServletFactory {
* @return The {@link FileLfsServlet} to provide the LFS Upload / Download API for a SCM Repository.
*/
public HttpServlet createFileLfsServletFor(Repository repository, HttpServletRequest request) {
LOG.trace("create lfs file servlet for repository {}", repository.getNamespaceAndName());
return new ScmFileTransferServlet(lfsBlobStoreFactory.getLfsBlobStore(repository));
}