blob: 2a55019761238cc47e8b57c1a37e587878acf71d [file] [log] [blame]
// Copyright (C) 2009 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.google.gerrit.server.project;
import static com.google.gerrit.common.CollectionsUtil.*;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/** Access control management for a user accessing a project's data. */
public class ProjectControl {
public static final int VISIBLE = 1 << 0;
public static final int OWNER = 1 << 1;
public static class GenericFactory {
private final ProjectCache projectCache;
@Inject
GenericFactory(final ProjectCache pc) {
projectCache = pc;
}
public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
throws NoSuchProjectException {
final ProjectState p = projectCache.get(nameKey);
if (p == null) {
throw new NoSuchProjectException(nameKey);
}
return p.controlFor(user);
}
}
public static class Factory {
private final ProjectCache projectCache;
private final Provider<CurrentUser> user;
@Inject
Factory(final ProjectCache pc, final Provider<CurrentUser> cu) {
projectCache = pc;
user = cu;
}
public ProjectControl controlFor(final Project.NameKey nameKey)
throws NoSuchProjectException {
final ProjectState p = projectCache.get(nameKey);
if (p == null) {
throw new NoSuchProjectException(nameKey);
}
return p.controlFor(user.get());
}
public ProjectControl validateFor(final Project.NameKey nameKey)
throws NoSuchProjectException {
return validateFor(nameKey, VISIBLE);
}
public ProjectControl ownerFor(final Project.NameKey nameKey)
throws NoSuchProjectException {
return validateFor(nameKey, OWNER);
}
public ProjectControl validateFor(final Project.NameKey nameKey,
final int need) throws NoSuchProjectException {
final ProjectControl c = controlFor(nameKey);
if ((need & VISIBLE) == VISIBLE && c.isVisible()) {
return c;
}
if ((need & OWNER) == OWNER && c.isOwner()) {
return c;
}
throw new NoSuchProjectException(nameKey);
}
}
interface AssistedFactory {
ProjectControl create(CurrentUser who, ProjectState ps);
}
private final SystemConfig systemConfig;
private final Set<AccountGroup.Id> uploadGroups;
private final Set<AccountGroup.Id> receiveGroups;
private final RefControl.Factory refControlFactory;
private final CurrentUser user;
private final ProjectState state;
@Inject
ProjectControl(final SystemConfig systemConfig,
@GitUploadPackGroups Set<AccountGroup.Id> uploadGroups,
@GitReceivePackGroups Set<AccountGroup.Id> receiveGroups,
final RefControl.Factory refControlFactory,
@Assisted CurrentUser who, @Assisted ProjectState ps) {
this.systemConfig = systemConfig;
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.refControlFactory = refControlFactory;
user = who;
state = ps;
}
public ProjectControl forAnonymousUser() {
return state.controlForAnonymousUser();
}
public ProjectControl forUser(final CurrentUser who) {
return state.controlFor(who);
}
public ChangeControl controlFor(final Change change) {
return new ChangeControl(controlForRef(change.getDest()), change);
}
public RefControl controlForRef(Branch.NameKey ref) {
return controlForRef(ref.get());
}
public RefControl controlForRef(String refName) {
return refControlFactory.create(this, refName);
}
public CurrentUser getCurrentUser() {
return user;
}
public ProjectState getProjectState() {
return state;
}
public Project getProject() {
return getProjectState().getProject();
}
/** Can this user see this project exists? */
public boolean isVisible() {
return visibleForReplication()
|| canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
}
public boolean canAddRefs() {
return (canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, ApprovalCategory.PUSH_HEAD_CREATE)
|| isOwnerAnyRef());
}
/** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() {
return visibleForReplication()
|| canPerformOnAllRefs(ApprovalCategory.READ, (short) 1);
}
/** Is this project completely visible for replication? */
boolean visibleForReplication() {
return getCurrentUser() instanceof ReplicationUser
&& ((ReplicationUser) getCurrentUser()).isEverythingVisible();
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
public boolean isOwner() {
return controlForRef(RefRight.ALL).isOwner()
|| getCurrentUser().isAdministrator();
}
/** Does this user have ownership on at least one reference name? */
public boolean isOwnerAnyRef() {
return canPerformOnAnyRef(ApprovalCategory.OWN, (short) 1)
|| getCurrentUser().isAdministrator();
}
/** @return true if the user can upload to at least one reference */
public boolean canPushToAtLeastOneRef() {
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2)
|| canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, (short) 1)
|| canPerformOnAnyRef(ApprovalCategory.PUSH_TAG, (short) 1);
}
// TODO (anatol.pomazau): Try to merge this method with similar RefRightsForPattern#canPerform
private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId,
short requireValue) {
final Set<AccountGroup.Id> groups = getEffectiveUserGroups();
for (final RefRight pr : state.getAllRights(actionId, true)) {
if (groups.contains(pr.getAccountGroupId())
&& pr.getMaxValue() >= requireValue) {
return true;
}
}
return false;
}
/**
* @return the effective groups of the current user for this project
*/
private Set<AccountGroup.Id> getEffectiveUserGroups() {
final Set<AccountGroup.Id> userGroups = user.getEffectiveGroups();
if (isOwner()) {
final Set<AccountGroup.Id> userGroupsOnProject =
new HashSet<AccountGroup.Id>(userGroups.size() + 1);
userGroupsOnProject.addAll(userGroups);
userGroupsOnProject.add(systemConfig.ownerGroupId);
return Collections.unmodifiableSet(userGroupsOnProject);
} else {
return userGroups;
}
}
private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId,
short requireValue) {
boolean canPerform = false;
final Set<String> patterns = allRefPatterns(actionId);
if (patterns.contains(RefRight.ALL)) {
// Only possible if granted on the pattern that
// matches every possible reference. Check all
// patterns also have the permission.
//
for (final String pattern : patterns) {
if (controlForRef(pattern).canPerform(actionId, requireValue)) {
canPerform = true;
} else {
return false;
}
}
}
return canPerform;
}
private Set<String> allRefPatterns(ApprovalCategory.Id actionId) {
final Set<String> all = new HashSet<String>();
for (final RefRight pr : state.getAllRights(actionId, true)) {
all.add(pr.getRefPattern());
}
return all;
}
public boolean canRunUploadPack() {
return isAnyIncludedIn(uploadGroups, getEffectiveUserGroups());
}
public boolean canRunReceivePack() {
return isAnyIncludedIn(receiveGroups, getEffectiveUserGroups());
}
}