Split off access validation and permission checking from SetAccess
This will let us reuse validation (but not permission checking) for a CreateConfigReview endpoint. Change-Id: Id0555f646122d2907e8e407eb5983c28cf19f363
This commit is contained in:
@@ -14,18 +14,10 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.common.data.AccessSection;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.GroupDescription;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.common.data.PermissionRule;
|
||||
import com.google.gerrit.common.errors.InvalidNameException;
|
||||
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
|
||||
import com.google.gerrit.extensions.api.access.PermissionInfo;
|
||||
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
|
||||
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
|
||||
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
@@ -49,23 +41,18 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
|
||||
@Singleton
|
||||
public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessInput> {
|
||||
protected final GroupBackend groupBackend;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final GroupsCollection groupsCollection;
|
||||
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
|
||||
private final AllProjectsName allProjects;
|
||||
private final Provider<SetParent> setParent;
|
||||
private final GetAccess getAccess;
|
||||
private final ProjectCache projectCache;
|
||||
private final Provider<IdentifiedUser> identifiedUser;
|
||||
private final SetAccessUtil accessUtil;
|
||||
|
||||
@Inject
|
||||
private SetAccess(
|
||||
@@ -77,16 +64,15 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
|
||||
GroupsCollection groupsCollection,
|
||||
ProjectCache projectCache,
|
||||
GetAccess getAccess,
|
||||
Provider<IdentifiedUser> identifiedUser) {
|
||||
Provider<IdentifiedUser> identifiedUser,
|
||||
SetAccessUtil accessUtil) {
|
||||
this.groupBackend = groupBackend;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.metaDataUpdateFactory = metaDataUpdateFactory;
|
||||
this.allProjects = allProjects;
|
||||
this.setParent = setParent;
|
||||
this.groupsCollection = groupsCollection;
|
||||
this.getAccess = getAccess;
|
||||
this.projectCache = projectCache;
|
||||
this.identifiedUser = identifiedUser;
|
||||
this.accessUtil = accessUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,113 +80,39 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
|
||||
throws ResourceNotFoundException, ResourceConflictException, IOException, AuthException,
|
||||
BadRequestException, UnprocessableEntityException, OrmException,
|
||||
PermissionBackendException {
|
||||
List<AccessSection> removals = getAccessSections(input.remove);
|
||||
List<AccessSection> additions = getAccessSections(input.add);
|
||||
MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
|
||||
|
||||
ProjectControl projectControl = rsrc.getControl();
|
||||
ProjectConfig config;
|
||||
|
||||
Project.NameKey newParentProjectName =
|
||||
input.parent == null ? null : new Project.NameKey(input.parent);
|
||||
|
||||
List<AccessSection> removals = accessUtil.getAccessSections(input.remove);
|
||||
List<AccessSection> additions = accessUtil.getAccessSections(input.add);
|
||||
try (MetaDataUpdate md = metaDataUpdateUser.create(rsrc.getNameKey())) {
|
||||
config = ProjectConfig.read(md);
|
||||
|
||||
// Perform removal checks
|
||||
for (AccessSection section : removals) {
|
||||
// Check that the user has the right permissions.
|
||||
boolean checkedAdmin = false;
|
||||
for (AccessSection section : Iterables.concat(additions, removals)) {
|
||||
boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
|
||||
|
||||
if (isGlobalCapabilities) {
|
||||
checkGlobalCapabilityPermissions(config.getName());
|
||||
} else if (!projectControl.controlForRef(section.getName()).isOwner()) {
|
||||
if (!checkedAdmin) {
|
||||
permissionBackend.user(identifiedUser).check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||
checkedAdmin = true;
|
||||
}
|
||||
} else if (!rsrc.getControl().controlForRef(section.getName()).isOwner()) {
|
||||
throw new AuthException(
|
||||
"You are not allowed to edit permissionsfor ref: " + section.getName());
|
||||
}
|
||||
}
|
||||
// Perform addition checks
|
||||
for (AccessSection section : additions) {
|
||||
String name = section.getName();
|
||||
boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(name);
|
||||
|
||||
if (isGlobalCapabilities) {
|
||||
checkGlobalCapabilityPermissions(config.getName());
|
||||
} else {
|
||||
if (!AccessSection.isValid(name)) {
|
||||
throw new BadRequestException("invalid section name");
|
||||
}
|
||||
if (!projectControl.controlForRef(name).isOwner()) {
|
||||
throw new AuthException("You are not allowed to edit permissionsfor ref: " + name);
|
||||
}
|
||||
RefPattern.validate(name);
|
||||
}
|
||||
|
||||
// Check all permissions for soundness
|
||||
for (Permission p : section.getPermissions()) {
|
||||
if (isGlobalCapabilities && !GlobalCapability.isCapability(p.getName())) {
|
||||
throw new BadRequestException(
|
||||
"Cannot add non-global capability " + p.getName() + " to global capabilities");
|
||||
}
|
||||
"You are not allowed to edit permissions for ref: " + section.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Apply removals
|
||||
for (AccessSection section : removals) {
|
||||
if (section.getPermissions().isEmpty()) {
|
||||
// Remove entire section
|
||||
config.remove(config.getAccessSection(section.getName()));
|
||||
}
|
||||
// Remove specific permissions
|
||||
for (Permission p : section.getPermissions()) {
|
||||
if (p.getRules().isEmpty()) {
|
||||
config.remove(config.getAccessSection(section.getName()), p);
|
||||
} else {
|
||||
for (PermissionRule r : p.getRules()) {
|
||||
config.remove(config.getAccessSection(section.getName()), p, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
accessUtil.validateChanges(config, removals, additions);
|
||||
accessUtil.applyChanges(config, removals, additions);
|
||||
|
||||
// Apply additions
|
||||
for (AccessSection section : additions) {
|
||||
AccessSection currentAccessSection = config.getAccessSection(section.getName());
|
||||
|
||||
if (currentAccessSection == null) {
|
||||
// Add AccessSection
|
||||
config.replace(section);
|
||||
} else {
|
||||
for (Permission p : section.getPermissions()) {
|
||||
Permission currentPermission = currentAccessSection.getPermission(p.getName());
|
||||
if (currentPermission == null) {
|
||||
// Add Permission
|
||||
currentAccessSection.addPermission(p);
|
||||
} else {
|
||||
for (PermissionRule r : p.getRules()) {
|
||||
// AddPermissionRule
|
||||
currentPermission.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newParentProjectName != null
|
||||
&& !config.getProject().getNameKey().equals(allProjects)
|
||||
&& !config.getProject().getParent(allProjects).equals(newParentProjectName)) {
|
||||
try {
|
||||
setParent
|
||||
.get()
|
||||
.validateParentUpdate(
|
||||
projectControl.getProject().getNameKey(),
|
||||
projectControl.getUser().asIdentifiedUser(),
|
||||
MoreObjects.firstNonNull(newParentProjectName, allProjects).get(),
|
||||
true);
|
||||
} catch (UnprocessableEntityException e) {
|
||||
throw new ResourceConflictException(e.getMessage(), e);
|
||||
}
|
||||
config.getProject().setParentName(newParentProjectName);
|
||||
}
|
||||
accessUtil.setParentName(
|
||||
identifiedUser.get(),
|
||||
config,
|
||||
rsrc.getNameKey(),
|
||||
input.parent == null ? null : new Project.NameKey(input.parent),
|
||||
!checkedAdmin);
|
||||
|
||||
if (!Strings.isNullOrEmpty(input.message)) {
|
||||
if (!input.message.endsWith("\n")) {
|
||||
@@ -221,68 +133,4 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
|
||||
|
||||
return getAccess.apply(rsrc.getNameKey());
|
||||
}
|
||||
|
||||
private List<AccessSection> getAccessSections(Map<String, AccessSectionInfo> sectionInfos)
|
||||
throws UnprocessableEntityException {
|
||||
if (sectionInfos == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AccessSection> sections = new ArrayList<>(sectionInfos.size());
|
||||
for (Map.Entry<String, AccessSectionInfo> entry : sectionInfos.entrySet()) {
|
||||
AccessSection accessSection = new AccessSection(entry.getKey());
|
||||
|
||||
if (entry.getValue().permissions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, PermissionInfo> permissionEntry :
|
||||
entry.getValue().permissions.entrySet()) {
|
||||
Permission p = new Permission(permissionEntry.getKey());
|
||||
if (permissionEntry.getValue().exclusive != null) {
|
||||
p.setExclusiveGroup(permissionEntry.getValue().exclusive);
|
||||
}
|
||||
|
||||
if (permissionEntry.getValue().rules == null) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, PermissionRuleInfo> permissionRuleInfoEntry :
|
||||
permissionEntry.getValue().rules.entrySet()) {
|
||||
PermissionRuleInfo pri = permissionRuleInfoEntry.getValue();
|
||||
|
||||
GroupDescription.Basic group = groupsCollection.parseId(permissionRuleInfoEntry.getKey());
|
||||
if (group == null) {
|
||||
throw new UnprocessableEntityException(
|
||||
permissionRuleInfoEntry.getKey() + " is not a valid group ID");
|
||||
}
|
||||
PermissionRule r = new PermissionRule(GroupReference.forGroup(group));
|
||||
if (pri != null) {
|
||||
if (pri.max != null) {
|
||||
r.setMax(pri.max);
|
||||
}
|
||||
if (pri.min != null) {
|
||||
r.setMin(pri.min);
|
||||
}
|
||||
r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
|
||||
if (pri.force != null) {
|
||||
r.setForce(pri.force);
|
||||
}
|
||||
}
|
||||
p.add(r);
|
||||
}
|
||||
accessSection.getPermissions().add(p);
|
||||
}
|
||||
sections.add(accessSection);
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
private void checkGlobalCapabilityPermissions(Project.NameKey projectName)
|
||||
throws BadRequestException, AuthException, PermissionBackendException {
|
||||
if (!allProjects.equals(projectName)) {
|
||||
throw new BadRequestException(
|
||||
"Cannot edit global capabilities for projects other than " + allProjects.get());
|
||||
}
|
||||
permissionBackend.user(identifiedUser).check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.common.data.AccessSection;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.GroupDescription;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.common.data.PermissionRule;
|
||||
import com.google.gerrit.common.errors.InvalidNameException;
|
||||
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
|
||||
import com.google.gerrit.extensions.api.access.PermissionInfo;
|
||||
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.group.GroupsCollection;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class SetAccessUtil {
|
||||
private final GroupsCollection groupsCollection;
|
||||
private final AllProjectsName allProjects;
|
||||
private final Provider<SetParent> setParent;
|
||||
|
||||
@Inject
|
||||
private SetAccessUtil(
|
||||
GroupsCollection groupsCollection,
|
||||
AllProjectsName allProjects,
|
||||
Provider<SetParent> setParent) {
|
||||
this.groupsCollection = groupsCollection;
|
||||
this.allProjects = allProjects;
|
||||
this.setParent = setParent;
|
||||
}
|
||||
|
||||
List<AccessSection> getAccessSections(Map<String, AccessSectionInfo> sectionInfos)
|
||||
throws UnprocessableEntityException {
|
||||
if (sectionInfos == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AccessSection> sections = new ArrayList<>(sectionInfos.size());
|
||||
for (Map.Entry<String, AccessSectionInfo> entry : sectionInfos.entrySet()) {
|
||||
if (entry.getValue().permissions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AccessSection accessSection = new AccessSection(entry.getKey());
|
||||
for (Map.Entry<String, PermissionInfo> permissionEntry :
|
||||
entry.getValue().permissions.entrySet()) {
|
||||
if (permissionEntry.getValue().rules == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Permission p = new Permission(permissionEntry.getKey());
|
||||
if (permissionEntry.getValue().exclusive != null) {
|
||||
p.setExclusiveGroup(permissionEntry.getValue().exclusive);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, PermissionRuleInfo> permissionRuleInfoEntry :
|
||||
permissionEntry.getValue().rules.entrySet()) {
|
||||
GroupDescription.Basic group = groupsCollection.parseId(permissionRuleInfoEntry.getKey());
|
||||
if (group == null) {
|
||||
throw new UnprocessableEntityException(
|
||||
permissionRuleInfoEntry.getKey() + " is not a valid group ID");
|
||||
}
|
||||
|
||||
PermissionRuleInfo pri = permissionRuleInfoEntry.getValue();
|
||||
PermissionRule r = new PermissionRule(GroupReference.forGroup(group));
|
||||
if (pri != null) {
|
||||
if (pri.max != null) {
|
||||
r.setMax(pri.max);
|
||||
}
|
||||
if (pri.min != null) {
|
||||
r.setMin(pri.min);
|
||||
}
|
||||
r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
|
||||
if (pri.force != null) {
|
||||
r.setForce(pri.force);
|
||||
}
|
||||
}
|
||||
p.add(r);
|
||||
}
|
||||
accessSection.getPermissions().add(p);
|
||||
}
|
||||
sections.add(accessSection);
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the removals and additions are logically valid, but doesn't check current user's
|
||||
* permission.
|
||||
*/
|
||||
void validateChanges(
|
||||
ProjectConfig config, List<AccessSection> removals, List<AccessSection> additions)
|
||||
throws BadRequestException, AuthException, InvalidNameException, PermissionBackendException {
|
||||
// Perform permission checks
|
||||
for (AccessSection section : Iterables.concat(additions, removals)) {
|
||||
boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
|
||||
if (isGlobalCapabilities) {
|
||||
if (!allProjects.equals(config.getName())) {
|
||||
throw new BadRequestException(
|
||||
"Cannot edit global capabilities for projects other than " + allProjects.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform addition checks
|
||||
for (AccessSection section : additions) {
|
||||
String name = section.getName();
|
||||
boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(name);
|
||||
|
||||
if (!isGlobalCapabilities) {
|
||||
if (!AccessSection.isValid(name)) {
|
||||
throw new BadRequestException("invalid section name");
|
||||
}
|
||||
RefPattern.validate(name);
|
||||
} else {
|
||||
// Check all permissions for soundness
|
||||
for (Permission p : section.getPermissions()) {
|
||||
if (!GlobalCapability.isCapability(p.getName())) {
|
||||
throw new BadRequestException(
|
||||
"Cannot add non-global capability " + p.getName() + " to global capabilities");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void applyChanges(
|
||||
ProjectConfig config, List<AccessSection> removals, List<AccessSection> additions) {
|
||||
// Apply removals
|
||||
for (AccessSection section : removals) {
|
||||
if (section.getPermissions().isEmpty()) {
|
||||
// Remove entire section
|
||||
config.remove(config.getAccessSection(section.getName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove specific permissions
|
||||
for (Permission p : section.getPermissions()) {
|
||||
if (p.getRules().isEmpty()) {
|
||||
config.remove(config.getAccessSection(section.getName()), p);
|
||||
} else {
|
||||
for (PermissionRule r : p.getRules()) {
|
||||
config.remove(config.getAccessSection(section.getName()), p, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply additions
|
||||
for (AccessSection section : additions) {
|
||||
AccessSection currentAccessSection = config.getAccessSection(section.getName());
|
||||
|
||||
if (currentAccessSection == null) {
|
||||
// Add AccessSection
|
||||
config.replace(section);
|
||||
} else {
|
||||
for (Permission p : section.getPermissions()) {
|
||||
Permission currentPermission = currentAccessSection.getPermission(p.getName());
|
||||
if (currentPermission == null) {
|
||||
// Add Permission
|
||||
currentAccessSection.addPermission(p);
|
||||
} else {
|
||||
for (PermissionRule r : p.getRules()) {
|
||||
// AddPermissionRule
|
||||
currentPermission.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setParentName(
|
||||
IdentifiedUser identifiedUser,
|
||||
ProjectConfig config,
|
||||
Project.NameKey projectName,
|
||||
Project.NameKey newParentProjectName,
|
||||
boolean checkAdmin)
|
||||
throws ResourceConflictException, AuthException, PermissionBackendException {
|
||||
if (newParentProjectName != null
|
||||
&& !config.getProject().getNameKey().equals(allProjects)
|
||||
&& !config.getProject().getParent(allProjects).equals(newParentProjectName)) {
|
||||
try {
|
||||
setParent
|
||||
.get()
|
||||
.validateParentUpdate(
|
||||
projectName, identifiedUser, newParentProjectName.get(), checkAdmin);
|
||||
} catch (UnprocessableEntityException e) {
|
||||
throw new ResourceConflictException(e.getMessage(), e);
|
||||
}
|
||||
config.getProject().setParentName(newParentProjectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user