blob: 679104eb94007e79df7366f953d1c390496961b9 [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.client.reviewdb.ApprovalCategory.PUSH_HEAD;
import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_TAG;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.spearce.jgit.lib.Constants;
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 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);
}
}
private final CurrentUser user;
private final ProjectState state;
ProjectControl(final CurrentUser who, final ProjectState ps) {
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(this, change);
}
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 canPerform(ApprovalCategory.READ, (short) 1);
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
public boolean isOwner() {
return canPerform(ApprovalCategory.OWN, (short) 1)
|| getCurrentUser().isAdministrator();
}
/** Can this user create the given ref through this access path? */
public boolean canCreateRef(final String refname) {
switch (user.getAccessPath()) {
case WEB:
if (isOwner()) {
return true;
}
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_CREATE)) {
return true;
}
return false;
case SSH:
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_CREATE)) {
return true;
}
if (isTag(refname) && canPerform(PUSH_TAG, (short) 1)) {
return true;
}
return false;
default:
return false;
}
}
/** Can this user delete the given ref through this access path? */
public boolean canDeleteRef(final String refname) {
switch (user.getAccessPath()) {
case WEB:
if (isOwner()) {
return true;
}
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
return true;
}
return false;
case SSH:
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
return true;
}
return false;
default:
return false;
}
}
/**
* Can this user perform the action in this project, at the level asked?
* <p>
* This method checks the project rights against the user's effective groups.
* If no right for the given category was granted to any of the user's
* effective groups, then the rights from the wildcard project are checked.
*
* @param actionId unique action id.
* @param requireValue minimum value the application needs to perform this
* action.
* @return true if the action can be performed; false if the user lacks the
* necessary permission.
*/
public boolean canPerform(final ApprovalCategory.Id actionId,
final short requireValue) {
final Set<AccountGroup.Id> groups = user.getEffectiveGroups();
int val = Integer.MIN_VALUE;
for (final ProjectRight pr : state.getLocalRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& groups.contains(pr.getAccountGroupId())) {
if (val < 0 && pr.getMaxValue() > 0) {
// If one of the user's groups had denied them access, but
// this group grants them access, prefer the grant over
// the denial. We have to break the tie somehow and we
// prefer being "more open" to being "more closed".
//
val = pr.getMaxValue();
} else {
// Otherwise we use the largest value we can get.
//
val = Math.max(pr.getMaxValue(), val);
}
}
}
if (val == Integer.MIN_VALUE && actionId.canInheritFromWildProject()) {
for (final ProjectRight pr : state.getInheritedRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& groups.contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);
}
}
}
return val >= requireValue;
}
private static boolean isHead(final String refname) {
return refname.startsWith(Constants.R_HEADS);
}
private static boolean isTag(final String refname) {
return refname.startsWith(Constants.R_TAGS);
}
}