blob: 7f0942b8a37801a62df50025701e495c45ae5a39 [file] [log] [blame]
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.ericsson.gerrit.plugins.projectgroupstructure;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefPattern;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Set defaults access rights for root projects.
*
* <p>Default access rights are read from <review_site>/data/project-group-structure/project.config.
* The format of that file is the same as regular project.config except that group, in addition to
* be a group name, can be set to token ${owner} instead which will be replaced by the group owning
* the project.
*/
@Singleton
public class DefaultAccessRights implements NewProjectCreatedListener {
private static final Logger log = LoggerFactory.getLogger(DefaultAccessRights.class);
private static final String OWNER_TOKEN = "${owner}";
private final GroupCache groupCache;
private final ProjectCache projectCache;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final ProjectConfig.Factory projectConfigFactory;
private final FileBasedConfig defaultAccessRightsConfig;
@Inject
public DefaultAccessRights(
ProjectConfig.Factory projectConfigFactory,
MetaDataUpdate.User metaDataUpdateFactory,
ProjectCache projectCache,
GroupCache groupCache,
@PluginData Path dataDir) {
this.groupCache = groupCache;
this.projectCache = projectCache;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectConfigFactory = projectConfigFactory;
defaultAccessRightsConfig =
new FileBasedConfig(dataDir.resolve(ProjectConfig.PROJECT_CONFIG).toFile(), FS.DETECTED);
try {
defaultAccessRightsConfig.load();
} catch (IOException | ConfigInvalidException e) {
// Swallow the exception to allow the plugin to load, we still want the
// project structure to be enforced even if defaults access rights will
// not be set.
log.error(
"Failed to load default access rights config {}, no access right will be set on root projects: {}",
defaultAccessRightsConfig.getFile().getAbsolutePath(),
e.getMessage(),
e);
}
}
@Override
public void onNewProjectCreated(NewProjectCreatedListener.Event event) {
String projectName = event.getProjectName();
// only set default access rights for root projects, if configured.
if (projectName.contains("/") || defaultAccessRightsConfig.getSections().isEmpty()) {
return;
}
ProjectState project = projectCache.get(Project.NameKey.parse(projectName));
if (project == null) {
log.error("Could not retrieve projet {} from cache", projectName);
return;
}
try (MetaDataUpdate md = metaDataUpdateFactory.create(project.getProject().getNameKey())) {
ProjectConfig config = projectConfigFactory.read(md);
setAccessRights(config, project);
md.setMessage("Set default access rights\n");
config.commit(md);
} catch (Exception e) {
log.error("Failed to set defauts access rights {}", e.getMessage(), e);
}
}
private void setAccessRights(ProjectConfig config, ProjectState project) {
for (String refName : defaultAccessRightsConfig.getSubsections(ProjectConfig.ACCESS)) {
if (AccessSection.isValidRefSectionName(refName) && isValidRegex(refName)) {
AccessSection as = config.getAccessSection(refName, true);
getPermissions(refName, as);
setPermissions(refName, as, getOwnerGroupName(project));
}
}
}
private void getPermissions(String refName, AccessSection as) {
for (String varName :
defaultAccessRightsConfig.getStringList(
ProjectConfig.ACCESS, refName, "exclusiveGroupPermissions")) {
Arrays.stream(varName.split("[, \t]{1,}"))
.filter(Permission::isPermission)
.forEach(n -> as.getPermission(n, true).setExclusiveGroup(true));
}
}
private void setPermissions(String refName, AccessSection as, String ownerGroupName) {
for (String value : defaultAccessRightsConfig.getNames(ProjectConfig.ACCESS, refName)) {
if (Permission.isPermission(value)) {
Permission perm = as.getPermission(value, true);
setPermissionRules(ownerGroupName, perm, refName, value);
} else {
log.error("Invalid permission {}", value);
}
}
}
private String getOwnerGroupName(ProjectState project) {
Set<AccountGroup.UUID> owners = project.getAllOwners();
if (!owners.isEmpty()) {
Optional<InternalGroup> owner = groupCache.get(owners.iterator().next());
if (owner.isPresent()) {
return owner.get().getName();
}
}
return String.format("no owners for project %s", project.getProject().getName());
}
private boolean isValidRegex(String refPattern) {
try {
RefPattern.validateRegExp(refPattern);
} catch (InvalidNameException e) {
log.error("Invalid ref name: {}", e.getMessage());
return false;
}
return true;
}
private void setPermissionRules(
String ownerGroupName, Permission perm, String refName, String value) {
for (String ruleString :
defaultAccessRightsConfig.getStringList(ProjectConfig.ACCESS, refName, value)) {
PermissionRule rule;
try {
rule =
PermissionRule.fromString(
ruleString.replaceAll(Pattern.quote(OWNER_TOKEN), ownerGroupName),
Permission.hasRange(value));
} catch (IllegalArgumentException notRule) {
log.error(
"Invalid rule in {}{}.{}: {}",
ProjectConfig.ACCESS,
refName != null ? "." + refName : "",
value,
notRule.getMessage());
continue;
}
if (rule.getGroup().getUUID() == null) {
// this means that group is not already in the groups file, so
// we need to check if group exist if if it does, get its
// uuid.
Optional<InternalGroup> group =
groupCache.get(AccountGroup.nameKey(rule.getGroup().getName()));
if (!group.isPresent()) {
log.error("Group {} not found", rule.getGroup().getName());
continue;
}
rule.getGroup().setUUID(group.get().getGroupUUID());
}
perm.add(rule);
}
}
}