/**
* 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.gitlab.mapping;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.gitlab.api.GitlabAPI;
import org.gitlab.api.models.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import svnserver.auth.User;
import svnserver.context.LocalContext;
import svnserver.ext.gitlab.config.GitLabContext;
import svnserver.repository.VcsAccess;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Access control by GitLab server.
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
public class GitLabAccess implements VcsAccess {
@NotNull
private final LoadingCache<String, GitlabProject> cache;
public GitLabAccess(@NotNull LocalContext local, @NotNull GitLabMappingConfig config, int projectId) {
this.cache = CacheBuilder.newBuilder()
.maximumSize(config.getCacheMaximumSize())
.expireAfterWrite(config.getCacheTimeSec(), TimeUnit.SECONDS)
.build(
new CacheLoader<String, GitlabProject>() {
@Override
public GitlabProject load(@NotNull String userId) throws Exception {
final GitlabAPI api = GitLabContext.sure(local.getShared()).connect();
String tailUrl = GitlabProject.URL + "/" + projectId;
if (!userId.isEmpty()) {
tailUrl += "?sudo=" + userId;
}
return api.retrieve().to(tailUrl, GitlabProject.class);
}
}
);
}
@Override
public void checkRead(@NotNull User user, @Nullable String path) throws SVNException, IOException {
try {
if (user.isAnonymous()) {
if (!getProjectAsAdmin().isPublic()) {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "This project has not public access"));
}
} else {
getProjectViaSudo(user);
}
} catch (FileNotFoundException ignored) {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "You're not authorized to read this project"));
}
}
@Override
public void checkWrite(@NotNull User user, @Nullable String path) throws SVNException, IOException {
if (user.isAnonymous()) {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "Anonymous user has no project write access"));
}
try {
final GitlabProject project = getProjectViaSudo(user);
if (isProjectOwner(project, user)) {
return;
}
final GitlabPermission permissions = project.getPermissions();
if (permissions != null) {
if (hasAccess(permissions.getProjectAccess(), GitlabAccessLevel.Developer) ||
hasAccess(permissions.getProjectGroupAccess(), GitlabAccessLevel.Developer)) {
return;
}
}
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "You're not authorized to write this project"));
} catch (FileNotFoundException ignored) {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "You're not authorized to read this project"));
}
}
private boolean isProjectOwner(@NotNull GitlabProject project, @NotNull User user) {
if (user.isAnonymous()) {
return false;
}
GitlabUser owner = project.getOwner();
//noinspection SimplifiableIfStatement
if (owner == null) {
return false;
}
return owner.getId().toString().equals(user.getExternalId())
|| owner.getName().equals(user.getUserName());
}
private boolean hasAccess(@Nullable GitlabProjectAccessLevel access, @NotNull GitlabAccessLevel level) {
if (access == null) return false;
GitlabAccessLevel accessLevel = access.getAccessLevel();
return accessLevel != null && (accessLevel.accessValue >= level.accessValue);
}
@NotNull
private GitlabProject getProjectAsAdmin() throws IOException {
try {
return cache.get("");
} catch (ExecutionException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new IllegalStateException(e);
}
}
@NotNull
private GitlabProject getProjectViaSudo(@NotNull User user) throws IOException {
try {
final String key = user.getExternalId() != null ? user.getExternalId() : user.getUserName();
if (key.isEmpty()) {
throw new IllegalStateException("Found user without identificator: " + user);
}
return cache.get(key);
} catch (ExecutionException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new IllegalStateException(e);
}
}
}