/** * This file is part of git-as-svn. It is subject to the license terms * in the LICENSE file found in the top-level directory of this distribution * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn, * including this file, may be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ package svnserver.ext.gitlfs.server; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.tmatesoft.svn.core.SVNException; import ru.bozaro.gitlfs.common.Constants; import ru.bozaro.gitlfs.common.data.Meta; import ru.bozaro.gitlfs.server.ContentManager; import ru.bozaro.gitlfs.server.ForbiddenError; import ru.bozaro.gitlfs.server.UnauthorizedError; import svnserver.auth.User; import svnserver.context.LocalContext; import svnserver.ext.gitlfs.LfsAuthHelper; import svnserver.ext.gitlfs.storage.LfsReader; import svnserver.ext.gitlfs.storage.LfsStorage; import svnserver.ext.gitlfs.storage.LfsWriter; import svnserver.ext.web.server.WebServer; import svnserver.repository.VcsAccess; import javax.servlet.http.HttpServletRequest; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Map; import java.util.Objects; /** * ContentManager wrapper for shared LFS server implementation. * * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com> */ public class LfsContentManager implements ContentManager { private final int tokenExpireSec; private final float tokenEnsureTime; @FunctionalInterface public interface Checker { void check(@NotNull User user, @Nullable String path) throws SVNException, IOException; } @NotNull private final LocalContext context; @NotNull private final LfsStorage storage; public LfsContentManager(@NotNull LocalContext context, @NotNull LfsStorage storage, int tokenExpireSec, float tokenEnsureTime) { this.context = context; this.storage = storage; this.tokenExpireSec = tokenExpireSec; this.tokenEnsureTime = tokenEnsureTime; } private User getAuthInfo(@NotNull HttpServletRequest request) { final WebServer server = context.getShared().sure(WebServer.class); final User user = server.getAuthInfo(request.getHeader(Constants.HEADER_AUTHORIZATION), Math.round(tokenExpireSec * tokenEnsureTime)); return user == null ? User.getAnonymous() : user; } public int getTokenExpireSec() { return tokenExpireSec; } @NotNull Map<String, String> createHeader(@NotNull HttpServletRequest request, @NotNull User user) throws IOException { final String auth = request.getHeader(Constants.HEADER_AUTHORIZATION); if (auth == null) { return Collections.emptyMap(); } if (auth.startsWith(WebServer.AUTH_TOKEN)) { return ImmutableMap.<String, String>builder() .put(Constants.HEADER_AUTHORIZATION, auth) .build(); } else { return LfsAuthHelper.createTokenHeader(context.getShared(), user, LfsAuthHelper.getExpire(tokenExpireSec)); } } @NotNull @Override public Downloader checkDownloadAccess(@NotNull HttpServletRequest request) throws IOException, ForbiddenError, UnauthorizedError { final VcsAccess access = context.sure(VcsAccess.class); final User user = checkAccess(request, access::checkRead); final Map<String, String> header = createHeader(request, user); return new Downloader() { @NotNull @Override public InputStream openObject(@NotNull String hash) throws IOException { final LfsReader reader = storage.getReader(LfsStorage.OID_PREFIX + hash); if (reader == null) { throw new FileNotFoundException(hash); } return reader.openStream(); } @Nullable @Override public InputStream openObjectGzipped(@NotNull String hash) throws IOException { final LfsReader reader = storage.getReader(LfsStorage.OID_PREFIX + hash); if (reader == null) { throw new FileNotFoundException(hash); } return reader.openGzipStream(); } @NotNull @Override public Map<String, String> createHeader(@NotNull Map<String, String> defaultHeader) { return header; } }; } @NotNull @Override public Uploader checkUploadAccess(@NotNull HttpServletRequest request) throws IOException, ForbiddenError, UnauthorizedError { final VcsAccess access = context.sure(VcsAccess.class); final User user = checkAccess(request, access::checkWrite); final Map<String, String> header = createHeader(request, user); return new Uploader() { @Override public void saveObject(@NotNull Meta meta, @NotNull InputStream content) throws IOException { try (final LfsWriter writer = storage.getWriter(Objects.requireNonNull(user))) { ByteStreams.copy(content, writer); writer.finish(LfsStorage.OID_PREFIX + meta.getOid()); } } @NotNull @Override public Map<String, String> createHeader(@NotNull Map<String, String> defaultHeader) { return header; } }; } public User checkAccess(@NotNull HttpServletRequest request, @NotNull Checker checker) throws IOException, UnauthorizedError, ForbiddenError { final User user = getAuthInfo(request); try { checker.check(user, null); } catch (SVNException ignored) { if (user.isAnonymous()) { final WebServer server1 = context.getShared().sure(WebServer.class); throw new UnauthorizedError("Basic realm=\"" + server1.getRealm() + "\""); } else { throw new ForbiddenError(); } } return user; } @Nullable @Override public Meta getMetadata(@NotNull String hash) throws IOException { final LfsReader reader = storage.getReader(LfsStorage.OID_PREFIX + hash); if (reader == null) { return null; } return new Meta(reader.getOid(true), reader.getSize()); } }