Move "ref_rights" table into Git

Permissions are stored in the project.config file within the
refs/meta/config branch of each project.  This makes the rules
more flexible in the future, as well as adds version control.

For example:

  [access "refs/*"]
    owner = group tools-owners

  [access "refs/heads/*"]
    label-Verified = -1..+1 group tools-dev
    label-Verified = -1..+1 group tools-owners
    label-Code-Review = -2..+2 group tools-owners
    submit = group tools-dev
    submit = group tools-owners

  [access "refs/heads/stable"]
    exclusiveGroupPermissions = read create push
    read = group Anonymous Users
    push = group tools-repo-maintainer

To enable easy remote editing of the configuration rules, the
following access block is added by default to -- All Projects --
and is thus inherited throughout the entire site:

  [access "refs/meta/config"]
    read = group Project Owners
    push = group Project Owners

This configuration section permits any project owner or site
administrator (as they are indirectly always a project owner of
any project) to push changes to the project.config file within
the refs/meta/config branch, updating access (and other project
information) remotely without using the web UI.

Change-Id: Idb56f657a4bf88108ad40bbb19d831e6806b68c5
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2011-01-05 12:46:21 -08:00
parent 83f6cc14af
commit 6a765190df
48 changed files with 2181 additions and 1628 deletions

View File

@@ -0,0 +1,129 @@
// Copyright (C) 2010 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.common.data;
import com.google.gerrit.reviewdb.Project;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/** Portion of a {@link Project} describing access rules. */
public class AccessSection implements Comparable<AccessSection> {
/** Pattern that matches all references in a project. */
public static final String ALL = "refs/*";
/** Pattern that matches all branches in a project. */
public static final String HEADS = "refs/heads/*";
/** Prefix that triggers a regular expression pattern. */
public static final String REGEX_PREFIX = "^";
/** @return true if the name is likely to be a valid access section name. */
public static boolean isAccessSection(String name) {
return name.startsWith("refs/") || name.startsWith("^refs/");
}
protected String refPattern;
protected List<Permission> permissions;
protected AccessSection() {
}
public AccessSection(String refPattern) {
setRefPattern(refPattern);
}
public String getRefPattern() {
return refPattern;
}
public void setRefPattern(String refPattern) {
this.refPattern = refPattern;
}
public List<Permission> getPermissions() {
if (permissions == null) {
permissions = new ArrayList<Permission>();
}
return permissions;
}
public void setPermissions(List<Permission> list) {
Set<String> names = new HashSet<String>();
for (Permission p : list) {
if (!names.add(p.getName().toLowerCase())) {
throw new IllegalArgumentException();
}
}
permissions = list;
}
public Permission getPermission(String name) {
return getPermission(name, false);
}
public Permission getPermission(String name, boolean create) {
for (Permission p : getPermissions()) {
if (p.getName().equalsIgnoreCase(name)) {
return p;
}
}
if (create) {
Permission p = new Permission(name);
permissions.add(p);
return p;
} else {
return null;
}
}
public void remove(Permission permission) {
if (permission != null) {
removePermission(permission.getName());
}
}
public void removePermission(String name) {
if (permissions != null) {
for (Iterator<Permission> itr = permissions.iterator(); itr.hasNext();) {
if (name.equalsIgnoreCase(itr.next().getName())) {
itr.remove();
}
}
}
}
@Override
public int compareTo(AccessSection o) {
return comparePattern().compareTo(o.comparePattern());
}
private String comparePattern() {
if (getRefPattern().startsWith(REGEX_PREFIX)) {
return getRefPattern().substring(REGEX_PREFIX.length());
}
return getRefPattern();
}
@Override
public String toString() {
return "AccessSection[" + getRefPattern() + "]";
}
}

View File

@@ -31,6 +31,7 @@ public class ApprovalType {
protected short maxNegative; protected short maxNegative;
protected short maxPositive; protected short maxPositive;
private transient List<Integer> intList;
private transient Map<Short, ApprovalCategoryValue> byValue; private transient Map<Short, ApprovalCategoryValue> byValue;
protected ApprovalType() { protected ApprovalType() {
@@ -56,6 +57,9 @@ public class ApprovalType {
maxPositive = values.get(values.size() - 1).getValue(); maxPositive = values.get(values.size() - 1).getValue();
} }
} }
// Force the label name to pre-compute so we don't have data race conditions.
getCategory().getLabelName();
} }
public ApprovalCategory getCategory() { public ApprovalCategory getCategory() {
@@ -107,4 +111,16 @@ public class ApprovalType {
} }
} }
} }
public List<Integer> getValuesAsList() {
if (intList == null) {
intList = new ArrayList<Integer>(values.size());
for (ApprovalCategoryValue acv : values) {
intList.add(Integer.valueOf(acv.getValue()));
}
Collections.sort(intList);
Collections.reverse(intList);
}
return intList;
}
} }

View File

@@ -19,20 +19,17 @@ import com.google.gerrit.reviewdb.ApprovalCategory;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class ApprovalTypes { public class ApprovalTypes {
protected List<ApprovalType> approvalTypes; protected List<ApprovalType> approvalTypes;
protected List<ApprovalType> actionTypes; private transient Map<ApprovalCategory.Id, ApprovalType> byId;
private transient Map<ApprovalCategory.Id, ApprovalType> byCategoryId; private transient Map<String, ApprovalType> byLabel;
protected ApprovalTypes() { protected ApprovalTypes() {
} }
public ApprovalTypes(final List<ApprovalType> approvals, public ApprovalTypes(final List<ApprovalType> approvals) {
final List<ApprovalType> actions) {
approvalTypes = approvals; approvalTypes = approvals;
actionTypes = actions;
byCategory(); byCategory();
} }
@@ -40,33 +37,35 @@ public class ApprovalTypes {
return approvalTypes; return approvalTypes;
} }
public List<ApprovalType> getActionTypes() { public ApprovalType byId(final ApprovalCategory.Id id) {
return actionTypes;
}
public ApprovalType getApprovalType(final ApprovalCategory.Id id) {
return byCategory().get(id); return byCategory().get(id);
} }
public Set<ApprovalCategory.Id> getApprovalCategories() {
return byCategory().keySet();
}
private Map<ApprovalCategory.Id, ApprovalType> byCategory() { private Map<ApprovalCategory.Id, ApprovalType> byCategory() {
if (byCategoryId == null) { if (byId == null) {
byCategoryId = new HashMap<ApprovalCategory.Id, ApprovalType>(); byId = new HashMap<ApprovalCategory.Id, ApprovalType>();
if (actionTypes != null) {
for (final ApprovalType t : actionTypes) {
byCategoryId.put(t.getCategory().getId(), t);
}
}
if (approvalTypes != null) { if (approvalTypes != null) {
for (final ApprovalType t : approvalTypes) { for (final ApprovalType t : approvalTypes) {
byCategoryId.put(t.getCategory().getId(), t); byId.put(t.getCategory().getId(), t);
} }
} }
} }
return byCategoryId; return byId;
}
public ApprovalType byLabel(String labelName) {
return byLabel().get(labelName.toLowerCase());
}
private Map<String, ApprovalType> byLabel() {
if (byLabel == null) {
byLabel = new HashMap<String, ApprovalType>();
if (approvalTypes != null) {
for (ApprovalType t : approvalTypes) {
byLabel.put(t.getCategory().getLabelName().toLowerCase(), t);
}
}
}
return byLabel;
} }
} }

View File

@@ -39,10 +39,10 @@ public class ChangeDetail {
protected List<PatchSet> patchSets; protected List<PatchSet> patchSets;
protected List<ApprovalDetail> approvals; protected List<ApprovalDetail> approvals;
protected Set<ApprovalCategory.Id> missingApprovals; protected Set<ApprovalCategory.Id> missingApprovals;
protected boolean canSubmit;
protected List<ChangeMessage> messages; protected List<ChangeMessage> messages;
protected PatchSet.Id currentPatchSetId; protected PatchSet.Id currentPatchSetId;
protected PatchSetDetail currentDetail; protected PatchSetDetail currentDetail;
protected Set<ApprovalCategory.Id> currentActions;
public ChangeDetail() { public ChangeDetail() {
} }
@@ -87,6 +87,14 @@ public class ChangeDetail {
canRevert = a; canRevert = a;
} }
public boolean canSubmit() {
return canSubmit;
}
public void setCanSubmit(boolean a) {
canSubmit = a;
}
public Change getChange() { public Change getChange() {
return change; return change;
} }
@@ -153,14 +161,6 @@ public class ChangeDetail {
missingApprovals = a; missingApprovals = a;
} }
public Set<ApprovalCategory.Id> getCurrentActions() {
return currentActions;
}
public void setCurrentActions(Set<ApprovalCategory.Id> a) {
currentActions = a;
}
public boolean isCurrentPatchSet(final PatchSetDetail detail) { public boolean isCurrentPatchSet(final PatchSetDetail detail) {
return currentPatchSetId != null return currentPatchSetId != null
&& detail.getPatchSet().getId().equals(currentPatchSetId); && detail.getPatchSet().getId().equals(currentPatchSetId);

View File

@@ -0,0 +1,76 @@
// Copyright (C) 2010 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.common.data;
import com.google.gerrit.reviewdb.AccountGroup;
/** Describes a group within a projects {@link AccessSection}s. */
public class GroupReference implements Comparable<GroupReference> {
/** @return a new reference to the given group description. */
public static GroupReference forGroup(AccountGroup group) {
return new GroupReference(group.getGroupUUID(), group.getName());
}
protected String uuid;
protected String name;
protected GroupReference() {
}
public GroupReference(AccountGroup.UUID uuid, String name) {
setUUID(uuid);
setName(name);
}
public AccountGroup.UUID getUUID() {
return uuid != null ? new AccountGroup.UUID(uuid) : null;
}
public void setUUID(AccountGroup.UUID newUUID) {
uuid = newUUID.get();
}
public String getName() {
return name;
}
public void setName(String newName) {
this.name = newName;
}
@Override
public int compareTo(GroupReference o) {
return uuid(this).compareTo(uuid(o));
}
private static String uuid(GroupReference a) {
return a.getUUID() != null ? a.getUUID().get() : "?";
}
@Override
public int hashCode() {
return uuid(this).hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof GroupReference && compareTo((GroupReference) o) == 0;
}
@Override
public String toString() {
return "Group[" + getName() + " / " + getUUID() + "]";
}
}

View File

@@ -15,39 +15,35 @@
package com.google.gerrit.common.data; package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment; import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo; import com.google.gerrit.reviewdb.PatchSetInfo;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
public class PatchSetPublishDetail { public class PatchSetPublishDetail {
protected AccountInfoCache accounts; protected AccountInfoCache accounts;
protected PatchSetInfo patchSetInfo; protected PatchSetInfo patchSetInfo;
protected Change change; protected Change change;
protected List<PatchLineComment> drafts; protected List<PatchLineComment> drafts;
protected Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed; protected List<PermissionRange> labels;
protected Map<ApprovalCategory.Id, PatchSetApproval> given; protected List<PatchSetApproval> given;
protected boolean isSubmitAllowed; protected boolean canSubmit;
public Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> getAllowed() { public List<PermissionRange> getLabels() {
return allowed; return labels;
} }
public void setAllowed( public void setLabels(List<PermissionRange> labels) {
Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed) { this.labels = labels;
this.allowed = allowed;
} }
public Map<ApprovalCategory.Id, PatchSetApproval> getGiven() { public List<PatchSetApproval> getGiven() {
return given; return given;
} }
public void setGiven(Map<ApprovalCategory.Id, PatchSetApproval> given) { public void setGiven(List<PatchSetApproval> given) {
this.given = given; this.given = given;
} }
@@ -67,8 +63,8 @@ public class PatchSetPublishDetail {
this.drafts = drafts; this.drafts = drafts;
} }
public void setSubmitAllowed(boolean allowed) { public void setCanSubmit(boolean allowed) {
isSubmitAllowed = allowed; canSubmit = allowed;
} }
public AccountInfoCache getAccounts() { public AccountInfoCache getAccounts() {
@@ -87,20 +83,25 @@ public class PatchSetPublishDetail {
return drafts; return drafts;
} }
public boolean isAllowed(final ApprovalCategory.Id id) { public PermissionRange getRange(final String permissionName) {
final Set<ApprovalCategoryValue.Id> s = getAllowed(id); for (PermissionRange s : labels) {
return s != null && !s.isEmpty(); if (s.getName().equals(permissionName)) {
return s;
}
}
return null;
} }
public Set<ApprovalCategoryValue.Id> getAllowed(final ApprovalCategory.Id id) { public PatchSetApproval getChangeApproval(ApprovalCategory.Id id) {
return allowed.get(id); for (PatchSetApproval a : given) {
if (a.getCategoryId().equals(id)) {
return a;
}
}
return null;
} }
public PatchSetApproval getChangeApproval(final ApprovalCategory.Id id) { public boolean canSubmit() {
return given.get(id); return canSubmit;
}
public boolean isSubmitAllowed() {
return isSubmitAllowed;
} }
} }

View File

@@ -0,0 +1,190 @@
// Copyright (C) 2010 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.common.data;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/** A single permission within an {@link AccessSection} of a project. */
public class Permission implements Comparable<Permission> {
public static final String CREATE = "create";
public static final String FORGE_AUTHOR = "forgeAuthor";
public static final String FORGE_COMMITTER = "forgeCommitter";
public static final String FORGE_SERVER = "forgeServerAsCommitter";
public static final String LABEL = "label-";
public static final String OWNER = "owner";
public static final String PUSH = "push";
public static final String PUSH_MERGE = "pushMerge";
public static final String PUSH_TAG = "pushTag";
public static final String READ = "read";
public static final String SUBMIT = "submit";
private static final List<String> NAMES_LC;
static {
NAMES_LC = new ArrayList<String>();
NAMES_LC.add(OWNER.toLowerCase());
NAMES_LC.add(READ.toLowerCase());
NAMES_LC.add(CREATE.toLowerCase());
NAMES_LC.add(FORGE_AUTHOR.toLowerCase());
NAMES_LC.add(FORGE_COMMITTER.toLowerCase());
NAMES_LC.add(FORGE_SERVER.toLowerCase());
NAMES_LC.add(PUSH.toLowerCase());
NAMES_LC.add(PUSH_MERGE.toLowerCase());
NAMES_LC.add(PUSH_TAG.toLowerCase());
NAMES_LC.add(LABEL.toLowerCase());
NAMES_LC.add(SUBMIT.toLowerCase());
}
/** @return true if the name is recognized as a permission name. */
public static boolean isPermission(String varName) {
String lc = varName.toLowerCase();
if (lc.startsWith(LABEL)) {
return LABEL.length() < lc.length();
}
return NAMES_LC.contains(lc);
}
/** @return true if the permission name is actually for a review label. */
public static boolean isLabel(String varName) {
return varName.startsWith(LABEL) && LABEL.length() < varName.length();
}
/** @return permission name for the given review label. */
public static String forLabel(String labelName) {
return LABEL + labelName;
}
protected String name;
protected boolean exclusiveGroup;
protected List<PermissionRule> rules;
protected Permission() {
}
public Permission(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isLabel() {
return isLabel(getName());
}
public String getLabel() {
if (isLabel()) {
return getName().substring(LABEL.length());
}
return null;
}
public boolean getExclusiveGroup() {
// Only permit exclusive group behavior on non OWNER permissions,
// otherwise an owner might lose access to a delegated subspace.
//
return exclusiveGroup && !OWNER.equals(getName());
}
public void setExclusiveGroup(boolean newExclusiveGroup) {
exclusiveGroup = newExclusiveGroup;
}
public List<PermissionRule> getRules() {
initRules();
return rules;
}
public void setRules(List<PermissionRule> list) {
rules = list;
}
public void add(PermissionRule rule) {
initRules();
rules.add(rule);
}
public void remove(PermissionRule rule) {
if (rule != null) {
removeRule(rule.getGroup());
}
}
public void removeRule(GroupReference group) {
if (rules != null) {
for (Iterator<PermissionRule> itr = rules.iterator(); itr.hasNext();) {
if (sameGroup(itr.next(), group)) {
itr.remove();
}
}
}
}
public PermissionRule getRule(GroupReference group) {
return getRule(group, false);
}
public PermissionRule getRule(GroupReference group, boolean create) {
initRules();
for (PermissionRule r : rules) {
if (sameGroup(r, group)) {
return r;
}
}
if (create) {
PermissionRule r = new PermissionRule(group);
rules.add(r);
return r;
} else {
return null;
}
}
private static boolean sameGroup(PermissionRule rule, GroupReference group) {
if (group.getUUID() != null) {
return group.getUUID().equals(rule.getGroup().getUUID());
} else if (group.getName() != null) {
return group.getName().equals(rule.getGroup().getName());
} else {
return false;
}
}
private void initRules() {
if (rules == null) {
rules = new ArrayList<PermissionRule>(4);
}
}
@Override
public int compareTo(Permission b) {
int cmp = index(this) - index(b);
if (cmp == 0) getName().compareTo(b.getName());
return cmp;
}
private static int index(Permission a) {
String lc = a.isLabel() ? Permission.LABEL : a.getName().toLowerCase();
int index = NAMES_LC.indexOf(lc);
return 0 <= index ? index : NAMES_LC.size();
}
}

View File

@@ -0,0 +1,95 @@
// Copyright (C) 2010 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.common.data;
public class PermissionRange implements Comparable<PermissionRange> {
protected String name;
protected int min;
protected int max;
protected PermissionRange() {
}
public PermissionRange(String name, int min, int max) {
this.name = name;
if (min <= max) {
this.min = min;
this.max = max;
} else {
this.min = max;
this.max = min;
}
}
public String getName() {
return name;
}
public boolean isLabel() {
return Permission.isLabel(getName());
}
public String getLabel() {
return isLabel() ? getName().substring(Permission.LABEL.length()) : null;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
/** True if the value is within the range. */
public boolean contains(int value) {
return getMin() <= value && value <= getMax();
}
/** Normalize the value to fit within the bounds of the range. */
public int squash(int value) {
return Math.min(Math.max(getMin(), value), getMax());
}
/** True both {@link #getMin()} and {@link #getMax()} are 0. */
public boolean isEmpty() {
return getMin() == 0 && getMax() == 0;
}
@Override
public int compareTo(PermissionRange o) {
return getName().compareTo(o.getName());
}
@Override
public String toString() {
StringBuilder r = new StringBuilder();
if (getMin() < 0 && getMax() == 0) {
r.append(getMin());
r.append(' ');
} else {
if (getMin() != getMax()) {
if (0 <= getMin()) r.append('+');
r.append(getMin());
r.append("..");
}
if (0 <= getMax()) r.append('+');
r.append(getMax());
r.append(' ');
}
return r.toString();
}
}

View File

@@ -0,0 +1,182 @@
// Copyright (C) 2010 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.common.data;
public class PermissionRule implements Comparable<PermissionRule> {
protected boolean deny;
protected boolean force;
protected int min;
protected int max;
protected GroupReference group;
public PermissionRule() {
}
public PermissionRule(GroupReference group) {
this.group = group;
}
public boolean getDeny() {
return deny;
}
public void setDeny(boolean newDeny) {
deny = newDeny;
}
public boolean getForce() {
return force;
}
public void setForce(boolean newForce) {
force = newForce;
}
public Integer getMin() {
return min;
}
public void setMin(Integer min) {
this.min = min;
}
public void setMax(Integer max) {
this.max = max;
}
public Integer getMax() {
return max;
}
public void setRange(int newMin, int newMax) {
if (newMax < newMin) {
min = newMax;
max = newMin;
} else {
min = newMin;
max = newMax;
}
}
public GroupReference getGroup() {
return group;
}
public void setGroup(GroupReference newGroup) {
group = newGroup;
}
@Override
public int compareTo(PermissionRule o) {
int cmp = deny(this) - deny(o);
if (cmp == 0) cmp = range(o) - range(this);
if (cmp == 0) cmp = group(this).compareTo(group(o));
return cmp;
}
private static int deny(PermissionRule a) {
return a.getDeny() ? 1 : 0;
}
private static int range(PermissionRule a) {
return Math.abs(a.getMin()) + Math.abs(a.getMax());
}
private static String group(PermissionRule a) {
return a.getGroup().getName() != null ? a.getGroup().getName() : "";
}
@Override
public String toString() {
return asString(true);
}
public String asString(boolean canUseRange) {
StringBuilder r = new StringBuilder();
if (getDeny()) {
r.append("deny ");
}
if (getForce()) {
r.append("+force ");
}
if (canUseRange && (getMin() != 0 || getMax() != 0)) {
if (0 <= getMin()) r.append('+');
r.append(getMin());
r.append("..");
if (0 <= getMax()) r.append('+');
r.append(getMax());
r.append(' ');
}
r.append("group ");
r.append(getGroup().getName());
return r.toString();
}
public static PermissionRule fromString(String src, boolean mightUseRange) {
final String orig = src;
final PermissionRule rule = new PermissionRule();
src = src.trim();
if (src.startsWith("deny ")) {
rule.setDeny(true);
src = src.substring(5).trim();
}
if (src.startsWith("+force ")) {
rule.setForce(true);
src = src.substring("+force ".length()).trim();
}
if (mightUseRange && !src.startsWith("group ")) {
int sp = src.indexOf(' ');
String range = src.substring(0, sp);
if (range.matches("^([+-]\\d+)\\.\\.([+-]\\d)$")) {
int dotdot = range.indexOf("..");
int min = parseInt(range.substring(0, dotdot));
int max = parseInt(range.substring(dotdot + 2));
rule.setRange(min, max);
} else {
throw new IllegalArgumentException("Invalid range in rule: " + orig);
}
src = src.substring(sp + 1).trim();
}
if (src.startsWith("group ")) {
src = src.substring(6).trim();
GroupReference group = new GroupReference();
group.setName(src);
rule.setGroup(group);
} else {
throw new IllegalArgumentException("Rule must include group: " + orig);
}
return rule;
}
private static int parseInt(String value) {
if (value.startsWith("+")) {
value = value.substring(1);
}
return Integer.parseInt(value);
}
}

View File

@@ -28,6 +28,8 @@ import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountDiffPreference; import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences; import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage; import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch; import com.google.gerrit.reviewdb.Patch;
@@ -35,8 +37,6 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetInfo; import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.UserIdentity; import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ClickHandler;
@@ -54,7 +54,6 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel; import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -401,12 +400,8 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
private void populateActions(final PatchSetDetail detail) { private void populateActions(final PatchSetDetail detail) {
final boolean isOpen = changeDetail.getChange().getStatus().isOpen(); final boolean isOpen = changeDetail.getChange().getStatus().isOpen();
Set<ApprovalCategory.Id> allowed = changeDetail.getCurrentActions();
if (allowed == null) {
allowed = Collections.emptySet();
}
if (isOpen && allowed.contains(ApprovalCategory.SUBMIT)) { if (isOpen && changeDetail.canSubmit()) {
final Button b = final Button b =
new Button(Util.M new Button(Util.M
.submitPatchSet(detail.getPatchSet().getPatchSetId())); .submitPatchSet(detail.getPatchSet().getPatchSetId()));

View File

@@ -25,8 +25,11 @@ import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.client.ui.SmallHeading; import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail; import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.PatchSetPublishDetail; import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue; import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
@@ -218,16 +221,25 @@ public class PublishCommentScreen extends AccountScreen implements
} }
private void initApprovals(final PatchSetPublishDetail r, final Panel body) { private void initApprovals(final PatchSetPublishDetail r, final Panel body) {
for (final ApprovalType ct : Gerrit.getConfig().getApprovalTypes() ApprovalTypes types = Gerrit.getConfig().getApprovalTypes();
.getApprovalTypes()) {
if (r.isAllowed(ct.getCategory().getId())) { for (ApprovalType type : types.getApprovalTypes()) {
initApprovalType(r, body, ct); String permission = Permission.forLabel(type.getCategory().getLabelName());
PermissionRange range = r.getRange(permission);
if (range != null && !range.isEmpty()) {
initApprovalType(r, body, type, range);
}
}
for (PermissionRange range : r.getLabels()) {
if (!range.isEmpty() && types.byLabel(range.getLabel()) == null) {
// TODO: this is a non-standard label. Offer it without the type.
} }
} }
} }
private void initApprovalType(final PatchSetPublishDetail r, private void initApprovalType(final PatchSetPublishDetail r,
final Panel body, final ApprovalType ct) { final Panel body, final ApprovalType ct, final PermissionRange range) {
body.add(new SmallHeading(ct.getCategory().getName() + ":")); body.add(new SmallHeading(ct.getCategory().getName() + ":"));
final VerticalPanel vp = new VerticalPanel(); final VerticalPanel vp = new VerticalPanel();
@@ -236,11 +248,10 @@ public class PublishCommentScreen extends AccountScreen implements
new ArrayList<ApprovalCategoryValue>(ct.getValues()); new ArrayList<ApprovalCategoryValue>(ct.getValues());
Collections.reverse(lst); Collections.reverse(lst);
final ApprovalCategory.Id catId = ct.getCategory().getId(); final ApprovalCategory.Id catId = ct.getCategory().getId();
final Set<ApprovalCategoryValue.Id> allowed = r.getAllowed(catId);
final PatchSetApproval prior = r.getChangeApproval(catId); final PatchSetApproval prior = r.getChangeApproval(catId);
for (final ApprovalCategoryValue buttonValue : lst) { for (final ApprovalCategoryValue buttonValue : lst) {
if (!allowed.contains(buttonValue.getId())) { if (!range.contains(buttonValue.getValue())) {
continue; continue;
} }
@@ -306,7 +317,7 @@ public class PublishCommentScreen extends AccountScreen implements
} }
} }
submit.setVisible(r.isSubmitAllowed()); submit.setVisible(r.canSubmit());
} }
private void onSend(final boolean submit) { private void onSend(final boolean submit) {

View File

@@ -33,6 +33,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory; import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.CategoryFunction; import com.google.gerrit.server.workflow.CategoryFunction;
@@ -94,6 +95,7 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
if (patch == null) { if (patch == null) {
throw new NoSuchEntityException(); throw new NoSuchEntityException();
} }
final CanSubmitResult canSubmitResult = control.canSubmit(patch.getId());
aic.want(change.getOwner()); aic.want(change.getOwner());
@@ -103,6 +105,7 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon()); detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon());
detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore()); detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore());
detail.setCanSubmit(canSubmitResult == CanSubmitResult.OK);
detail.setStarred(control.getCurrentUser().getStarredChanges().contains( detail.setStarred(control.getCurrentUser().getStarredChanges().contains(
changeId)); changeId));
@@ -141,23 +144,13 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
final Set<ApprovalCategory.Id> missingApprovals = final Set<ApprovalCategory.Id> missingApprovals =
new HashSet<ApprovalCategory.Id>(); new HashSet<ApprovalCategory.Id>();
final Set<ApprovalCategory.Id> currentActions =
new HashSet<ApprovalCategory.Id>();
for (final ApprovalType at : approvalTypes.getApprovalTypes()) { for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(at.getCategory()).run(at, fs); CategoryFunction.forCategory(at.getCategory()).run(at, fs);
if (!fs.isValid(at)) { if (!fs.isValid(at)) {
missingApprovals.add(at.getCategory().getId()); missingApprovals.add(at.getCategory().getId());
} }
} }
for (final ApprovalType at : approvalTypes.getActionTypes()) {
if (CategoryFunction.forCategory(at.getCategory()).isValid(
control.getCurrentUser(), at, fs)) {
currentActions.add(at.getCategory().getId());
}
}
detail.setMissingApprovals(missingApprovals); detail.setMissingApprovals(missingApprovals);
detail.setCurrentActions(currentActions);
} }
final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() // final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() //

View File

@@ -15,19 +15,14 @@
package com.google.gerrit.httpd.rpc.changedetail; package com.google.gerrit.httpd.rpc.changedetail;
import com.google.gerrit.common.data.AccountInfoCache; import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.PatchSetPublishDetail; import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.httpd.rpc.Handler; import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment; import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo; import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory; import com.google.gerrit.server.account.AccountInfoCacheFactory;
@@ -36,27 +31,20 @@ import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.CanSubmitResult; import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import java.util.HashMap; import java.util.ArrayList;
import java.util.HashSet; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail> { final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail> {
interface Factory { interface Factory {
PatchSetPublishDetailFactory create(PatchSet.Id patchSetId); PatchSetPublishDetailFactory create(PatchSet.Id patchSetId);
} }
private final ProjectCache projectCache;
private final PatchSetInfoFactory infoFactory; private final PatchSetInfoFactory infoFactory;
private final ApprovalTypes approvalTypes;
private final ReviewDb db; private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory; private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory aic; private final AccountInfoCacheFactory aic;
@@ -68,19 +56,14 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
private PatchSetInfo patchSetInfo; private PatchSetInfo patchSetInfo;
private Change change; private Change change;
private List<PatchLineComment> drafts; private List<PatchLineComment> drafts;
private Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
private Map<ApprovalCategory.Id, PatchSetApproval> given;
@Inject @Inject
PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory, PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
final ProjectCache projectCache, final ApprovalTypes approvalTypes,
final ReviewDb db, final ReviewDb db,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory, final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final ChangeControl.Factory changeControlFactory, final ChangeControl.Factory changeControlFactory,
final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) { final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
this.projectCache = projectCache;
this.infoFactory = infoFactory; this.infoFactory = infoFactory;
this.approvalTypes = approvalTypes;
this.db = db; this.db = db;
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
this.aic = accountInfoCacheFactory.create(); this.aic = accountInfoCacheFactory.create();
@@ -98,15 +81,17 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
patchSetInfo = infoFactory.get(patchSetId); patchSetInfo = infoFactory.get(patchSetId);
drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList(); drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList();
allowed = new HashMap<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>>(); List<PermissionRange> allowed = Collections.emptyList();
given = new HashMap<ApprovalCategory.Id, PatchSetApproval>(); List<PatchSetApproval> given = Collections.emptyList();
if (change.getStatus().isOpen() if (change.getStatus().isOpen()
&& patchSetId.equals(change.currentPatchSetId())) { && patchSetId.equals(change.currentPatchSetId())) {
computeAllowed(); allowed = new ArrayList<PermissionRange>(control.getLabelRanges());
for (final PatchSetApproval a : db.patchSetApprovals().byPatchSetUser( Collections.sort(allowed);
patchSetId, user.getAccountId())) {
given.put(a.getCategoryId(), a); given = db.patchSetApprovals() //
} .byPatchSetUser(patchSetId, user.getAccountId()) //
.toList();
} }
aic.want(change.getOwner()); aic.want(change.getOwner());
@@ -117,46 +102,12 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
detail.setPatchSetInfo(patchSetInfo); detail.setPatchSetInfo(patchSetInfo);
detail.setChange(change); detail.setChange(change);
detail.setDrafts(drafts); detail.setDrafts(drafts);
detail.setAllowed(allowed); detail.setLabels(allowed);
detail.setGiven(given); detail.setGiven(given);
final CanSubmitResult canSubmitResult = control.canSubmit(patchSetId); final CanSubmitResult canSubmitResult = control.canSubmit(patchSetId);
detail.setSubmitAllowed(canSubmitResult == CanSubmitResult.OK); detail.setCanSubmit(canSubmitResult == CanSubmitResult.OK);
return detail; return detail;
} }
private void computeAllowed() {
final Set<AccountGroup.UUID> am = user.getEffectiveGroups();
final ProjectState pe = projectCache.get(change.getProject());
for (ApprovalCategory.Id category : approvalTypes.getApprovalCategories()) {
RefControl rc = pe.controlFor(user).controlForRef(change.getDest());
List<RefRight> categoryRights = rc.getApplicableRights(category);
computeAllowed(am, categoryRights, category);
}
}
private void computeAllowed(final Set<AccountGroup.UUID> am,
final List<RefRight> list, ApprovalCategory.Id category) {
Set<ApprovalCategoryValue.Id> s = allowed.get(category);
if (s == null) {
s = new HashSet<ApprovalCategoryValue.Id>();
allowed.put(category, s);
}
for (final RefRight r : list) {
if (!am.contains(r.getAccountGroupUUID())) {
continue;
}
final ApprovalType at =
approvalTypes.getApprovalType(r.getApprovalCategoryId());
for (short m = r.getMinValue(); m <= r.getMaxValue(); m++) {
final ApprovalCategoryValue v = at.getValue(m);
if (v != null) {
s.add(v.getId());
}
}
}
}
} }

View File

@@ -188,7 +188,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
.byPatchSetUser(ps_id, aid)) { .byPatchSetUser(ps_id, aid)) {
final ApprovalCategory.Id category = ca.getCategoryId(); final ApprovalCategory.Id category = ca.getCategoryId();
if (change.getStatus().isOpen()) { if (change.getStatus().isOpen()) {
fs.normalize(approvalTypes.getApprovalType(category), ca); fs.normalize(approvalTypes.byId(category), ca);
} }
if (ca.getValue() == 0 if (ca.getValue() == 0
|| ApprovalCategory.SUBMIT.equals(category)) { || ApprovalCategory.SUBMIT.equals(category)) {
@@ -232,7 +232,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(ps_id)) { for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(ps_id)) {
final ApprovalCategory.Id category = ca.getCategoryId(); final ApprovalCategory.Id category = ca.getCategoryId();
if (change.getStatus().isOpen()) { if (change.getStatus().isOpen()) {
fs.normalize(approvalTypes.getApprovalType(category), ca); fs.normalize(approvalTypes.byId(category), ca);
} }
if (ca.getValue() == 0 if (ca.getValue() == 0
|| ApprovalCategory.SUBMIT.equals(category)) { || ApprovalCategory.SUBMIT.equals(category)) {

View File

@@ -24,33 +24,6 @@ public final class ApprovalCategory {
public static final ApprovalCategory.Id SUBMIT = public static final ApprovalCategory.Id SUBMIT =
new ApprovalCategory.Id("SUBM"); new ApprovalCategory.Id("SUBM");
/** Id of the special "Read" action (and category). */
public static final ApprovalCategory.Id READ =
new ApprovalCategory.Id("READ");
/** Id of the special "Own" category; manages a project. */
public static final ApprovalCategory.Id OWN = new ApprovalCategory.Id("OWN");
/** Id of the special "Push Annotated Tag" action (and category). */
public static final ApprovalCategory.Id PUSH_TAG =
new ApprovalCategory.Id("pTAG");
public static final short PUSH_TAG_SIGNED = 1;
public static final short PUSH_TAG_ANNOTATED = 2;
/** Id of the special "Push Branch" action (and category). */
public static final ApprovalCategory.Id PUSH_HEAD =
new ApprovalCategory.Id("pHD");
public static final short PUSH_HEAD_UPDATE = 1;
public static final short PUSH_HEAD_CREATE = 2;
public static final short PUSH_HEAD_REPLACE = 3;
/** Id of the special "Forge Identity" category. */
public static final ApprovalCategory.Id FORGE_IDENTITY =
new ApprovalCategory.Id("FORG");
public static final short FORGE_AUTHOR = 1;
public static final short FORGE_COMMITTER = 2;
public static final short FORGE_SERVER = 3;
public static class Id extends StringKey<Key<?>> { public static class Id extends StringKey<Key<?>> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -73,15 +46,6 @@ public final class ApprovalCategory {
protected void set(String newValue) { protected void set(String newValue) {
id = newValue; id = newValue;
} }
/** True if the right can be assigned on the wild project. */
public boolean canBeOnWildProject() {
if (OWN.equals(this)) {
return false;
} else {
return true;
}
}
} }
/** Internal short unique identifier for this category. */ /** Internal short unique identifier for this category. */
@@ -96,16 +60,7 @@ public final class ApprovalCategory {
@Column(id = 3, length = 4, notNull = false) @Column(id = 3, length = 4, notNull = false)
protected String abbreviatedName; protected String abbreviatedName;
/** /** Order of this category within the Approvals table when presented. */
* Order of this category within the Approvals table when presented.
* <p>
* If < 0 (e.g. -1) this category is not shown in the Approvals table but is
* instead considered to be an action that the user might be able to perform,
* e.g. "Submit".
* <p>
* If >= 0 this category is shown in the Approvals table, sorted along with
* its siblings by <code>position, name</code>.
*/
@Column(id = 4) @Column(id = 4)
protected short position; protected short position;
@@ -117,6 +72,9 @@ public final class ApprovalCategory {
@Column(id = 6) @Column(id = 6)
protected boolean copyMinScore; protected boolean copyMinScore;
/** Computed name derived from {@link #name}. */
protected String labelName;
protected ApprovalCategory() { protected ApprovalCategory() {
} }
@@ -136,6 +94,26 @@ public final class ApprovalCategory {
public void setName(final String n) { public void setName(final String n) {
name = n; name = n;
labelName = null;
}
/** Clean version of {@link #getName()}, e.g. "Code Review" is "Code-Review". */
public String getLabelName() {
if (labelName == null) {
StringBuilder r = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (('0' <= c && c <= '9') //
|| ('a' <= c && c <= 'z') //
|| ('A' <= c && c <= 'Z')) {
r.append(c);
} else if (c == ' ') {
r.append('-');
}
}
labelName = r.toString();
}
return labelName;
} }
public String getAbbreviatedName() { public String getAbbreviatedName() {
@@ -154,14 +132,6 @@ public final class ApprovalCategory {
position = p; position = p;
} }
public boolean isAction() {
return position < 0;
}
public boolean isRange() {
return !isAction();
}
public String getFunctionName() { public String getFunctionName() {
return functionName; return functionName;
} }

View File

@@ -1,246 +0,0 @@
// Copyright (C) 2010 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.reviewdb;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
import com.google.gwtorm.client.StringKey;
import java.util.Comparator;
/** Grant to use an {@link ApprovalCategory} in the scope of a git ref. */
public final class RefRight {
/** Pattern that matches all references in a project. */
public static final String ALL = "refs/*";
/** Prefix that triggers a regular expression pattern. */
public static final String REGEX_PREFIX = "^";
public static class RefPattern extends
StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
protected String pattern;
protected RefPattern() {
}
public RefPattern(final String pattern) {
this.pattern = pattern;
}
@Override
public String get() {
return pattern;
}
@Override
protected void set(String pattern) {
this.pattern = pattern;
}
}
public static class Key extends CompoundKey<Project.NameKey> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
protected Project.NameKey projectName;
@Column(id = 2)
protected RefPattern refPattern;
@Column(id = 3)
protected ApprovalCategory.Id categoryId;
@Column(id = 4)
protected AccountGroup.Id groupId;
protected Key() {
projectName = new Project.NameKey();
refPattern = new RefPattern();
categoryId = new ApprovalCategory.Id();
groupId = new AccountGroup.Id();
}
public Key(final Project.NameKey projectName, final RefPattern refPattern,
final ApprovalCategory.Id categoryId, final AccountGroup.Id groupId) {
this.projectName = projectName;
this.refPattern = refPattern;
this.categoryId = categoryId;
this.groupId = groupId;
}
@Override
public Project.NameKey getParentKey() {
return projectName;
}
public Project.NameKey getProjectNameKey() {
return projectName;
}
public String getRefPattern() {
return refPattern.get();
}
public void setGroupId(AccountGroup.Id groupId) {
this.groupId = groupId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,
groupId};
}
}
@Column(id = 1, name = Column.NONE)
protected Key key;
@Column(id = 2)
protected short minValue;
@Column(id = 3)
protected short maxValue;
protected transient AccountGroup.UUID groupUUID;
protected RefRight() {
}
public RefRight(RefRight.Key key) {
this.key = key;
}
public RefRight(final RefRight refRight, final AccountGroup.UUID groupId) {
this(new RefRight.Key(refRight.getKey().projectName,
refRight.getKey().refPattern, refRight.getKey().categoryId, null));
setMinValue(refRight.getMinValue());
setMaxValue(refRight.getMaxValue());
setAccountGroupUUID(groupId);
}
public RefRight.Key getKey() {
return key;
}
public String getRefPattern() {
if (isExclusive()) {
return key.refPattern.get().substring(1);
}
return key.refPattern.get();
}
public String getRefPatternForDisplay() {
return key.refPattern.get();
}
public Project.NameKey getProjectNameKey() {
return getKey().getProjectNameKey();
}
public boolean isExclusive() {
return key.refPattern.get().startsWith("-");
}
public ApprovalCategory.Id getApprovalCategoryId() {
return key.categoryId;
}
public AccountGroup.Id getAccountGroupId() {
return key.groupId;
}
public AccountGroup.UUID getAccountGroupUUID() {
return groupUUID;
}
public void setAccountGroupUUID(AccountGroup.UUID uuid) {
groupUUID = uuid;
}
public short getMinValue() {
return minValue;
}
public void setMinValue(final short m) {
minValue = m;
}
public short getMaxValue() {
return maxValue;
}
public void setMaxValue(final short m) {
maxValue = m;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("{group :");
s.append(getAccountGroupId().get());
s.append(", proj :");
s.append(getProjectNameKey().get());
s.append(", cat :");
s.append(getApprovalCategoryId().get());
s.append(", pattern :");
s.append(getRefPatternForDisplay());
s.append(", min :");
s.append(getMinValue());
s.append(", max :");
s.append(getMaxValue());
s.append("}");
return s.toString();
}
@Override
public int hashCode() {
return getKey().hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof RefRight) {
RefRight a = this;
RefRight b = (RefRight) o;
return a.getKey().equals(b.getKey())
&& a.getMinValue() == b.getMinValue()
&& a.getMaxValue() == b.getMaxValue();
}
return false;
}
public static final Comparator<RefRight> REF_PATTERN_ORDER =
new Comparator<RefRight>() {
@Override
public int compare(RefRight a, RefRight b) {
int aLength = a.getRefPattern().length();
int bLength = b.getRefPattern().length();
if (bLength == aLength) {
ApprovalCategory.Id aCat = a.getApprovalCategoryId();
ApprovalCategory.Id bCat = b.getApprovalCategoryId();
if (aCat.get().equals(bCat.get())) {
return a.getRefPattern().compareTo(b.getRefPattern());
}
return a.getApprovalCategoryId().get()
.compareTo(b.getApprovalCategoryId().get());
}
return bLength - aLength;
}
};
}

View File

@@ -1,33 +0,0 @@
// Copyright (C) 2010 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.reviewdb;
import com.google.gwtorm.client.Access;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.PrimaryKey;
import com.google.gwtorm.client.Query;
import com.google.gwtorm.client.ResultSet;
public interface RefRightAccess extends Access<RefRight, RefRight.Key> {
@PrimaryKey("key")
RefRight get(RefRight.Key refRight) throws OrmException;
@Query("WHERE key.projectName = ?")
ResultSet<RefRight> byProject(Project.NameKey project) throws OrmException;
@Query("WHERE key.categoryId = ? AND key.groupId = ?")
ResultSet<RefRight> byCategoryGroup(ApprovalCategory.Id cat,
AccountGroup.Id group) throws OrmException;
}

View File

@@ -110,9 +110,6 @@ public interface ReviewDb extends Schema {
@Relation @Relation
PatchLineCommentAccess patchComments(); PatchLineCommentAccess patchComments();
@Relation
RefRightAccess refRights();
@Relation @Relation
TrackingIdAccess trackingIds(); TrackingIdAccess trackingIds();

View File

@@ -157,14 +157,6 @@ ON patch_set_ancestors (ancestor_revision);
-- @PrimaryKey covers: all, suggestByName -- @PrimaryKey covers: all, suggestByName
-- *********************************************************************
-- RefRightAccess
-- @PrimaryKey covers: byProject
-- covers: byCategoryGroup
CREATE INDEX ref_rights_byCatGroup
ON ref_rights (category_id, group_id);
-- ********************************************************************* -- *********************************************************************
-- TrackingIdAccess -- TrackingIdAccess
-- --

View File

@@ -239,14 +239,6 @@ ON patch_set_ancestors (ancestor_revision);
-- covers: ownedByGroup -- covers: ownedByGroup
-- *********************************************************************
-- RefRightAccess
-- @PrimaryKey covers: byProject
-- covers: byCategoryGroup
CREATE INDEX ref_rights_byCatGroup
ON ref_rights (category_id, group_id);
-- ********************************************************************* -- *********************************************************************
-- TrackingIdAccess -- TrackingIdAccess
-- --

View File

@@ -433,7 +433,7 @@ public class ChangeHookRunner {
Entry<ApprovalCategory.Id, ApprovalCategoryValue.Id> approval) { Entry<ApprovalCategory.Id, ApprovalCategoryValue.Id> approval) {
ApprovalAttribute a = new ApprovalAttribute(); ApprovalAttribute a = new ApprovalAttribute();
a.type = approval.getKey().get(); a.type = approval.getKey().get();
final ApprovalType at = approvalTypes.getApprovalType(approval.getKey()); final ApprovalType at = approvalTypes.byId(approval.getKey());
a.description = at.getCategory().getName(); a.description = at.getCategory().getName();
a.value = Short.toString(approval.getValue().get()); a.value = Short.toString(approval.getValue().get());
return a; return a;

View File

@@ -39,8 +39,7 @@ class ApprovalTypesProvider implements Provider<ApprovalTypes> {
@Override @Override
public ApprovalTypes get() { public ApprovalTypes get() {
List<ApprovalType> approvalTypes = new ArrayList<ApprovalType>(2); List<ApprovalType> types = new ArrayList<ApprovalType>(2);
List<ApprovalType> actionTypes = new ArrayList<ApprovalType>(2);
try { try {
final ReviewDb db = schema.open(); final ReviewDb db = schema.open();
@@ -48,12 +47,7 @@ class ApprovalTypesProvider implements Provider<ApprovalTypes> {
for (final ApprovalCategory c : db.approvalCategories().all()) { for (final ApprovalCategory c : db.approvalCategories().all()) {
final List<ApprovalCategoryValue> values = final List<ApprovalCategoryValue> values =
db.approvalCategoryValues().byCategory(c.getId()).toList(); db.approvalCategoryValues().byCategory(c.getId()).toList();
final ApprovalType type = new ApprovalType(c, values); types.add(new ApprovalType(c, values));
if (type.getCategory().isAction()) {
actionTypes.add(type);
} else {
approvalTypes.add(type);
}
} }
} finally { } finally {
db.close(); db.close();
@@ -62,8 +56,6 @@ class ApprovalTypesProvider implements Provider<ApprovalTypes> {
throw new ProvisionException("Cannot query approval categories", e); throw new ProvisionException("Cannot query approval categories", e);
} }
approvalTypes = Collections.unmodifiableList(approvalTypes); return new ApprovalTypes(Collections.unmodifiableList(types));
actionTypes = Collections.unmodifiableList(actionTypes);
return new ApprovalTypes(approvalTypes, actionTypes);
} }
} }

View File

@@ -212,7 +212,7 @@ public class EventFactory {
a.by = asAccountAttribute(approval.getAccountId()); a.by = asAccountAttribute(approval.getAccountId());
a.grantedOn = approval.getGranted().getTime() / 1000L; a.grantedOn = approval.getGranted().getTime() / 1000L;
ApprovalType at = approvalTypes.getApprovalType(approval.getCategoryId()); ApprovalType at = approvalTypes.byId(approval.getCategoryId());
if (at != null) { if (at != null) {
a.description = at.getCategory().getName(); a.description = at.getCategory().getName();
} }

View File

@@ -212,10 +212,13 @@ public class CreateCodeReviewNotes {
if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) { if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
submit = a; submit = a;
} else { } else {
formatter.appendApproval( ApprovalCategory type = approvalTypes.byId(a.getCategoryId()).getCategory();
approvalTypes.getApprovalType(a.getCategoryId()).getCategory(), if (type != null) {
a.getValue(), formatter.appendApproval(
accountCache.get(a.getAccountId()).getAccount()); type,
a.getValue(),
accountCache.get(a.getAccountId()).getAccount());
}
} }
} }

View File

@@ -805,7 +805,7 @@ public class MergeOp {
tag = "Tested-by"; tag = "Tested-by";
} else { } else {
final ApprovalType at = final ApprovalType at =
approvalTypes.getApprovalType(a.getCategoryId()); approvalTypes.byId(a.getCategoryId());
if (at == null) { if (at == null) {
// A deprecated/deleted approval type, ignore it. // A deprecated/deleted approval type, ignore it.
continue; continue;

View File

@@ -14,24 +14,45 @@
package com.google.gerrit.server.git; package com.google.gerrit.server.git;
import static com.google.gerrit.common.data.AccessSection.isAccessSection;
import static com.google.gerrit.common.data.Permission.isPermission;
import com.google.gerrit.common.data.AccessSection;
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.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.Project.SubmitType; import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gerrit.server.account.GroupCache;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ProjectConfig extends VersionedMetaData { public class ProjectConfig extends VersionedMetaData {
private static final String PROJECT_CONFIG = "project.config"; private static final String PROJECT_CONFIG = "project.config";
private static final String GROUP_LIST = "groups";
private static final String PROJECT = "project"; private static final String PROJECT = "project";
private static final String KEY_DESCRIPTION = "description"; private static final String KEY_DESCRIPTION = "description";
private static final String ACCESS = "access"; private static final String ACCESS = "access";
private static final String KEY_INHERIT_FROM = "inheritFrom"; private static final String KEY_INHERIT_FROM = "inheritFrom";
private static final String KEY_GROUP_PERMISSIONS = "exclusiveGroupPermissions";
private static final String RECEIVE = "receive"; private static final String RECEIVE = "receive";
private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy"; private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy";
@@ -48,6 +69,8 @@ public class ProjectConfig extends VersionedMetaData {
private Project.NameKey projectName; private Project.NameKey projectName;
private Project project; private Project project;
private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
private Map<String, AccessSection> accessSections;
public static ProjectConfig read(MetaDataUpdate update) throws IOException, public static ProjectConfig read(MetaDataUpdate update) throws IOException,
ConfigInvalidException { ConfigInvalidException {
@@ -71,6 +94,60 @@ public class ProjectConfig extends VersionedMetaData {
return project; return project;
} }
public AccessSection getAccessSection(String name) {
return getAccessSection(name, false);
}
public AccessSection getAccessSection(String name, boolean create) {
AccessSection as = accessSections.get(name);
if (as == null && create) {
as = new AccessSection(name);
accessSections.put(name, as);
}
return as;
}
public Collection<AccessSection> getAccessSections() {
return accessSections.values();
}
public void remove(AccessSection section) {
accessSections.remove(section.getRefPattern());
}
public GroupReference resolve(AccountGroup group) {
return resolve(GroupReference.forGroup(group));
}
public GroupReference resolve(GroupReference group) {
if (group != null) {
GroupReference ref = groupsByUUID.get(group.getUUID());
if (ref != null) {
return ref;
}
groupsByUUID.put(group.getUUID(), group);
}
return group;
}
/**
* Check all GroupReferences use current group name, repairing stale ones.
*
* @param groupCache cache to use when looking up group information by UUID.
* @return true if one or more group names was stale.
*/
public boolean updateGroupNames(GroupCache groupCache) {
boolean dirty = false;
for (GroupReference ref : groupsByUUID.values()) {
AccountGroup g = groupCache.get(ref.getUUID());
if (g != null && !g.getName().equals(ref.getName())) {
dirty = true;
ref.setName(g.getName());
}
}
return dirty;
}
@Override @Override
protected String getRefName() { protected String getRefName() {
return GitRepositoryManager.REF_CONFIG; return GitRepositoryManager.REF_CONFIG;
@@ -78,6 +155,8 @@ public class ProjectConfig extends VersionedMetaData {
@Override @Override
protected void onLoad() throws IOException, ConfigInvalidException { protected void onLoad() throws IOException, ConfigInvalidException {
Map<String,GroupReference> groupsByName = readGroupList();
Config rc = readConfig(PROJECT_CONFIG); Config rc = readConfig(PROJECT_CONFIG);
project = new Project(projectName); project = new Project(projectName);
@@ -94,6 +173,80 @@ public class ProjectConfig extends VersionedMetaData {
p.setSubmitType(rc.getEnum(SUBMIT, null, KEY_ACTION, defaultSubmitAction)); p.setSubmitType(rc.getEnum(SUBMIT, null, KEY_ACTION, defaultSubmitAction));
p.setUseContentMerge(rc.getBoolean(SUBMIT, null, KEY_MERGE_CONTENT, false)); p.setUseContentMerge(rc.getBoolean(SUBMIT, null, KEY_MERGE_CONTENT, false));
accessSections = new HashMap<String, AccessSection>();
for (String refName : rc.getSubsections(ACCESS)) {
if (isAccessSection(refName)) {
AccessSection as = getAccessSection(refName, true);
for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
for (String n : varName.split("[, \t]{1,}")) {
if (isPermission(n)) {
as.getPermission(n, true).setExclusiveGroup(true);
}
}
}
for (String varName : rc.getNames(ACCESS, refName)) {
if (isPermission(varName)) {
Permission perm = as.getPermission(varName, true);
boolean useRange = perm.isLabel();
for (String ruleString : rc.getStringList(ACCESS, refName, varName)) {
PermissionRule rule;
try {
rule = PermissionRule.fromString(ruleString, useRange);
} catch (IllegalArgumentException notRule) {
throw new ConfigInvalidException("Invalid rule in " + ACCESS
+ "." + refName + "." + varName + ": "
+ notRule.getMessage(), notRule);
}
GroupReference ref = groupsByName.get(rule.getGroup().getName());
if (ref == null) {
// The group wasn't mentioned in the groups table, so there is
// no valid UUID for it. Pool the reference anyway so at least
// all rules in the same file share the same GroupReference.
//
ref = rule.getGroup();
groupsByName.put(ref.getName(), ref);
}
rule.setGroup(ref);
perm.add(rule);
}
}
}
}
}
}
private Map<String, GroupReference> readGroupList() throws IOException,
ConfigInvalidException {
groupsByUUID = new HashMap<AccountGroup.UUID, GroupReference>();
Map<String, GroupReference> groupsByName =
new HashMap<String, GroupReference>();
BufferedReader br = new BufferedReader(new StringReader(readUTF8(GROUP_LIST)));
String s;
while ((s = br.readLine()) != null) {
if (s.isEmpty() || s.startsWith("#")) {
continue;
}
int tab = s.indexOf('\t');
if (tab < 0) {
throw new ConfigInvalidException("Invalid group line: " + s);
}
AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim());
String name = s.substring(tab + 1).trim();
GroupReference ref = new GroupReference(uuid, name);
groupsByUUID.put(uuid, ref);
groupsByName.put(name, ref);
}
return groupsByName;
} }
@Override @Override
@@ -120,6 +273,102 @@ public class ProjectConfig extends VersionedMetaData {
set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction); set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.isUseContentMerge()); set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.isUseContentMerge());
Set<AccountGroup.UUID> keepGroups = new HashSet<AccountGroup.UUID>();
for (AccessSection as : sort(accessSections.values())) {
String refName = as.getRefPattern();
StringBuilder doNotInherit = new StringBuilder();
for (Permission perm : sort(as.getPermissions())) {
if (perm.getExclusiveGroup()) {
if (0 < doNotInherit.length()) {
doNotInherit.append(' ');
}
doNotInherit.append(perm.getName());
}
}
if (0 < doNotInherit.length()) {
rc.setString(ACCESS, refName, KEY_GROUP_PERMISSIONS, doNotInherit.toString());
} else {
rc.unset(ACCESS, refName, KEY_GROUP_PERMISSIONS);
}
Set<String> have = new HashSet<String>();
for (Permission permission : sort(as.getPermissions())) {
have.add(permission.getName().toLowerCase());
boolean needRange = permission.isLabel();
List<String> rules = new ArrayList<String>();
for (PermissionRule rule : sort(permission.getRules())) {
GroupReference group = rule.getGroup();
if (group.getUUID() != null) {
keepGroups.add(group.getUUID());
}
rules.add(rule.asString(needRange));
}
rc.setStringList(ACCESS, refName, permission.getName(), rules);
}
for (String varName : rc.getNames(ACCESS, refName)) {
if (isPermission(varName) && !have.contains(varName.toLowerCase())) {
rc.unset(ACCESS, refName, varName);
}
}
}
for (String name : rc.getSubsections(ACCESS)) {
if (isAccessSection(name) && !accessSections.containsKey(name)) {
rc.unsetSection(ACCESS, name);
}
}
groupsByUUID.keySet().retainAll(keepGroups);
saveConfig(PROJECT_CONFIG, rc); saveConfig(PROJECT_CONFIG, rc);
saveGroupList();
}
private void saveGroupList() throws IOException {
if (groupsByUUID.isEmpty()) {
saveFile(GROUP_LIST, null);
return;
}
final int uuidLen = 40;
StringBuilder buf = new StringBuilder();
buf.append(pad(uuidLen, "# UUID"));
buf.append('\t');
buf.append("Group Name");
buf.append('\n');
buf.append('#');
buf.append('\n');
for (GroupReference g : sort(groupsByUUID.values())) {
if (g.getUUID() != null && g.getName() != null) {
buf.append(pad(uuidLen, g.getUUID().get()));
buf.append('\t');
buf.append(g.getName());
buf.append('\n');
}
}
saveUTF8(GROUP_LIST, buf.toString());
}
private static String pad(int len, String src) {
if (len <= src.length()) {
return src;
}
StringBuilder r = new StringBuilder(len);
r.append(src);
while (r.length() < len) {
r.append(' ');
}
return r.toString();
}
private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
ArrayList<T> r = new ArrayList<T>(m);
Collections.sort(r);
return r;
} }
} }

View File

@@ -1359,7 +1359,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
} }
final ApprovalType type = final ApprovalType type =
approvalTypes.getApprovalType(a.getCategoryId()); approvalTypes.byId(a.getCategoryId());
if (a.getPatchSetId().equals(priorPatchSet) if (a.getPatchSetId().equals(priorPatchSet)
&& type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) { && type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
// If there was a negative vote on the prior patch set, carry it // If there was a negative vote on the prior patch set, carry it

View File

@@ -51,8 +51,7 @@ class ReviewNoteHeaderFormatter {
void appendApproval(ApprovalCategory category, void appendApproval(ApprovalCategory category,
short value, Account user) { short value, Account user) {
// TODO: use category.getLabel() when available sb.append(category.getLabelName());
sb.append(category.getName().replace(' ', '-'));
sb.append(value < 0 ? "-" : "+").append(Math.abs(value)).append(": "); sb.append(value < 0 ? "-" : "+").append(Math.abs(value)).append(": ");
appendUserData(user); appendUserData(user);
sb.append("\n"); sb.append("\n");

View File

@@ -69,6 +69,18 @@ public abstract class VersionedMetaData {
return revision.copy(); return revision.copy();
} }
/** Initialize in-memory as though the repository branch doesn't exist. */
public void createInMemory() {
try {
revision = null;
onLoad();
} catch (IOException err) {
throw new RuntimeException("Unexpected IOException", err);
} catch (ConfigInvalidException err) {
throw new RuntimeException("Unexpected ConfigInvalidException", err);
}
}
/** /**
* Load the current version from the branch. * Load the current version from the branch.
* <p> * <p>

View File

@@ -171,7 +171,7 @@ public class PublishComments implements Callable<VoidResult> {
final short o = a.getValue(); final short o = a.getValue();
a.setValue(want.get()); a.setValue(want.get());
a.cache(change); a.cache(change);
functionState.normalize(types.getApprovalType(a.getCategoryId()), a); functionState.normalize(types.byId(a.getCategoryId()), a);
if (o != a.getValue()) { if (o != a.getValue()) {
// Value changed, ensure we update the database. // Value changed, ensure we update the database.
// //

View File

@@ -16,13 +16,12 @@ package com.google.gerrit.server.project;
import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.workflow.CategoryFunction; import com.google.gerrit.server.workflow.CategoryFunction;
@@ -31,7 +30,6 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -164,8 +162,14 @@ public class ChangeControl {
return canAbandon(); // Anyone who can abandon the change can restore it back return canAbandon(); // Anyone who can abandon the change can restore it back
} }
public short normalize(ApprovalCategory.Id category, short score) { /** All value ranges of any allowed label permission. */
return getRefControl().normalize(category, score); public List<PermissionRange> getLabelRanges() {
return getRefControl().getLabelRanges();
}
/** The range of permitted values associated with a label permission. */
public PermissionRange getRange(String permission) {
return getRefControl().getRange(permission);
} }
/** Can this user add a patch set to this change? */ /** Can this user add a patch set to this change? */
@@ -240,34 +244,22 @@ public class ChangeControl {
return result; return result;
} }
final List<PatchSetApproval> allApprovals = final List<PatchSetApproval> all =
new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet( db.patchSetApprovals().byPatchSet(patchSetId).toList();
patchSetId).toList());
final PatchSetApproval myAction =
ChangeUtil.createSubmitApproval(patchSetId,
(IdentifiedUser) getCurrentUser(), db);
final ApprovalType actionType =
approvalTypes.getApprovalType(myAction.getCategoryId());
if (actionType == null || !actionType.getCategory().isAction()) {
return new CanSubmitResult("Invalid action " + myAction.getCategoryId());
}
final FunctionState fs = final FunctionState fs =
functionStateFactory.create(change, patchSetId, allApprovals); functionStateFactory.create(change, patchSetId, all);
for (ApprovalType c : approvalTypes.getApprovalTypes()) { for (ApprovalType c : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(c.getCategory()).run(c, fs); CategoryFunction.forCategory(c.getCategory()).run(c, fs);
} }
if (!CategoryFunction.forCategory(actionType.getCategory()).isValid(
getCurrentUser(), actionType, fs)) { for (ApprovalType type : approvalTypes.getApprovalTypes()) {
return new CanSubmitResult(actionType.getCategory().getName() if (!fs.isValid(type)) {
+ " not permitted"); return new CanSubmitResult("Requires " + type.getCategory().getName());
} }
fs.normalize(actionType, myAction);
if (myAction.getValue() <= 0) {
return new CanSubmitResult(actionType.getCategory().getName()
+ " not permitted");
} }
return CanSubmitResult.OK; return CanSubmitResult.OK;
} }
} }

View File

@@ -29,9 +29,6 @@ public interface ProjectCache {
/** Invalidate the cached information about the given project. */ /** Invalidate the cached information about the given project. */
public void evict(Project p); public void evict(Project p);
/** Invalidate the cached information about all projects. */
public void evictAll();
/** @return sorted iteration of projects. */ /** @return sorted iteration of projects. */
public abstract Iterable<Project.NameKey> all(); public abstract Iterable<Project.NameKey> all();

View File

@@ -14,9 +14,7 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule; import com.google.gerrit.server.cache.CacheModule;
@@ -33,13 +31,9 @@ import com.google.inject.name.Named;
import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@@ -99,11 +93,6 @@ public class ProjectCacheImpl implements ProjectCache {
} }
} }
/** Invalidate the cached information about all projects. */
public void evictAll() {
byName.removeAll();
}
@Override @Override
public void onCreateProject(Project.NameKey newProjectName) { public void onCreateProject(Project.NameKey newProjectName) {
listLock.lock(); listLock.lock();
@@ -193,30 +182,7 @@ public class ProjectCacheImpl implements ProjectCache {
try { try {
final ProjectConfig cfg = new ProjectConfig(key); final ProjectConfig cfg = new ProjectConfig(key);
cfg.load(git); cfg.load(git);
return projectStateFactory.create(cfg);
final Project p = cfg.getProject();
Collection<RefRight> rights = db.refRights().byProject(key).toList();
Set<AccountGroup.Id> groupIds = new HashSet<AccountGroup.Id>();
for (RefRight r : rights) {
groupIds.add(r.getAccountGroupId());
}
Map<AccountGroup.Id, AccountGroup> groupsById =
db.accountGroups().toMap(db.accountGroups().get(groupIds));
for (RefRight r : rights) {
AccountGroup group = groupsById.get(r.getAccountGroupId());
if (group != null) {
r.setAccountGroupUUID(group.getGroupUUID());
} else {
r.setAccountGroupUUID(new AccountGroup.UUID("DELETED_GROUP_"
+ r.getAccountGroupId().get()));
}
}
rights = Collections.unmodifiableCollection(rights);
return projectStateFactory.create(p, rights);
} finally { } finally {
git.close(); git.close();
} }

View File

@@ -14,13 +14,15 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import static com.google.gerrit.common.CollectionsUtil.*; import static com.google.gerrit.common.CollectionsUtil.isAnyIncludedIn;
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.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch; import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser; import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.config.GitReceivePackGroups; import com.google.gerrit.server.config.GitReceivePackGroups;
@@ -29,6 +31,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -108,6 +111,8 @@ public class ProjectControl {
private final CurrentUser user; private final CurrentUser user;
private final ProjectState state; private final ProjectState state;
private Collection<AccessSection> access;
@Inject @Inject
ProjectControl(@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups, ProjectControl(@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
@GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups, @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
@@ -155,18 +160,18 @@ public class ProjectControl {
/** Can this user see this project exists? */ /** Can this user see this project exists? */
public boolean isVisible() { public boolean isVisible() {
return visibleForReplication() return visibleForReplication()
|| canPerformOnAnyRef(ApprovalCategory.READ, (short) 1); || canPerformOnAnyRef(Permission.READ);
} }
public boolean canAddRefs() { public boolean canAddRefs() {
return (canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, ApprovalCategory.PUSH_HEAD_CREATE) return (canPerformOnAnyRef(Permission.CREATE)
|| isOwnerAnyRef()); || isOwnerAnyRef());
} }
/** Can this user see all the refs in this projects? */ /** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() { public boolean allRefsAreVisible() {
return visibleForReplication() return visibleForReplication()
|| canPerformOnAllRefs(ApprovalCategory.READ, (short) 1); || canPerformOnAllRefs(Permission.READ);
} }
/** Is this project completely visible for replication? */ /** Is this project completely visible for replication? */
@@ -177,49 +182,60 @@ public class ProjectControl {
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */ /** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
public boolean isOwner() { public boolean isOwner() {
return controlForRef(RefRight.ALL).isOwner() return controlForRef(AccessSection.ALL).isOwner()
|| getCurrentUser().isAdministrator(); || getCurrentUser().isAdministrator();
} }
/** Does this user have ownership on at least one reference name? */ /** Does this user have ownership on at least one reference name? */
public boolean isOwnerAnyRef() { public boolean isOwnerAnyRef() {
return canPerformOnAnyRef(ApprovalCategory.OWN, (short) 1) return canPerformOnAnyRef(Permission.OWNER)
|| getCurrentUser().isAdministrator(); || getCurrentUser().isAdministrator();
} }
/** @return true if the user can upload to at least one reference */ /** @return true if the user can upload to at least one reference */
public boolean canPushToAtLeastOneRef() { public boolean canPushToAtLeastOneRef() {
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2) return canPerformOnAnyRef(Permission.PUSH)
|| canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, (short) 1) || canPerformOnAnyRef(Permission.PUSH_TAG);
|| canPerformOnAnyRef(ApprovalCategory.PUSH_TAG, (short) 1);
} }
// TODO (anatol.pomazau): Try to merge this method with similar RefRightsForPattern#canPerform private boolean canPerformOnAnyRef(String permissionName) {
private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId,
short requireValue) {
final Set<AccountGroup.UUID> groups = user.getEffectiveGroups(); final Set<AccountGroup.UUID> groups = user.getEffectiveGroups();
for (final RefRight pr : state.getAllRights(actionId, true)) { for (AccessSection section : access()) {
if (groups.contains(pr.getAccountGroupUUID()) Permission permission = section.getPermission(permissionName);
&& pr.getMaxValue() >= requireValue) { if (permission == null) {
return true; continue;
}
for (PermissionRule rule : permission.getRules()) {
if (rule.getDeny()) {
continue;
}
// Being in a group that was granted this permission is only an
// approximation. There might be overrides and doNotInherit
// that would render this to be false.
//
if (groups.contains(rule.getGroup().getUUID())
&& controlForRef(section.getRefPattern()).canPerform(permissionName)) {
return true;
}
} }
} }
return false; return false;
} }
private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId, private boolean canPerformOnAllRefs(String permission) {
short requireValue) {
boolean canPerform = false; boolean canPerform = false;
final Set<String> patterns = allRefPatterns(actionId); Set<String> patterns = allRefPatterns(permission);
if (patterns.contains(RefRight.ALL)) { if (patterns.contains(AccessSection.ALL)) {
// Only possible if granted on the pattern that // Only possible if granted on the pattern that
// matches every possible reference. Check all // matches every possible reference. Check all
// patterns also have the permission. // patterns also have the permission.
// //
for (final String pattern : patterns) { for (final String pattern : patterns) {
if (controlForRef(pattern).canPerform(actionId, requireValue)) { if (controlForRef(pattern).canPerform(permission)) {
canPerform = true; canPerform = true;
} else { } else {
return false; return false;
@@ -229,14 +245,24 @@ public class ProjectControl {
return canPerform; return canPerform;
} }
private Set<String> allRefPatterns(ApprovalCategory.Id actionId) { private Set<String> allRefPatterns(String permissionName) {
final Set<String> all = new HashSet<String>(); Set<String> all = new HashSet<String>();
for (final RefRight pr : state.getAllRights(actionId, true)) { for (AccessSection section : access()) {
all.add(pr.getRefPattern()); Permission permission = section.getPermission(permissionName);
if (permission != null) {
all.add(section.getRefPattern());
}
} }
return all; return all;
} }
Collection<AccessSection> access() {
if (access == null) {
access = state.getAllAccessSections();
}
return access;
}
public boolean canRunUploadPack() { public boolean canRunUploadPack() {
return isAnyIncludedIn(uploadGroups, user.getEffectiveGroups()); return isAnyIncludedIn(uploadGroups, user.getEffectiveGroups());
} }

View File

@@ -14,13 +14,16 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import com.google.gerrit.common.data.AccessSection;
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.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.WildProjectName; import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -28,15 +31,13 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
/** Cached information on a project. */ /** Cached information on a project. */
public class ProjectState { public class ProjectState {
public interface Factory { public interface Factory {
ProjectState create(Project project, Collection<RefRight> localRights); ProjectState create(ProjectConfig config);
} }
private final AnonymousUser anonymousUser; private final AnonymousUser anonymousUser;
@@ -44,92 +45,64 @@ public class ProjectState {
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final ProjectControl.AssistedFactory projectControlFactory; private final ProjectControl.AssistedFactory projectControlFactory;
private final Project project; private final ProjectConfig config;
private final Collection<RefRight> localRights;
private final Set<AccountGroup.UUID> localOwners; private final Set<AccountGroup.UUID> localOwners;
private volatile Collection<RefRight> inheritedRights;
@Inject @Inject
protected ProjectState(final AnonymousUser anonymousUser, protected ProjectState(final AnonymousUser anonymousUser,
final ProjectCache projectCache, final ProjectCache projectCache,
@WildProjectName final Project.NameKey wildProject, @WildProjectName final Project.NameKey wildProject,
final ProjectControl.AssistedFactory projectControlFactory, final ProjectControl.AssistedFactory projectControlFactory,
@Assisted final Project project, @Assisted final ProjectConfig config) {
@Assisted Collection<RefRight> rights) {
this.anonymousUser = anonymousUser; this.anonymousUser = anonymousUser;
this.projectCache = projectCache; this.projectCache = projectCache;
this.wildProject = wildProject; this.wildProject = wildProject;
this.projectControlFactory = projectControlFactory; this.projectControlFactory = projectControlFactory;
this.config = config;
if (wildProject.equals(project.getNameKey())) { HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
rights = new ArrayList<RefRight>(rights); AccessSection all = config.getAccessSection(AccessSection.ALL);
for (Iterator<RefRight> itr = rights.iterator(); itr.hasNext();) { if (all != null) {
if (!itr.next().getApprovalCategoryId().canBeOnWildProject()) { Permission owner = all.getPermission(Permission.OWNER);
itr.remove(); if (owner != null) {
for (PermissionRule rule : owner.getRules()) {
GroupReference ref = rule.getGroup();
if (ref.getUUID() != null) {
groups.add(ref.getUUID());
}
} }
} }
rights = Collections.unmodifiableCollection(rights);
}
this.project = project;
this.localRights = rights;
final HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
for (final RefRight right : rights) {
if (ApprovalCategory.OWN.equals(right.getApprovalCategoryId())
&& right.getMaxValue() > 0
&& right.getRefPattern().equals(RefRight.ALL)) {
groups.add(right.getAccountGroupUUID());
}
} }
localOwners = Collections.unmodifiableSet(groups); localOwners = Collections.unmodifiableSet(groups);
} }
public Project getProject() { public Project getProject() {
return project; return getConfig().getProject();
}
public ProjectConfig getConfig() {
return config;
} }
/** Get the rights that pertain only to this project. */ /** Get the rights that pertain only to this project. */
public Collection<RefRight> getLocalRights() { public Collection<AccessSection> getLocalAccessSections() {
return localRights; return getConfig().getAccessSections();
} }
/** /** Get the rights this project inherits. */
* Get the rights that pertain only to this project. public Collection<AccessSection> getInheritedAccessSections() {
* if (isWildProject()) {
* @param action the category requested.
* @return immutable collection of rights for the requested category.
*/
public Collection<RefRight> getLocalRights(ApprovalCategory.Id action) {
return filter(getLocalRights(), action);
}
/** Get the rights this project inherits from the wild project. */
public Collection<RefRight> getInheritedRights() {
if (inheritedRights == null) {
inheritedRights = computeInheritedRights();
}
return inheritedRights;
}
void setInheritedRights(Collection<RefRight> all) {
inheritedRights = all;
}
private Collection<RefRight> computeInheritedRights() {
if (isSpecialWildProject()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<RefRight> inherited = new ArrayList<RefRight>(); List<AccessSection> inherited = new ArrayList<AccessSection>();
Set<Project.NameKey> seen = new HashSet<Project.NameKey>(); Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
Project.NameKey parent = project.getParent(); Project.NameKey parent = getProject().getParent();
while (parent != null && seen.add(parent)) { while (parent != null && seen.add(parent)) {
ProjectState s = projectCache.get(parent); ProjectState s = projectCache.get(parent);
if (s != null) { if (s != null) {
inherited.addAll(s.getLocalRights()); inherited.addAll(s.getLocalAccessSections());
parent = s.getProject().getParent(); parent = s.getProject().getParent();
} else { } else {
break; break;
@@ -138,76 +111,21 @@ public class ProjectState {
// Wild project is the parent, or the root of the tree // Wild project is the parent, or the root of the tree
if (parent == null) { if (parent == null) {
inherited.addAll(getWildProjectRights()); ProjectState s = projectCache.get(wildProject);
} if (s != null) {
inherited.addAll(s.getLocalAccessSections());
return Collections.unmodifiableCollection(inherited);
}
private Collection<RefRight> getWildProjectRights() {
final ProjectState s = projectCache.get(wildProject);
return s != null ? s.getLocalRights() : Collections.<RefRight> emptyList();
}
/**
* Utility class that is needed to filter overridden refrights
*/
private static class Grant {
final AccountGroup.Id group;
final String pattern;
private Grant(AccountGroup.Id group, String pattern) {
this.group = group;
this.pattern = pattern;
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
Grant grant = (Grant) o;
return group.equals(grant.group) && pattern.equals(grant.pattern);
}
@Override
public int hashCode() {
int result = group.hashCode();
result = 31 * result + pattern.hashCode();
return result;
}
}
/**
* Get the rights this project has and inherits from the wild project.
*
* @param action the category requested.
* @param dropOverridden whether to remove inherited permissions in case if we have a
* local one that matches (action,group,ref)
* @return immutable collection of rights for the requested category.
*/
public Collection<RefRight> getAllRights(ApprovalCategory.Id action, boolean dropOverridden) {
Collection<RefRight> rights = new LinkedList<RefRight>(getLocalRights(action));
rights.addAll(filter(getInheritedRights(), action));
if (dropOverridden) {
Set<Grant> grants = new HashSet<Grant>();
Iterator<RefRight> iter = rights.iterator();
while (iter.hasNext()) {
RefRight right = iter.next();
Grant grant = new Grant(right.getAccountGroupId(), right.getRefPattern());
if (grants.contains(grant)) {
iter.remove();
} else {
grants.add(grant);
}
} }
} }
return Collections.unmodifiableCollection(rights);
return inherited;
} }
/** Is this the special wild project which manages inherited rights? */ /** Get both local and inherited access sections. */
public boolean isSpecialWildProject() { public Collection<AccessSection> getAllAccessSections() {
return project.getNameKey().equals(wildProject); List<AccessSection> all = new ArrayList<AccessSection>();
all.addAll(getLocalAccessSections());
all.addAll(getInheritedAccessSections());
return all;
} }
/** /**
@@ -217,12 +135,12 @@ public class ProjectState {
* that has local owners are returned * that has local owners are returned
*/ */
public Set<AccountGroup.UUID> getOwners() { public Set<AccountGroup.UUID> getOwners() {
if (!localOwners.isEmpty() || isSpecialWildProject() Project.NameKey parentName = getProject().getParent();
|| project.getParent() == null) { if (!localOwners.isEmpty() || parentName == null || isWildProject()) {
return localOwners; return localOwners;
} }
final ProjectState parent = projectCache.get(project.getParent()); ProjectState parent = projectCache.get(parentName);
if (parent != null) { if (parent != null) {
return parent.getOwners(); return parent.getOwners();
} }
@@ -238,12 +156,22 @@ public class ProjectState {
* assigned for one of the parent projects (the inherited owners). * assigned for one of the parent projects (the inherited owners).
*/ */
public Set<AccountGroup.UUID> getAllOwners() { public Set<AccountGroup.UUID> getAllOwners() {
final HashSet<AccountGroup.UUID> owners = new HashSet<AccountGroup.UUID>(); HashSet<AccountGroup.UUID> owners = new HashSet<AccountGroup.UUID>();
for (final RefRight right : getAllRights(ApprovalCategory.OWN, true)) { owners.addAll(localOwners);
if (right.getMaxValue() > 0 && right.getRefPattern().equals(RefRight.ALL)) {
owners.add(right.getAccountGroupUUID()); Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
Project.NameKey parent = getProject().getParent();
while (parent != null && seen.add(parent)) {
ProjectState s = projectCache.get(parent);
if (s != null) {
owners.addAll(s.localOwners);
parent = s.getProject().getParent();
} else {
break;
} }
} }
return Collections.unmodifiableSet(owners); return Collections.unmodifiableSet(owners);
} }
@@ -255,20 +183,7 @@ public class ProjectState {
return projectControlFactory.create(user, this); return projectControlFactory.create(user, this);
} }
private static Collection<RefRight> filter(Collection<RefRight> all, private boolean isWildProject() {
ApprovalCategory.Id actionId) { return wildProject.equals(getProject().getNameKey());
if (all.isEmpty()) {
return Collections.emptyList();
}
final Collection<RefRight> mine = new ArrayList<RefRight>(all.size());
for (final RefRight right : all) {
if (right.getApprovalCategoryId().equals(actionId)) {
mine.add(right);
}
}
if (mine.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(mine);
} }
} }

View File

@@ -14,25 +14,13 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_AUTHOR; import com.google.gerrit.common.CollectionsUtil;
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_COMMITTER; import com.google.gerrit.common.data.AccessSection;
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_IDENTITY;
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_SERVER;
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_UPDATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_SIGNED;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
import com.google.gerrit.common.data.ParamertizedString; import com.google.gerrit.common.data.ParamertizedString;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -49,7 +37,6 @@ import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@@ -57,8 +44,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -71,6 +56,9 @@ public class RefControl {
private final ProjectControl projectControl; private final ProjectControl projectControl;
private final String refName; private final String refName;
private Map<String, List<PermissionRule>> permissions;
private Boolean owner;
private Boolean canForgeAuthor; private Boolean canForgeAuthor;
private Boolean canForgeCommitter; private Boolean canForgeCommitter;
@@ -111,26 +99,29 @@ public class RefControl {
/** Is this user a ref owner? */ /** Is this user a ref owner? */
public boolean isOwner() { public boolean isOwner() {
if (canPerform(OWN, (short) 1)) { if (owner == null) {
return true; if (canPerform(Permission.OWNER)) {
} owner = true;
// We have to prevent infinite recursion here, the project control } else if (getRefName().equals(
// calls us to find out if there is ownership of all references in AccessSection.ALL.substring(0, AccessSection.ALL.length() - 1))) {
// order to determine project level ownership. // We have to prevent infinite recursion here, the project control
// // calls us to find out if there is ownership of all references in
if (getRefName().equals( // order to determine project level ownership.
RefRight.ALL.substring(0, RefRight.ALL.length() - 1))) { //
return getCurrentUser().isAdministrator(); owner = getCurrentUser().isAdministrator();
} else {
return getProjectControl().isOwner(); } else {
owner = getProjectControl().isOwner();
}
} }
return owner;
} }
/** Can this user see this reference exists? */ /** Can this user see this reference exists? */
public boolean isVisible() { public boolean isVisible() {
return getProjectControl().visibleForReplication() return getProjectControl().visibleForReplication()
|| canPerform(READ, (short) 1); || canPerform(Permission.READ);
} }
/** /**
@@ -141,27 +132,40 @@ public class RefControl {
* ref * ref
*/ */
public boolean canUpload() { public boolean canUpload() {
return canPerform(READ, (short) 2); return getProjectControl()
.controlForRef("refs/for/" + getRefName())
.canPerform(Permission.PUSH);
} }
/** @return true if this user can submit merge patch sets to this ref */ /** @return true if this user can submit merge patch sets to this ref */
public boolean canUploadMerges() { public boolean canUploadMerges() {
return canPerform(READ, (short) 3); return getProjectControl()
.controlForRef("refs/for/" + getRefName())
.canPerform(Permission.PUSH_MERGE);
} }
/** @return true if this user can submit patch sets to this ref */ /** @return true if this user can submit patch sets to this ref */
public boolean canSubmit() { public boolean canSubmit() {
return canPerform(ApprovalCategory.SUBMIT, (short) 1); return canPerform(Permission.SUBMIT);
} }
/** @return true if the user can update the reference as a fast-forward. */ /** @return true if the user can update the reference as a fast-forward. */
public boolean canUpdate() { public boolean canUpdate() {
return canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE); return canPerform(Permission.PUSH);
} }
/** @return true if the user can rewind (force push) the reference. */ /** @return true if the user can rewind (force push) the reference. */
public boolean canForceUpdate() { public boolean canForceUpdate() {
return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE) || canDelete(); return canPushWithForce() || canDelete();
}
private boolean canPushWithForce() {
for (PermissionRule rule : access(Permission.PUSH)) {
if (rule.getForce()) {
return true;
}
}
return false;
} }
/** /**
@@ -183,7 +187,7 @@ public class RefControl {
} }
if (object instanceof RevCommit) { if (object instanceof RevCommit) {
return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE); return owner || canPerform(Permission.CREATE);
} else if (object instanceof RevTag) { } else if (object instanceof RevTag) {
final RevTag tag = (RevTag) object; final RevTag tag = (RevTag) object;
@@ -205,7 +209,7 @@ public class RefControl {
} else { } else {
valid = false; valid = false;
} }
if (!valid && !owner && !canPerform(FORGE_IDENTITY, FORGE_COMMITTER)) { if (!valid && !owner && !canForgeCommitter()) {
return false; return false;
} }
} }
@@ -214,9 +218,9 @@ public class RefControl {
// than if it doesn't have a PGP signature. // than if it doesn't have a PGP signature.
// //
if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) { if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
return owner || canPerform(PUSH_TAG, PUSH_TAG_SIGNED); return owner || canPerform(Permission.PUSH_TAG);
} else { } else {
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANNOTATED); return owner || canPerform(Permission.PUSH_TAG);
} }
} else { } else {
@@ -233,10 +237,10 @@ public class RefControl {
public boolean canDelete() { public boolean canDelete() {
switch (getCurrentUser().getAccessPath()) { switch (getCurrentUser().getAccessPath()) {
case WEB_UI: case WEB_UI:
return isOwner() || canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE); return isOwner() || canPushWithForce();
case GIT: case GIT:
return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE); return canPushWithForce();
default: default:
return false; return false;
@@ -246,7 +250,7 @@ public class RefControl {
/** @return true if this user can forge the author line in a commit. */ /** @return true if this user can forge the author line in a commit. */
public boolean canForgeAuthor() { public boolean canForgeAuthor() {
if (canForgeAuthor == null) { if (canForgeAuthor == null) {
canForgeAuthor = canPerform(FORGE_IDENTITY, FORGE_AUTHOR); canForgeAuthor = canPerform(Permission.FORGE_AUTHOR);
} }
return canForgeAuthor; return canForgeAuthor;
} }
@@ -254,314 +258,103 @@ public class RefControl {
/** @return true if this user can forge the committer line in a commit. */ /** @return true if this user can forge the committer line in a commit. */
public boolean canForgeCommitter() { public boolean canForgeCommitter() {
if (canForgeCommitter == null) { if (canForgeCommitter == null) {
canForgeCommitter = canPerform(FORGE_IDENTITY, FORGE_COMMITTER); canForgeCommitter = canPerform(Permission.FORGE_COMMITTER);
} }
return canForgeCommitter; return canForgeCommitter;
} }
/** @return true if this user can forge the server on the committer line. */ /** @return true if this user can forge the server on the committer line. */
public boolean canForgeGerritServerIdentity() { public boolean canForgeGerritServerIdentity() {
return canPerform(FORGE_IDENTITY, FORGE_SERVER); return canPerform(Permission.FORGE_SERVER);
} }
public short normalize(ApprovalCategory.Id category, short score) { /** All value ranges of any allowed label permission. */
short minAllowed = 0, maxAllowed = 0; public List<PermissionRange> getLabelRanges() {
for (RefRight r : getApplicableRights(category)) { List<PermissionRange> r = new ArrayList<PermissionRange>();
if (getCurrentUser().getEffectiveGroups().contains(r.getAccountGroupUUID())) { for (Map.Entry<String, List<PermissionRule>> e : permissions().entrySet()) {
minAllowed = (short) Math.min(minAllowed, r.getMinValue()); if (Permission.isLabel(e.getKey())) {
maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue()); r.add(toRange(e.getKey(), e.getValue()));
} }
} }
return r;
if (score < minAllowed) {
score = minAllowed;
}
if (score > maxAllowed) {
score = maxAllowed;
}
return score;
} }
/** /** The range of permitted values associated with a label permission. */
* Convenience holder class used to map a ref pattern to the list of public PermissionRange getRange(String permission) {
* {@code RefRight}s that use it in the database. if (Permission.isLabel(permission)) {
*/ return toRange(permission, access(permission));
public final static class RefRightsForPattern {
private final List<RefRight> rights;
private boolean containsExclusive;
public RefRightsForPattern() {
rights = new ArrayList<RefRight>();
containsExclusive = false;
} }
return null;
}
public void addRight(RefRight right) { private static PermissionRange toRange(String permissionName, List<PermissionRule> ruleList) {
rights.add(right); int min = 0;
if (right.isExclusive()) { int max = 0;
containsExclusive = true; for (PermissionRule rule : ruleList) {
} min = Math.min(min, rule.getMin());
max = Math.max(max, rule.getMax());
} }
return new PermissionRange(permissionName, min, max);
}
public List<RefRight> getRights() { /** True if the user has this permission. Works only for non labels. */
return Collections.unmodifiableList(rights); boolean canPerform(String permissionName) {
} return !access(permissionName).isEmpty();
}
public boolean containsExclusive() { /** Rules for the given permission, or the empty list. */
return containsExclusive; private List<PermissionRule> access(String permissionName) {
} List<PermissionRule> r = permissions().get(permissionName);
return r != null ? r : Collections.<PermissionRule> emptyList();
}
/** /** All rules that pertain to this user, on this reference. */
* Returns The max allowed value for this ref pattern for all specified private Map<String, List<PermissionRule>> permissions() {
* groups. if (permissions == null) {
* List<AccessSection> sections = new ArrayList<AccessSection>();
* @param groups The groups of the user for (AccessSection section : projectControl.access()) {
* @return The allowed value for this ref for all the specified groups if (appliesToRef(section)) {
*/ sections.add(section);
private boolean allowedValueForRef(Set<AccountGroup.UUID> groups, short level) {
for (RefRight right : rights) {
if (groups.contains(right.getAccountGroupUUID())
&& right.getMaxValue() >= level) {
return true;
} }
} }
return false; Collections.sort(sections, new MostSpecificComparator(getRefName()));
}
}
boolean canPerform(ApprovalCategory.Id actionId, short level) { Set<SeenRule> seen = new HashSet<SeenRule>();
final Set<AccountGroup.UUID> groups = getCurrentUser().getEffectiveGroups(); Set<String> exclusiveGroupPermissions = new HashSet<String>();
List<RefRight> allRights = new ArrayList<RefRight>(); permissions = new HashMap<String, List<PermissionRule>>();
allRights.addAll(getAllRights(actionId)); for (AccessSection section : sections) {
for (Permission permission : section.getPermissions()) {
if (exclusiveGroupPermissions.contains(permission.getName())) {
continue;
}
SortedMap<String, RefRightsForPattern> perPatternRights = for (PermissionRule rule : permission.getRules()) {
sortedRightsByPattern(allRights); if (matchGroup(rule.getGroup().getUUID())) {
SeenRule s = new SeenRule(section, permission, rule);
for (RefRightsForPattern right : perPatternRights.values()) { if (seen.add(s) && !rule.getDeny()) {
if (right.allowedValueForRef(groups, level)) { List<PermissionRule> r = permissions.get(permission.getName());
return true; if (r == null) {
} r = new ArrayList<PermissionRule>(2);
if (right.containsExclusive() && !actionId.equals(OWN)) { permissions.put(permission.getName(), r);
break; }
} r.add(rule);
} }
return false;
}
/**
* Order the Ref Pattern by the most specific. This sort is done by:
* <ul>
* <li>1 - The minor value of Levenshtein string distance between the branch
* name and the regex string shortest example. A shorter distance is a more
* specific match.
* <li>2 - Finites first, infinities after.
* <li>3 - Number of transitions.
* <li>4 - Length of the expression text.
* </ul>
*
* Levenshtein distance is a measure of the similarity between two strings.
* The distance is the number of deletions, insertions, or substitutions
* required to transform one string into another.
*
* For example, if given refs/heads/m* and refs/heads/*, the distances are 5
* and 6. It means that refs/heads/m* is more specific because it's closer to
* refs/heads/master than refs/heads/*.
*
* Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
* distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
* transitions, which after all turns it more specific.
*/
private final Comparator<String> BY_MOST_SPECIFIC_SORT =
new Comparator<String>() {
public int compare(final String pattern1, final String pattern2) {
int cmp = distance(pattern1) - distance(pattern2);
if (cmp == 0) {
boolean p1_finite = finite(pattern1);
boolean p2_finite = finite(pattern2);
if (p1_finite && !p2_finite) {
cmp = -1;
} else if (!p1_finite && p2_finite) {
cmp = 1;
} else /* if (f1 == f2) */{
cmp = 0;
} }
} }
if (cmp == 0) {
cmp = transitions(pattern1) - transitions(pattern2);
}
if (cmp == 0) {
cmp = pattern2.length() - pattern1.length();
}
return cmp;
}
private int distance(String pattern) { if (permission.getExclusiveGroup()) {
String example; exclusiveGroupPermissions.add(permission.getName());
if (isRE(pattern)) {
example = shortestExample(pattern);
} else if (pattern.endsWith("/*")) {
example = pattern.substring(0, pattern.length() - 1) + '1';
} else if (pattern.equals(getRefName())) {
return 0;
} else {
return Math.max(pattern.length(), getRefName().length());
}
return StringUtils.getLevenshteinDistance(example, getRefName());
}
private boolean finite(String pattern) {
if (isRE(pattern)) {
return toRegExp(pattern).toAutomaton().isFinite();
} else if (pattern.endsWith("/*")) {
return false;
} else {
return true;
} }
} }
private int transitions(String pattern) {
if (isRE(pattern)) {
return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
} else if (pattern.endsWith("/*")) {
return pattern.length();
} else {
return pattern.length();
}
}
};
/**
* Sorts all given rights into a map, ordered by descending length of
* ref pattern.
*
* For example, if given the following rights in argument:
*
* ["refs/heads/master", group1, -1, +1],
* ["refs/heads/master", group2, -2, +2],
* ["refs/heads/*", group3, -1, +1]
* ["refs/heads/stable", group2, -1, +1]
*
* Then the following map is returned:
* "refs/heads/master" => {
* ["refs/heads/master", group1, -1, +1],
* ["refs/heads/master", group2, -2, +2]
* }
* "refs/heads/stable" => {["refs/heads/stable", group2, -1, +1]}
* "refs/heads/*" => {["refs/heads/*", group3, -1, +1]}
*
* @param actionRights
* @return A sorted map keyed off the ref pattern of all rights.
*/
private SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
List<RefRight> actionRights) {
SortedMap<String, RefRightsForPattern> rights =
new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT);
for (RefRight actionRight : actionRights) {
RefRightsForPattern patternRights =
rights.get(actionRight.getRefPattern());
if (patternRights == null) {
patternRights = new RefRightsForPattern();
rights.put(actionRight.getRefPattern(), patternRights);
}
patternRights.addRight(actionRight);
}
return rights;
}
private List<RefRight> getAllRights(ApprovalCategory.Id actionId) {
final List<RefRight> allRefRights = filter(getProjectState().getAllRights(actionId, true));
return resolveOwnerGroups(allRefRights);
}
/**
* Returns all applicable rights for a given approval category.
*
* Applicable rights are defined as the list of {@code RefRight}s which match
* the ref for which this object was created, stopping the ref wildcard
* matching when an exclusive ref right was encountered, for the given
* approval category.
* @param id The {@link ApprovalCategory.Id}.
* @return All applicable rights.
*/
public List<RefRight> getApplicableRights(final ApprovalCategory.Id id) {
List<RefRight> l = new ArrayList<RefRight>();
l.addAll(getAllRights(id));
SortedMap<String, RefRightsForPattern> perPatternRights =
sortedRightsByPattern(l);
List<RefRight> applicable = new ArrayList<RefRight>();
for (RefRightsForPattern patternRights : perPatternRights.values()) {
applicable.addAll(patternRights.getRights());
if (patternRights.containsExclusive()) {
break;
} }
} }
return Collections.unmodifiableList(applicable); return permissions;
} }
/** private boolean appliesToRef(AccessSection section) {
* Resolves all refRights which assign privileges to the 'Project Owners' String refPattern = section.getRefPattern();
* group. All other refRights stay unchanged.
*
* @param refRights refRights to be resolved
* @return the resolved refRights
*/
private List<RefRight> resolveOwnerGroups(final List<RefRight> refRights) {
final List<RefRight> resolvedRefRights =
new ArrayList<RefRight>(refRights.size());
for (final RefRight refRight : refRights) {
resolvedRefRights.addAll(resolveOwnerGroups(refRight));
}
return resolvedRefRights;
}
/**
* Checks if the given refRight assigns privileges to the 'Project Owners'
* group.
* If yes, resolves the 'Project Owners' group to the concrete groups that
* own the project and creates new refRights for the concrete owner groups
* which are returned.
* If no, the given refRight is returned unchanged.
*
* @param refRight refRight
* @return the resolved refRights
*/
private Set<RefRight> resolveOwnerGroups(final RefRight refRight) {
final Set<RefRight> resolvedRefRights = new HashSet<RefRight>();
if (AccountGroup.PROJECT_OWNERS.equals(refRight.getAccountGroupUUID())) {
for (final AccountGroup.UUID ownerGroup : getProjectState().getAllOwners()) {
if (!AccountGroup.PROJECT_OWNERS.equals(ownerGroup)) {
resolvedRefRights.add(new RefRight(refRight, ownerGroup));
}
}
} else {
resolvedRefRights.add(refRight);
}
return resolvedRefRights;
}
private List<RefRight> filter(Collection<RefRight> all) {
List<RefRight> mine = new ArrayList<RefRight>(all.size());
for (RefRight right : all) {
if (matches(right.getRefPattern())) {
mine.add(right);
}
}
return mine;
}
private ProjectState getProjectState() {
return projectControl.getProjectState();
}
private boolean matches(String refPattern) {
if (isTemplate(refPattern)) { if (isTemplate(refPattern)) {
ParamertizedString template = new ParamertizedString(refPattern); ParamertizedString template = new ParamertizedString(refPattern);
HashMap<String, String> p = new HashMap<String, String>(); HashMap<String, String> p = new HashMap<String, String>();
@@ -596,6 +389,18 @@ public class RefControl {
} }
} }
private boolean matchGroup(AccountGroup.UUID uuid) {
Set<AccountGroup.UUID> userGroups = getCurrentUser().getEffectiveGroups();
if (AccountGroup.PROJECT_OWNERS.equals(uuid)) {
ProjectState state = projectControl.getProjectState();
return CollectionsUtil.isAnyIncludedIn(state.getAllOwners(), userGroups);
} else {
return userGroups.contains(uuid);
}
}
private static boolean isTemplate(String refPattern) { private static boolean isTemplate(String refPattern) {
return 0 <= refPattern.indexOf("${"); return 0 <= refPattern.indexOf("${");
} }
@@ -608,7 +413,7 @@ public class RefControl {
} }
private static boolean isRE(String refPattern) { private static boolean isRE(String refPattern) {
return refPattern.startsWith(RefRight.REGEX_PREFIX); return refPattern.startsWith(AccessSection.REGEX_PREFIX);
} }
public static String shortestExample(String pattern) { public static String shortestExample(String pattern) {
@@ -627,4 +432,143 @@ public class RefControl {
} }
return new RegExp(refPattern, RegExp.NONE); return new RegExp(refPattern, RegExp.NONE);
} }
/** Tracks whether or not a permission has been overridden. */
private static class SeenRule {
final String refPattern;
final String permissionName;
final AccountGroup.UUID group;
SeenRule(AccessSection section, Permission permission, PermissionRule rule) {
refPattern = section.getRefPattern();
permissionName = permission.getName();
group = rule.getGroup().getUUID();
}
@Override
public int hashCode() {
int hc = refPattern.hashCode();
hc = hc * 31 + permissionName.hashCode();
if (group != null) {
hc = hc * 31 + group.hashCode();
}
return hc;
}
@Override
public boolean equals(Object other) {
if (other instanceof SeenRule) {
SeenRule a = this;
SeenRule b = (SeenRule) other;
return a.refPattern.equals(b.refPattern) //
&& a.permissionName.equals(b.permissionName) //
&& eq(a.group, b.group);
}
return false;
}
private boolean eq(AccountGroup.UUID a, AccountGroup.UUID b) {
return a != null && b != null && a.equals(b);
}
}
/**
* Order the Ref Pattern by the most specific. This sort is done by:
* <ul>
* <li>1 - The minor value of Levenshtein string distance between the branch
* name and the regex string shortest example. A shorter distance is a more
* specific match.
* <li>2 - Finites first, infinities after.
* <li>3 - Number of transitions.
* <li>4 - Length of the expression text.
* </ul>
*
* Levenshtein distance is a measure of the similarity between two strings.
* The distance is the number of deletions, insertions, or substitutions
* required to transform one string into another.
*
* For example, if given refs/heads/m* and refs/heads/*, the distances are 5
* and 6. It means that refs/heads/m* is more specific because it's closer to
* refs/heads/master than refs/heads/*.
*
* Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
* distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
* transitions, which after all turns it more specific.
*/
private static final class MostSpecificComparator implements
Comparator<AccessSection> {
private final String refName;
MostSpecificComparator(String refName) {
this.refName = refName;
}
public int compare(AccessSection a, AccessSection b) {
return compare(a.getRefPattern(), b.getRefPattern());
}
private int compare(final String pattern1, final String pattern2) {
int cmp = distance(pattern1) - distance(pattern2);
if (cmp == 0) {
boolean p1_finite = finite(pattern1);
boolean p2_finite = finite(pattern2);
if (p1_finite && !p2_finite) {
cmp = -1;
} else if (!p1_finite && p2_finite) {
cmp = 1;
} else /* if (f1 == f2) */{
cmp = 0;
}
}
if (cmp == 0) {
cmp = transitions(pattern1) - transitions(pattern2);
}
if (cmp == 0) {
cmp = pattern2.length() - pattern1.length();
}
return cmp;
}
private int distance(String pattern) {
String example;
if (isRE(pattern)) {
example = shortestExample(pattern);
} else if (pattern.endsWith("/*")) {
example = pattern.substring(0, pattern.length() - 1) + '1';
} else if (pattern.equals(refName)) {
return 0;
} else {
return Math.max(pattern.length(), refName.length());
}
return StringUtils.getLevenshteinDistance(example, refName);
}
private boolean finite(String pattern) {
if (isRE(pattern)) {
return toRegExp(pattern).toAutomaton().isFinite();
} else if (pattern.endsWith("/*")) {
return false;
} else {
return true;
}
}
private int transitions(String pattern) {
if (isRE(pattern)) {
return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
} else if (pattern.endsWith("/*")) {
return pattern.length();
} else {
return pattern.length();
}
}
}
} }

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
@@ -33,48 +34,54 @@ class LabelPredicate extends OperatorPredicate<ChangeData> {
private static enum Test { private static enum Test {
EQ { EQ {
@Override @Override
public boolean match(short psValue, short expValue) { public boolean match(int psValue, int expValue) {
return psValue == expValue; return psValue == expValue;
} }
}, },
GT_EQ { GT_EQ {
@Override @Override
public boolean match(short psValue, short expValue) { public boolean match(int psValue, int expValue) {
return psValue >= expValue; return psValue >= expValue;
} }
}, },
LT_EQ { LT_EQ {
@Override @Override
public boolean match(short psValue, short expValue) { public boolean match(int psValue, int expValue) {
return psValue <= expValue; return psValue <= expValue;
} }
}; };
abstract boolean match(short psValue, short expValue); abstract boolean match(int psValue, int expValue);
} }
private static ApprovalCategory.Id category(ApprovalTypes types, String toFind) { private static ApprovalCategory category(ApprovalTypes types, String toFind) {
if (types.getApprovalType(new ApprovalCategory.Id(toFind)) != null) { if (types.byLabel(toFind) != null) {
return new ApprovalCategory.Id(toFind); return types.byLabel(toFind).getCategory();
}
if (types.byId(new ApprovalCategory.Id(toFind)) != null) {
return types.byId(new ApprovalCategory.Id(toFind)).getCategory();
} }
for (ApprovalType at : types.getApprovalTypes()) { for (ApprovalType at : types.getApprovalTypes()) {
String name = at.getCategory().getName(); ApprovalCategory category = at.getCategory();
if (toFind.equalsIgnoreCase(name)) {
return at.getCategory().getId();
} else if (toFind.equalsIgnoreCase(name.replace(" ", ""))) { if (toFind.equalsIgnoreCase(category.getName())) {
return at.getCategory().getId(); return category;
} else if (toFind.equalsIgnoreCase(category.getName().replace(" ", ""))) {
return category;
} }
} }
for (ApprovalType at : types.getApprovalTypes()) { for (ApprovalType at : types.getApprovalTypes()) {
if (toFind.equalsIgnoreCase(at.getCategory().getAbbreviatedName())) { ApprovalCategory category = at.getCategory();
return at.getCategory().getId(); if (toFind.equalsIgnoreCase(category.getAbbreviatedName())) {
return category;
} }
} }
return new ApprovalCategory.Id(toFind); return new ApprovalCategory(new ApprovalCategory.Id(toFind), toFind);
} }
private static Test op(String op) { private static Test op(String op) {
@@ -92,19 +99,20 @@ class LabelPredicate extends OperatorPredicate<ChangeData> {
} }
} }
private static short value(String value) { private static int value(String value) {
if (value.startsWith("+")) { if (value.startsWith("+")) {
value = value.substring(1); value = value.substring(1);
} }
return Short.parseShort(value); return Integer.parseInt(value);
} }
private final ChangeControl.GenericFactory ccFactory; private final ChangeControl.GenericFactory ccFactory;
private final IdentifiedUser.GenericFactory userFactory; private final IdentifiedUser.GenericFactory userFactory;
private final Provider<ReviewDb> dbProvider; private final Provider<ReviewDb> dbProvider;
private final Test test; private final Test test;
private final ApprovalCategory.Id category; private final ApprovalCategory category;
private final short expVal; private final String permissionName;
private final int expVal;
LabelPredicate(ChangeControl.GenericFactory ccFactory, LabelPredicate(ChangeControl.GenericFactory ccFactory,
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider, IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
@@ -131,13 +139,15 @@ class LabelPredicate extends OperatorPredicate<ChangeData> {
test = Test.EQ; test = Test.EQ;
expVal = 1; expVal = 1;
} }
this.permissionName = Permission.forLabel(category.getLabelName());
} }
@Override @Override
public boolean match(final ChangeData object) throws OrmException { public boolean match(final ChangeData object) throws OrmException {
for (PatchSetApproval p : object.currentApprovals(dbProvider)) { for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
if (p.getCategoryId().equals(category)) { if (p.getCategoryId().equals(category)) {
short psVal = p.getValue(); int psVal = p.getValue();
if (test.match(psVal, expVal)) { if (test.match(psVal, expVal)) {
// Double check the value is still permitted for the user. // Double check the value is still permitted for the user.
// //
@@ -149,7 +159,7 @@ class LabelPredicate extends OperatorPredicate<ChangeData> {
// //
continue; continue;
} }
psVal = cc.normalize(category, psVal); psVal = cc.getRange(permissionName).squash(psVal);
} catch (NoSuchChangeException e) { } catch (NoSuchChangeException e) {
// The project has disappeared. // The project has disappeared.
// //

View File

@@ -14,13 +14,16 @@
package com.google.gerrit.server.schema; package com.google.gerrit.server.schema;
import com.google.gerrit.common.Version;
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.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName; import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue; import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.CurrentSchemaVersion; import com.google.gerrit.reviewdb.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
@@ -31,8 +34,6 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.NoReplication; import com.google.gerrit.server.git.NoReplication;
import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.workflow.NoOpFunction;
import com.google.gerrit.server.workflow.SubmitFunction;
import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcExecutor; import com.google.gwtorm.jdbc.JdbcExecutor;
@@ -69,6 +70,11 @@ public class SchemaCreator {
private final ScriptRunner index_postgres; private final ScriptRunner index_postgres;
private final ScriptRunner mysql_nextval; private final ScriptRunner mysql_nextval;
private AccountGroup admin;
private AccountGroup anonymous;
private AccountGroup registered;
private AccountGroup owners;
@Inject @Inject
public SchemaCreator(final SitePaths site, public SchemaCreator(final SitePaths site,
@Current final SchemaVersion version, final GitRepositoryManager mgr, @Current final SchemaVersion version, final GitRepositoryManager mgr,
@@ -103,14 +109,8 @@ public class SchemaCreator {
db.schemaVersion().insert(Collections.singleton(sVer)); db.schemaVersion().insert(Collections.singleton(sVer));
final SystemConfig sConfig = initSystemConfig(db); final SystemConfig sConfig = initSystemConfig(db);
initOwnerCategory(db);
initReadCategory(db, sConfig);
initVerifiedCategory(db); initVerifiedCategory(db);
initCodeReviewCategory(db, sConfig); initCodeReviewCategory(db, sConfig);
initSubmitCategory(db);
initPushTagCategory(db);
initPushUpdateBranchCategory(db);
initForgeIdentityCategory(db, sConfig);
if (mgr != null) { if (mgr != null) {
// TODO This should never be null when initializing a site. // TODO This should never be null when initializing a site.
@@ -145,14 +145,14 @@ public class SchemaCreator {
} }
private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException { private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException {
final AccountGroup admin = newGroup(c, "Administrators", null); admin = newGroup(c, "Administrators", null);
admin.setDescription("Gerrit Site Administrators"); admin.setDescription("Gerrit Site Administrators");
admin.setType(AccountGroup.Type.INTERNAL); admin.setType(AccountGroup.Type.INTERNAL);
c.accountGroups().insert(Collections.singleton(admin)); c.accountGroups().insert(Collections.singleton(admin));
c.accountGroupNames().insert( c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(admin))); Collections.singleton(new AccountGroupName(admin)));
final AccountGroup anonymous = anonymous =
newGroup(c, "Anonymous Users", AccountGroup.ANONYMOUS_USERS); newGroup(c, "Anonymous Users", AccountGroup.ANONYMOUS_USERS);
anonymous.setDescription("Any user, signed-in or not"); anonymous.setDescription("Any user, signed-in or not");
anonymous.setOwnerGroupId(admin.getId()); anonymous.setOwnerGroupId(admin.getId());
@@ -161,7 +161,7 @@ public class SchemaCreator {
c.accountGroupNames().insert( c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(anonymous))); Collections.singleton(new AccountGroupName(anonymous)));
final AccountGroup registered = registered =
newGroup(c, "Registered Users", AccountGroup.REGISTERED_USERS); newGroup(c, "Registered Users", AccountGroup.REGISTERED_USERS);
registered.setDescription("Any signed-in user"); registered.setDescription("Any signed-in user");
registered.setOwnerGroupId(admin.getId()); registered.setOwnerGroupId(admin.getId());
@@ -178,8 +178,7 @@ public class SchemaCreator {
c.accountGroupNames().insert( c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(batchUsers))); Collections.singleton(new AccountGroupName(batchUsers)));
final AccountGroup owners = owners = newGroup(c, "Project Owners", AccountGroup.PROJECT_OWNERS);
newGroup(c, "Project Owners", AccountGroup.PROJECT_OWNERS);
owners.setDescription("Any owner of the project"); owners.setDescription("Any owner of the project");
owners.setOwnerGroupId(admin.getId()); owners.setOwnerGroupId(admin.getId());
owners.setType(AccountGroup.Type.SYSTEM); owners.setType(AccountGroup.Type.SYSTEM);
@@ -236,7 +235,29 @@ public class SchemaCreator {
p.setDescription("Rights inherited by all other projects"); p.setDescription("Rights inherited by all other projects");
p.setUseContributorAgreements(false); p.setUseContributorAgreements(false);
md.setMessage("Created project\n"); AccessSection all = config.getAccessSection(AccessSection.ALL, true);
AccessSection heads = config.getAccessSection(AccessSection.HEADS, true);
AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
PermissionRule review = rule(config, registered);
review.setRange(-1, 1);
heads.getPermission(Permission.LABEL + "Code-Review", true).add(review);
all.getPermission(Permission.READ, true) //
.add(rule(config, admin));
all.getPermission(Permission.READ, true) //
.add(rule(config, anonymous));
config.getAccessSection("refs/for/" + AccessSection.ALL, true) //
.getPermission(Permission.PUSH, true) //
.add(rule(config, registered));
all.getPermission(Permission.FORGE_AUTHOR, true) //
.add(rule(config, registered));
meta.getPermission(Permission.READ, true) //
.add(rule(config, owners));
md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
if (!config.commit(md)) { if (!config.commit(md)) {
throw new IOException("Cannot create " + DEFAULT_WILD_NAME.get()); throw new IOException("Cannot create " + DEFAULT_WILD_NAME.get());
} }
@@ -245,6 +266,10 @@ public class SchemaCreator {
} }
} }
private PermissionRule rule(ProjectConfig config, AccountGroup group) {
return new PermissionRule(config.resolve(group));
}
private void initVerifiedCategory(final ReviewDb c) throws OrmException { private void initVerifiedCategory(final ReviewDb c) throws OrmException {
final ApprovalCategory cat; final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals; final ArrayList<ApprovalCategoryValue> vals;
@@ -277,143 +302,6 @@ public class SchemaCreator {
vals.add(value(cat, -2, "Do not submit")); vals.add(value(cat, -2, "Do not submit"));
c.approvalCategories().insert(Collections.singleton(cat)); c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals); c.approvalCategoryValues().insert(vals);
final RefRight approve =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME,
new RefRight.RefPattern("refs/heads/*"), cat.getId(),
sConfig.registeredGroupId));
approve.setMaxValue((short) 1);
approve.setMinValue((short) -1);
c.refRights().insert(Collections.singleton(approve));
}
private void initOwnerCategory(final ReviewDb c) throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
cat = new ApprovalCategory(ApprovalCategory.OWN, "Owner");
cat.setPosition((short) -1);
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, 1, "Administer All Settings"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initReadCategory(final ReviewDb c, final SystemConfig sConfig)
throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access");
cat.setPosition((short) -1);
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, 3, "Upload merges permission"));
vals.add(value(cat, 2, "Upload permission"));
vals.add(value(cat, 1, "Read access"));
vals.add(value(cat, -1, "No access"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
final RefRight.RefPattern pattern = new RefRight.RefPattern(RefRight.ALL);
{
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.anonymousGroupId));
read.setMaxValue((short) 1);
read.setMinValue((short) 1);
c.refRights().insert(Collections.singleton(read));
}
{
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.registeredGroupId));
read.setMaxValue((short) 2);
read.setMinValue((short) 1);
c.refRights().insert(Collections.singleton(read));
}
{
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.adminGroupId));
read.setMaxValue((short) 1);
read.setMinValue((short) 1);
c.refRights().insert(Collections.singleton(read));
}
}
private void initSubmitCategory(final ReviewDb c) throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit");
cat.setPosition((short) -1);
cat.setFunctionName(SubmitFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, 1, "Submit"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initPushTagCategory(final ReviewDb c) throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Tag");
cat.setPosition((short) -1);
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag"));
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
"Create Annotated Tag"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initPushUpdateBranchCategory(final ReviewDb c)
throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch");
cat.setPosition((short) -1);
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
"Force Push Branch; Delete Branch"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initForgeIdentityCategory(final ReviewDb c,
final SystemConfig sConfig) throws OrmException {
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> values;
cat =
new ApprovalCategory(ApprovalCategory.FORGE_IDENTITY, "Forge Identity");
cat.setPosition((short) -1);
cat.setFunctionName(NoOpFunction.NAME);
values = new ArrayList<ApprovalCategoryValue>();
values.add(value(cat, ApprovalCategory.FORGE_AUTHOR,
"Forge Author Identity"));
values.add(value(cat, ApprovalCategory.FORGE_COMMITTER,
"Forge Committer or Tagger Identity"));
values.add(value(cat, ApprovalCategory.FORGE_SERVER,
"Forge Gerrit Code Review Server Identity"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(values);
RefRight right =
new RefRight(new RefRight.Key(sConfig.wildProjectName,
new RefRight.RefPattern(RefRight.ALL),
ApprovalCategory.FORGE_IDENTITY, sConfig.registeredGroupId));
right.setMinValue(ApprovalCategory.FORGE_AUTHOR);
right.setMaxValue(ApprovalCategory.FORGE_AUTHOR);
c.refRights().insert(Collections.singleton(right));
} }
private static ApprovalCategoryValue value(final ApprovalCategory cat, private static ApprovalCategoryValue value(final ApprovalCategory cat,

View File

@@ -14,7 +14,24 @@
package com.google.gerrit.server.schema; package com.google.gerrit.server.schema;
import static com.google.gerrit.common.data.Permission.CREATE;
import static com.google.gerrit.common.data.Permission.FORGE_AUTHOR;
import static com.google.gerrit.common.data.Permission.FORGE_COMMITTER;
import static com.google.gerrit.common.data.Permission.FORGE_SERVER;
import static com.google.gerrit.common.data.Permission.LABEL;
import static com.google.gerrit.common.data.Permission.OWNER;
import static com.google.gerrit.common.data.Permission.PUSH;
import static com.google.gerrit.common.data.Permission.PUSH_MERGE;
import static com.google.gerrit.common.data.Permission.PUSH_TAG;
import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.common.data.Permission.SUBMIT;
import com.google.gerrit.common.data.AccessSection;
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.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.reviewdb.SystemConfig;
@@ -38,6 +55,7 @@ import java.io.IOException;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -48,7 +66,19 @@ class Schema_53 extends SchemaVersion {
private final PersonIdent serverUser; private final PersonIdent serverUser;
private SystemConfig systemConfig; private SystemConfig systemConfig;
private Map<AccountGroup.Id, AccountGroup> groupMap; private Map<AccountGroup.Id, GroupReference> groupMap;
private Map<ApprovalCategory.Id, ApprovalCategory> categoryMap;
private GroupReference projectOwners;
private Map<Project.NameKey, Project.NameKey> parentsByProject;
private Map<Project.NameKey, List<OldRefRight>> rightsByProject;
private final String OLD_SUBMIT = "SUBM";
private final String OLD_READ = "READ";
private final String OLD_OWN = "OWN";
private final String OLD_PUSH_TAG = "pTAG";
private final String OLD_PUSH_HEAD = "pHD";
private final String OLD_FORGE_IDENTITY = "FORG";
@Inject @Inject
Schema_53(Provider<Schema_52> prior, GitRepositoryManager mgr, Schema_53(Provider<Schema_52> prior, GitRepositoryManager mgr,
@@ -62,16 +92,33 @@ class Schema_53 extends SchemaVersion {
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
SQLException { SQLException {
systemConfig = db.systemConfig().get(new SystemConfig.Key()); systemConfig = db.systemConfig().get(new SystemConfig.Key());
categoryMap = db.approvalCategories().toMap(db.approvalCategories().all());
assignGroupUUIDs(db); assignGroupUUIDs(db);
readOldRefRights(db);
readProjectParents(db);
exportProjectConfig(db); exportProjectConfig(db);
deleteActionCategories(db);
}
private void deleteActionCategories(ReviewDb db) throws OrmException {
List<ApprovalCategory> delete = new ArrayList<ApprovalCategory>();
for (ApprovalCategory category : categoryMap.values()) {
if (category.getPosition() < 0) {
delete.add(category);
}
}
db.approvalCategories().delete(delete);
} }
private void assignGroupUUIDs(ReviewDb db) throws OrmException { private void assignGroupUUIDs(ReviewDb db) throws OrmException {
groupMap = new HashMap<AccountGroup.Id, AccountGroup>(); groupMap = new HashMap<AccountGroup.Id, GroupReference>();
List<AccountGroup> groups = db.accountGroups().all().toList(); List<AccountGroup> groups = db.accountGroups().all().toList();
for (AccountGroup g : groups) { for (AccountGroup g : groups) {
if (g.getId().equals(systemConfig.ownerGroupId)) { if (g.getId().equals(systemConfig.ownerGroupId)) {
g.setGroupUUID(AccountGroup.PROJECT_OWNERS); g.setGroupUUID(AccountGroup.PROJECT_OWNERS);
projectOwners = GroupReference.forGroup(g);
} else if (g.getId().equals(systemConfig.anonymousGroupId)) { } else if (g.getId().equals(systemConfig.anonymousGroupId)) {
g.setGroupUUID(AccountGroup.ANONYMOUS_USERS); g.setGroupUUID(AccountGroup.ANONYMOUS_USERS);
@@ -82,7 +129,7 @@ class Schema_53 extends SchemaVersion {
} else { } else {
g.setGroupUUID(GroupUUID.make(g.getName(), serverUser)); g.setGroupUUID(GroupUUID.make(g.getName(), serverUser));
} }
groupMap.put(g.getId(), g); groupMap.put(g.getId(), GroupReference.forGroup(g));
} }
db.accountGroups().update(groups); db.accountGroups().update(groups);
@@ -92,7 +139,7 @@ class Schema_53 extends SchemaVersion {
} }
private AccountGroup.UUID toUUID(AccountGroup.Id id) { private AccountGroup.UUID toUUID(AccountGroup.Id id) {
return groupMap.get(id).getGroupUUID(); return groupMap.get(id).getUUID();
} }
private void exportProjectConfig(ReviewDb db) throws OrmException, private void exportProjectConfig(ReviewDb db) throws OrmException,
@@ -123,6 +170,16 @@ class Schema_53 extends SchemaVersion {
ProjectConfig config = ProjectConfig.read(md); ProjectConfig config = ProjectConfig.read(md);
loadProject(rs, config.getProject()); loadProject(rs, config.getProject());
config.getAccessSections().clear();
convertRights(config);
// Grant out read on the config branch by default.
//
if (config.getProject().getNameKey().equals(systemConfig.wildProjectName)) {
AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
Permission read = meta.getPermission(READ, true);
read.getRule(config.resolve(projectOwners), true);
}
md.setMessage("Import project configuration from SQL\n"); md.setMessage("Import project configuration from SQL\n");
if (!config.commit(md)) { if (!config.commit(md)) {
@@ -169,4 +226,248 @@ class Schema_53 extends SchemaVersion {
project.setUseContentMerge("Y".equals(rs.getString("use_content_merge"))); project.setUseContentMerge("Y".equals(rs.getString("use_content_merge")));
project.setParentName(rs.getString("parent_name")); project.setParentName(rs.getString("parent_name"));
} }
private void readOldRefRights(ReviewDb db) throws SQLException {
rightsByProject = new HashMap<Project.NameKey, List<OldRefRight>>();
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM ref_rights");
while (rs.next()) {
OldRefRight right = new OldRefRight(rs);
if (right.group == null || right.category == null) {
continue;
}
List<OldRefRight> list;
list = rightsByProject.get(right.project);
if (list == null) {
list = new ArrayList<OldRefRight>();
rightsByProject.put(right.project, list);
}
list.add(right);
}
rs.close();
stmt.close();
}
private void readProjectParents(ReviewDb db) throws SQLException {
parentsByProject = new HashMap<Project.NameKey, Project.NameKey>();
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM projects");
while (rs.next()) {
String name = rs.getString("name");
String parent_name = rs.getString("parent_name");
if (parent_name == null) {
parent_name = systemConfig.wildProjectName.get();
}
parentsByProject.put(new Project.NameKey(name), //
new Project.NameKey(parent_name));
}
rs.close();
stmt.close();
}
private void convertRights(ProjectConfig config) {
List<OldRefRight> myRights =
rightsByProject.get(config.getProject().getNameKey());
if (myRights == null) {
return;
}
for (OldRefRight old : myRights) {
AccessSection section = config.getAccessSection(old.ref_pattern, true);
GroupReference group = config.resolve(old.group);
if (OLD_SUBMIT.equals(old.category)) {
PermissionRule submit = rule(group);
submit.setDeny(old.max_value <= 0);
add(section, SUBMIT, old.exclusive, submit);
} else if (OLD_READ.equals(old.category)) {
if (old.exclusive) {
section.getPermission(READ, true).setExclusiveGroup(true);
newChangePermission(config, old.ref_pattern).setExclusiveGroup(true);
}
PermissionRule read = rule(group);
read.setDeny(old.max_value <= 0);
add(section, READ, old.exclusive, read);
if (3 <= old.max_value) {
newMergePermission(config, old.ref_pattern).add(rule(group));
} else if (3 <= inheritedMax(config, old)) {
newMergePermission(config, old.ref_pattern).add(deny(group));
}
if (2 <= old.max_value) {
newChangePermission(config, old.ref_pattern).add(rule(group));
} else if (2 <= inheritedMax(config, old)) {
newChangePermission(config, old.ref_pattern).add(deny(group));
}
} else if (OLD_OWN.equals(old.category)) {
add(section, OWNER, false, rule(group));
} else if (OLD_PUSH_TAG.equals(old.category)) {
PermissionRule push = rule(group);
push.setDeny(old.max_value <= 0);
add(section, PUSH_TAG, old.exclusive, push);
} else if (OLD_PUSH_HEAD.equals(old.category)) {
if (old.exclusive) {
section.getPermission(PUSH, true).setExclusiveGroup(true);
section.getPermission(CREATE, true).setExclusiveGroup(true);
}
PermissionRule push = rule(group);
push.setDeny(old.max_value <= 0);
push.setForce(3 <= old.max_value);
add(section, PUSH, old.exclusive, push);
if (2 <= old.max_value) {
add(section, CREATE, old.exclusive, rule(group));
} else if (2 <= inheritedMax(config, old)) {
add(section, CREATE, old.exclusive, deny(group));
}
} else if (OLD_FORGE_IDENTITY.equals(old.category)) {
if (old.exclusive) {
section.getPermission(FORGE_AUTHOR, true).setExclusiveGroup(true);
section.getPermission(FORGE_COMMITTER, true).setExclusiveGroup(true);
section.getPermission(FORGE_SERVER, true).setExclusiveGroup(true);
}
if (1 <= old.max_value) {
add(section, FORGE_AUTHOR, old.exclusive, rule(group));
}
if (2 <= old.max_value) {
add(section, FORGE_COMMITTER, old.exclusive, rule(group));
} else if (2 <= inheritedMax(config, old)) {
add(section, FORGE_COMMITTER, old.exclusive, deny(group));
}
if (3 <= old.max_value) {
add(section, FORGE_SERVER, old.exclusive, rule(group));
} else if (3 <= inheritedMax(config, old)) {
add(section, FORGE_SERVER, old.exclusive, deny(group));
}
} else {
PermissionRule rule = rule(group);
rule.setRange(old.min_value, old.max_value);
if (old.min_value == 0 && old.max_value == 0) {
rule.setDeny(true);
}
add(section, LABEL + varNameOf(old.category), old.exclusive, rule);
}
}
}
private static Permission newChangePermission(ProjectConfig config,
String name) {
if (name.startsWith(AccessSection.REGEX_PREFIX)) {
name = AccessSection.REGEX_PREFIX
+ "refs/for/"
+ name.substring(AccessSection.REGEX_PREFIX.length());
} else {
name = "refs/for/" + name;
}
return config.getAccessSection(name, true).getPermission(PUSH, true);
}
private static Permission newMergePermission(ProjectConfig config,
String name) {
if (name.startsWith(AccessSection.REGEX_PREFIX)) {
name = AccessSection.REGEX_PREFIX
+ "refs/for/"
+ name.substring(AccessSection.REGEX_PREFIX.length());
} else {
name = "refs/for/" + name;
}
return config.getAccessSection(name, true).getPermission(PUSH_MERGE, true);
}
private static PermissionRule rule(GroupReference group) {
return new PermissionRule(group);
}
private static PermissionRule deny(GroupReference group) {
PermissionRule rule = rule(group);
rule.setDeny(true);
return rule;
}
private int inheritedMax(ProjectConfig config, OldRefRight old) {
int max = 0;
String ref = old.ref_pattern;
String category = old.category;
AccountGroup.UUID group = old.group.getUUID();
Project.NameKey project = config.getProject().getParent();
if (project == null) {
project = systemConfig.wildProjectName;
}
do {
List<OldRefRight> rights = rightsByProject.get(project);
if (rights != null) {
for (OldRefRight r : rights) {
if (r.ref_pattern.equals(ref) //
&& r.group.getUUID().equals(group) //
&& r.category.equals(category)) {
max = Math.max(max, r.max_value);
break;
}
}
}
project = parentsByProject.get(project);
} while (!project.equals(systemConfig.wildProjectName));
return max;
}
private String varNameOf(String id) {
ApprovalCategory category = categoryMap.get(new ApprovalCategory.Id(id));
if (category == null) {
category = new ApprovalCategory(new ApprovalCategory.Id(id), id);
}
return category.getLabelName();
}
private static void add(AccessSection section, String name,
boolean exclusive, PermissionRule rule) {
Permission p = section.getPermission(name, true);
if (exclusive) {
p.setExclusiveGroup(true);
}
p.add(rule);
}
private class OldRefRight {
final int min_value;
final int max_value;
final String ref_pattern;
final boolean exclusive;
final GroupReference group;
final String category;
final Project.NameKey project;
OldRefRight(ResultSet rs) throws SQLException {
min_value = rs.getInt("min_value");
max_value = rs.getInt("max_value");
project = new Project.NameKey(rs.getString("project_name"));
String r = rs.getString("ref_pattern");
exclusive = r.startsWith("-");
if (exclusive) {
r = r.substring(1);
}
ref_pattern = r;
category = rs.getString("category_id");
group = groupMap.get(new AccountGroup.Id(rs.getInt("group_id")));
}
}
} }

View File

@@ -15,11 +15,10 @@
package com.google.gerrit.server.workflow; package com.google.gerrit.server.workflow;
import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.RefControl;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -29,7 +28,6 @@ public abstract class CategoryFunction {
private static Map<String, CategoryFunction> all = private static Map<String, CategoryFunction> all =
new HashMap<String, CategoryFunction>(); new HashMap<String, CategoryFunction>();
static { static {
all.put(SubmitFunction.NAME, new SubmitFunction());
all.put(MaxWithBlock.NAME, new MaxWithBlock()); all.put(MaxWithBlock.NAME, new MaxWithBlock());
all.put(MaxNoBlock.NAME, new MaxNoBlock()); all.put(MaxNoBlock.NAME, new MaxNoBlock());
all.put(NoOpFunction.NAME, new NoOpFunction()); all.put(NoOpFunction.NAME, new NoOpFunction());
@@ -44,21 +42,10 @@ public abstract class CategoryFunction {
* is not known to Gerrit and thus cannot be executed. * is not known to Gerrit and thus cannot be executed.
*/ */
public static CategoryFunction forCategory(final ApprovalCategory category) { public static CategoryFunction forCategory(final ApprovalCategory category) {
final CategoryFunction r = forName(category.getFunctionName()); final CategoryFunction r = all.get(category.getFunctionName());
return r != null ? r : new NoOpFunction(); return r != null ? r : new NoOpFunction();
} }
/**
* Locate a function by name.
*
* @param functionName the function's unique name.
* @return the function implementation; null if the function is not known to
* Gerrit and thus cannot be executed.
*/
public static CategoryFunction forName(final String functionName) {
return all.get(functionName);
}
/** /**
* Normalize ChangeApprovals and set the valid flag for this category. * Normalize ChangeApprovals and set the valid flag for this category.
* <p> * <p>
@@ -92,13 +79,8 @@ public abstract class CategoryFunction {
public boolean isValid(final CurrentUser user, final ApprovalType at, public boolean isValid(final CurrentUser user, final ApprovalType at,
final FunctionState state) { final FunctionState state) {
RefControl rc = state.controlFor(user); return !state.controlFor(user) //
for (final RefRight pr : rc.getApplicableRights(at.getCategory().getId())) { .getRange(Permission.forLabel(at.getCategory().getLabelName())) //
if (user.getEffectiveGroups().contains(pr.getAccountGroupUUID()) .isEmpty();
&& (pr.getMinValue() < 0 || pr.getMaxValue() > 0)) {
return true;
}
}
return false;
} }
} }

View File

@@ -16,13 +16,13 @@ package com.google.gerrit.server.workflow;
import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue; import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ApprovalCategory.Id; import com.google.gerrit.reviewdb.ApprovalCategory.Id;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@@ -139,28 +139,12 @@ public class FunctionState {
* of them is used. * of them is used.
* <p> * <p>
*/ */
private void applyRightFloor(final PatchSetApproval a) { private void applyRightFloor(final ApprovalType at, final PatchSetApproval a) {
final ApprovalCategory category = at.getCategory();
final String permission = Permission.forLabel(category.getLabelName());
final IdentifiedUser user = userFactory.create(a.getAccountId()); final IdentifiedUser user = userFactory.create(a.getAccountId());
RefControl rc = controlFor(user); final PermissionRange range = controlFor(user).getRange(permission);
a.setValue((short) range.squash(a.getValue()));
// Find the maximal range actually granted to the user.
//
short minAllowed = 0, maxAllowed = 0;
for (final RefRight r : rc.getApplicableRights(a.getCategoryId())) {
final AccountGroup.UUID grp = r.getAccountGroupUUID();
if (user.getEffectiveGroups().contains(grp)) {
minAllowed = (short) Math.min(minAllowed, r.getMinValue());
maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
}
}
// Normalize the value into that range.
//
if (a.getValue() < minAllowed) {
a.setValue(minAllowed);
} else if (a.getValue() > maxAllowed) {
a.setValue(maxAllowed);
}
} }
RefControl controlFor(final CurrentUser user) { RefControl controlFor(final CurrentUser user) {
@@ -172,7 +156,7 @@ public class FunctionState {
/** Run <code>applyTypeFloor</code>, <code>applyRightFloor</code>. */ /** Run <code>applyTypeFloor</code>, <code>applyRightFloor</code>. */
public void normalize(final ApprovalType at, final PatchSetApproval ca) { public void normalize(final ApprovalType at, final PatchSetApproval ca) {
applyTypeFloor(at, ca); applyTypeFloor(at, ca);
applyRightFloor(ca); applyRightFloor(at, ca);
} }
private static Id id(final ApprovalType at) { private static Id id(final ApprovalType at) {

View File

@@ -1,68 +0,0 @@
// Copyright (C) 2008 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.workflow;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.RefControl;
/**
* Computes if the submit function can be used.
* <p>
* In order to be considered "approved" this function requires that all approval
* categories with a position >= 0 (that is any whose
* {@link ApprovalCategory#isAction()} method returns false) is valid and that
* the change state be {@link Change.Status#NEW}.
* <p>
* This is mostly useful for actions, like {@link ApprovalCategory#SUBMIT}.
*/
public class SubmitFunction extends CategoryFunction {
public static String NAME = "Submit";
@Override
public void run(final ApprovalType at, final FunctionState state) {
state.valid(at, valid(at, state));
}
@Override
public boolean isValid(final CurrentUser user, final ApprovalType at,
final FunctionState state) {
if (valid(at, state)) {
RefControl rc = state.controlFor(user);
for (final RefRight pr : rc.getApplicableRights(at.getCategory().getId())) {
if (user.getEffectiveGroups().contains(pr.getAccountGroupUUID())
&& pr.getMaxValue() > 0) {
return true;
}
}
}
return false;
}
private static boolean valid(final ApprovalType at, final FunctionState state) {
if (state.getChange().getStatus() != Change.Status.NEW) {
return false;
}
for (final ApprovalType t : state.getApprovalTypes()) {
if (!state.isValid(t)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,191 @@
// Copyright (C) 2011 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.git;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.gerrit.common.data.AccessSection;
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.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
private final GroupReference developers = new GroupReference(
new AccountGroup.UUID("X"), "Developers");
private final GroupReference staff = new GroupReference(
new AccountGroup.UUID("Y"), "Staff");
private Repository db;
private TestRepository<Repository> util;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
db = createBareRepository();
util = new TestRepository<Repository>(db);
}
@Test
public void testReadConfig() throws Exception {
RevCommit rev = util.commit(util.tree( //
util.file("groups", util.blob(group(developers))), //
util.file("project.config", util.blob(""//
+ "[access \"refs/heads/*\"]\n" //
+ " exclusiveGroupPermissions = read submit create\n" //
+ " submit = group Developers\n" //
+ " push = group Developers\n" //
+ " read = group Developers\n")) //
));
ProjectConfig cfg = read(rev);
AccessSection section = cfg.getAccessSection("refs/heads/*");
assertNotNull("has refs/heads/*", section);
assertNull("no refs/*", cfg.getAccessSection("refs/*"));
Permission create = section.getPermission(Permission.CREATE);
Permission submit = section.getPermission(Permission.SUBMIT);
Permission read = section.getPermission(Permission.READ);
Permission push = section.getPermission(Permission.PUSH);
assertTrue(create.getExclusiveGroup());
assertTrue(submit.getExclusiveGroup());
assertTrue(read.getExclusiveGroup());
assertFalse(push.getExclusiveGroup());
}
@Test
public void testEditConfig() throws Exception {
RevCommit rev = util.commit(util.tree( //
util.file("groups", util.blob(group(developers))), //
util.file("project.config", util.blob(""//
+ "[access \"refs/heads/*\"]\n" //
+ " exclusiveGroupPermissions = read submit\n" //
+ " submit = group Developers\n" //
+ " upload = group Developers\n" //
+ " read = group Developers\n")) //
));
update(rev);
ProjectConfig cfg = read(rev);
AccessSection section = cfg.getAccessSection("refs/heads/*");
Permission submit = section.getPermission(Permission.SUBMIT);
submit.add(new PermissionRule(cfg.resolve(staff)));
rev = commit(cfg);
assertEquals(""//
+ "[access \"refs/heads/*\"]\n" //
+ " exclusiveGroupPermissions = read submit\n" //
+ " submit = group Developers\n" //
+ "\tsubmit = group Staff\n" //
+ " upload = group Developers\n" //
+ " read = group Developers\n", text(rev, "project.config"));
}
@Test
public void testEditConfigMissingGroupTableEntry() throws Exception {
RevCommit rev = util.commit(util.tree( //
util.file("groups", util.blob(group(developers))), //
util.file("project.config", util.blob(""//
+ "[access \"refs/heads/*\"]\n" //
+ " exclusiveGroupPermissions = read submit\n" //
+ " submit = group People Who Can Submit\n" //
+ " upload = group Developers\n" //
+ " read = group Developers\n")) //
));
update(rev);
ProjectConfig cfg = read(rev);
AccessSection section = cfg.getAccessSection("refs/heads/*");
Permission submit = section.getPermission(Permission.SUBMIT);
submit.add(new PermissionRule(cfg.resolve(staff)));
rev = commit(cfg);
assertEquals(""//
+ "[access \"refs/heads/*\"]\n" //
+ " exclusiveGroupPermissions = read submit\n" //
+ " submit = group People Who Can Submit\n" //
+ "\tsubmit = group Staff\n" //
+ " upload = group Developers\n" //
+ " read = group Developers\n", text(rev, "project.config"));
}
private ProjectConfig read(RevCommit rev) throws IOException,
ConfigInvalidException {
ProjectConfig cfg = new ProjectConfig(new Project.NameKey("test"));
cfg.load(db, rev);
return cfg;
}
private RevCommit commit(ProjectConfig cfg) throws IOException,
MissingObjectException, IncorrectObjectTypeException {
MetaDataUpdate md = new MetaDataUpdate(new NoReplication(), //
cfg.getProject().getNameKey(), //
db);
util.tick(5);
util.setAuthorAndCommitter(md.getCommitBuilder());
md.setMessage("Edit\n");
assertTrue("commit finished", cfg.commit(md));
Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
return util.getRevWalk().parseCommit(ref.getObjectId());
}
private void update(RevCommit rev) throws Exception {
RefUpdate u = db.updateRef(GitRepositoryManager.REF_CONFIG);
u.disableRefLog();
u.setNewObjectId(rev);
switch (u.forceUpdate()) {
case FAST_FORWARD:
case FORCED:
case NEW:
case NO_CHANGE:
break;
default:
fail("Cannot update ref for test: " + u.getResult());
}
}
private String text(RevCommit rev, String path) throws Exception {
RevObject blob = util.get(rev.getTree(), path);
byte[] data = db.open(blob).getCachedBytes(Integer.MAX_VALUE);
return RawParseUtils.decode(data);
}
private static String group(GroupReference g) {
return g.getUUID().get() + "\t" + g.getName() + "\n";
}
}

View File

@@ -14,23 +14,24 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN; import static com.google.gerrit.common.data.Permission.OWNER;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ; import static com.google.gerrit.common.data.Permission.PUSH;
import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT; import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.common.data.Permission.SUBMIT;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch; import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.reviewdb.RefRight.RefPattern;
import com.google.gerrit.server.AccessPath; import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
@@ -41,19 +42,17 @@ import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class RefControlTest extends TestCase { public class RefControlTest extends TestCase {
public void testOwnerProject() { public void testOwnerProject() {
grant(local, OWN, admin, "refs/*", 1); grant(local, OWNER, admin, "refs/*");
ProjectControl uBlah = user(devs); ProjectControl uBlah = user(devs);
ProjectControl uAdmin = user(devs, admin); ProjectControl uAdmin = user(devs, admin);
@@ -63,8 +62,8 @@ public class RefControlTest extends TestCase {
} }
public void testBranchDelegation1() { public void testBranchDelegation1() {
grant(local, OWN, admin, "refs/*", 1); grant(local, OWNER, admin, "refs/*");
grant(local, OWN, devs, "refs/heads/x/*", 1); grant(local, OWNER, devs, "refs/heads/x/*");
ProjectControl uDev = user(devs); ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner()); assertFalse("not owner", uDev.isOwner());
@@ -79,9 +78,10 @@ public class RefControlTest extends TestCase {
} }
public void testBranchDelegation2() { public void testBranchDelegation2() {
grant(local, OWN, admin, "refs/*", 1); grant(local, OWNER, admin, "refs/*");
grant(local, OWN, devs, "refs/heads/x/*", 1); grant(local, OWNER, devs, "refs/heads/x/*");
grant(local, OWN, fixers, "-refs/heads/x/y/*", 1); grant(local, OWNER, fixers, "refs/heads/x/y/*");
doNotInherit(local, OWNER, "refs/heads/x/y/*");
ProjectControl uDev = user(devs); ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner()); assertFalse("not owner", uDev.isOwner());
@@ -106,8 +106,11 @@ public class RefControlTest extends TestCase {
} }
public void testInheritRead_SingleBranchDeniesUpload() { public void testInheritRead_SingleBranchDeniesUpload() {
grant(parent, READ, registered, "refs/*", 1, 2); grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "-refs/heads/foobar", 1); grant(parent, PUSH, registered, "refs/for/refs/*");
grant(local, READ, registered, "refs/heads/foobar");
doNotInherit(local, READ, "refs/heads/foobar");
doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
ProjectControl u = user(); ProjectControl u = user();
assertTrue("can upload", u.canPushToAtLeastOneRef()); assertTrue("can upload", u.canPushToAtLeastOneRef());
@@ -120,8 +123,9 @@ public class RefControlTest extends TestCase {
} }
public void testInheritRead_SingleBranchDoesNotOverrideInherited() { public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
grant(parent, READ, registered, "refs/*", 1, 2); grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "refs/heads/foobar", 1); grant(parent, PUSH, registered, "refs/for/refs/*");
grant(local, READ, registered, "refs/heads/foobar");
ProjectControl u = user(); ProjectControl u = user();
assertTrue("can upload", u.canPushToAtLeastOneRef()); assertTrue("can upload", u.canPushToAtLeastOneRef());
@@ -134,16 +138,16 @@ public class RefControlTest extends TestCase {
} }
public void testInheritRead_OverrideWithDeny() { public void testInheritRead_OverrideWithDeny() {
grant(parent, READ, registered, "refs/*", 1); grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "refs/*", 0); grant(local, READ, registered, "refs/*").setDeny(true);
ProjectControl u = user(); ProjectControl u = user();
assertFalse("can't read", u.isVisible()); assertFalse("can't read", u.isVisible());
} }
public void testInheritRead_AppendWithDenyOfRef() { public void testInheritRead_AppendWithDenyOfRef() {
grant(parent, READ, registered, "refs/*", 1); grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "refs/heads/*", 0); grant(local, READ, registered, "refs/heads/*").setDeny(true);
ProjectControl u = user(); ProjectControl u = user();
assertTrue("can read", u.isVisible()); assertTrue("can read", u.isVisible());
@@ -153,9 +157,9 @@ public class RefControlTest extends TestCase {
} }
public void testInheritRead_OverridesAndDeniesOfRef() { public void testInheritRead_OverridesAndDeniesOfRef() {
grant(parent, READ, registered, "refs/*", 1); grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "refs/*", 0); grant(local, READ, registered, "refs/*").setDeny(true);
grant(local, READ, registered, "refs/heads/*", -1, 1); grant(local, READ, registered, "refs/heads/*");
ProjectControl u = user(); ProjectControl u = user();
assertTrue("can read", u.isVisible()); assertTrue("can read", u.isVisible());
@@ -165,9 +169,9 @@ public class RefControlTest extends TestCase {
} }
public void testInheritSubmit_OverridesAndDeniesOfRef() { public void testInheritSubmit_OverridesAndDeniesOfRef() {
grant(parent, SUBMIT, registered, "refs/*", 1); grant(parent, SUBMIT, registered, "refs/*");
grant(local, SUBMIT, registered, "refs/*", 0); grant(local, SUBMIT, registered, "refs/*").setDeny(true);
grant(local, SUBMIT, registered, "refs/heads/*", -1, 1); grant(local, SUBMIT, registered, "refs/heads/*");
ProjectControl u = user(); ProjectControl u = user();
assertFalse("can't submit", u.controlForRef("refs/foobar").canSubmit()); assertFalse("can't submit", u.controlForRef("refs/foobar").canSubmit());
@@ -176,8 +180,9 @@ public class RefControlTest extends TestCase {
} }
public void testCannotUploadToAnyRef() { public void testCannotUploadToAnyRef() {
grant(parent, READ, registered, "refs/*", 1); grant(parent, READ, registered, "refs/*");
grant(local, READ, devs, "refs/heads/*", 1, 2); grant(local, READ, devs, "refs/heads/*");
grant(local, PUSH, devs, "refs/for/refs/heads/*");
ProjectControl u = user(); ProjectControl u = user();
assertFalse("cannot upload", u.canPushToAtLeastOneRef()); assertFalse("cannot upload", u.canPushToAtLeastOneRef());
@@ -188,8 +193,8 @@ public class RefControlTest extends TestCase {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
private final Project.NameKey local = new Project.NameKey("test"); private ProjectConfig local;
private final Project.NameKey parent = new Project.NameKey("parent"); private ProjectConfig parent;
private final AccountGroup.UUID admin = new AccountGroup.UUID("test.admin"); private final AccountGroup.UUID admin = new AccountGroup.UUID("test.admin");
private final AccountGroup.UUID anonymous = AccountGroup.ANONYMOUS_USERS; private final AccountGroup.UUID anonymous = AccountGroup.ANONYMOUS_USERS;
private final AccountGroup.UUID registered = AccountGroup.REGISTERED_USERS; private final AccountGroup.UUID registered = AccountGroup.REGISTERED_USERS;
@@ -201,9 +206,6 @@ public class RefControlTest extends TestCase {
private final AuthConfig authConfig; private final AuthConfig authConfig;
private final AnonymousUser anonymousUser; private final AnonymousUser anonymousUser;
private final Map<AccountGroup.UUID, AccountGroup.Id> groupIds =
new HashMap<AccountGroup.UUID, AccountGroup.Id>();
public RefControlTest() { public RefControlTest() {
systemConfig = SystemConfig.create(); systemConfig = SystemConfig.create();
systemConfig.adminGroupUUID = admin; systemConfig.adminGroupUUID = admin;
@@ -231,14 +233,16 @@ public class RefControlTest extends TestCase {
anonymousUser = injector.getInstance(AnonymousUser.class); anonymousUser = injector.getInstance(AnonymousUser.class);
} }
private List<RefRight> localRights;
private List<RefRight> inheritedRights;
@Override @Override
protected void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
localRights = new ArrayList<RefRight>();
inheritedRights = new ArrayList<RefRight>(); parent = new ProjectConfig(new Project.NameKey("parent"));
parent.createInMemory();
local = new ProjectConfig(new Project.NameKey("local"));
local.createInMemory();
local.getProject().setParentName(parent.getProject().getName());
} }
private static void assertOwner(String ref, ProjectControl u) { private static void assertOwner(String ref, ProjectControl u) {
@@ -249,33 +253,27 @@ public class RefControlTest extends TestCase {
assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner()); assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner());
} }
private void grant(Project.NameKey project, ApprovalCategory.Id categoryId, private PermissionRule grant(ProjectConfig project, String permissionName,
AccountGroup.UUID group, String ref, int maxValue) { AccountGroup.UUID group, String ref) {
grant(project, categoryId, group, ref, maxValue, maxValue); PermissionRule rule = newRule(project, group);
project.getAccessSection(ref, true) //
.getPermission(permissionName, true) //
.add(rule);
return rule;
} }
private void grant(Project.NameKey project, ApprovalCategory.Id categoryId, private void doNotInherit(ProjectConfig project, String permissionName,
AccountGroup.UUID groupUUID, String ref, int minValue, int maxValue) { String ref) {
AccountGroup.Id groupId = groupIds.get(groupUUID); project.getAccessSection(ref, true) //
if (groupId == null) { .getPermission(permissionName, true) //
groupId = new AccountGroup.Id(groupIds.size() + 1); .setExclusiveGroup(true);
groupIds.put(groupUUID, groupId); }
}
RefRight right = private PermissionRule newRule(ProjectConfig project, AccountGroup.UUID groupUUID) {
new RefRight(new RefRight.Key(project, new RefPattern(ref), GroupReference group = new GroupReference(groupUUID, groupUUID.get());
categoryId, groupId)); group = project.resolve(group);
right.setAccountGroupUUID(groupUUID);
right.setMinValue((short) minValue);
right.setMaxValue((short) maxValue);
if (project == parent) { return new PermissionRule(group);
inheritedRights.add(right);
} else if (project == local) {
localRights.add(right);
} else {
fail("Unknown project key: " + project);
}
} }
private ProjectControl user(AccountGroup.UUID... memberOf) { private ProjectControl user(AccountGroup.UUID... memberOf) {
@@ -291,15 +289,40 @@ public class RefControlTest extends TestCase {
} }
private ProjectState newProjectState() { private ProjectState newProjectState() {
ProjectCache projectCache = null; final Map<Project.NameKey, ProjectState> all =
new HashMap<Project.NameKey, ProjectState>();
final ProjectCache projectCache = new ProjectCache() {
@Override
public ProjectState get(Project.NameKey projectName) {
return all.get(projectName);
}
@Override
public void evict(Project p) {
}
@Override
public Iterable<Project.NameKey> all() {
return Collections.emptySet();
}
@Override
public Iterable<Project.NameKey> byName(String prefix) {
return Collections.emptySet();
}
@Override
public void onCreateProject(Project.NameKey newProjectName) {
}
};
Project.NameKey wildProject = new Project.NameKey("-- All Projects --"); Project.NameKey wildProject = new Project.NameKey("-- All Projects --");
ProjectControl.AssistedFactory projectControlFactory = null; ProjectControl.AssistedFactory projectControlFactory = null;
Project project = new Project(parent); all.put(local.getProject().getNameKey(), new ProjectState(anonymousUser,
ProjectState ps = projectCache, wildProject, projectControlFactory, local));
new ProjectState(anonymousUser, projectCache, wildProject, all.put(parent.getProject().getNameKey(), new ProjectState(anonymousUser,
projectControlFactory, project, localRights); projectCache, wildProject, projectControlFactory, parent));
ps.setInheritedRights(inheritedRights); return all.get(local.getProject().getNameKey());
return ps;
} }
private class MockUser extends CurrentUser { private class MockUser extends CurrentUser {

View File

@@ -17,11 +17,8 @@ package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue; import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.workflow.NoOpFunction;
import com.google.gerrit.server.workflow.SubmitFunction;
import com.google.gerrit.testutil.InMemoryDatabase; import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcSchema; import com.google.gwtorm.jdbc.JdbcSchema;
@@ -163,7 +160,6 @@ public class SchemaCreatorTest extends TestCase {
assertEquals("R", cat.getAbbreviatedName()); assertEquals("R", cat.getAbbreviatedName());
assertEquals("MaxWithBlock", cat.getFunctionName()); assertEquals("MaxWithBlock", cat.getFunctionName());
assertTrue(cat.isCopyMinScore()); assertTrue(cat.isCopyMinScore());
assertFalse(cat.isAction());
assertTrue(0 <= cat.getPosition()); assertTrue(0 <= cat.getPosition());
} finally { } finally {
c.close(); c.close();
@@ -171,101 +167,6 @@ public class SchemaCreatorTest extends TestCase {
assertValueRange(codeReview, -2, -1, 0, 1, 2); assertValueRange(codeReview, -2, -1, 0, 1, 2);
} }
public void testCreateSchema_ApprovalCategory_Read() throws OrmException {
final ReviewDb c = db.create().open();
try {
final ApprovalCategory cat;
cat = c.approvalCategories().get(ApprovalCategory.READ);
assertNotNull(cat);
assertEquals(ApprovalCategory.READ, cat.getId());
assertEquals("Read Access", cat.getName());
assertNull(cat.getAbbreviatedName());
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
assertTrue(cat.isAction());
} finally {
c.close();
}
assertValueRange(ApprovalCategory.READ, -1, 1, 2, 3);
}
public void testCreateSchema_ApprovalCategory_Submit() throws OrmException {
final ReviewDb c = db.create().open();
try {
final ApprovalCategory cat;
cat = c.approvalCategories().get(ApprovalCategory.SUBMIT);
assertNotNull(cat);
assertEquals(ApprovalCategory.SUBMIT, cat.getId());
assertEquals("Submit", cat.getName());
assertNull(cat.getAbbreviatedName());
assertEquals(SubmitFunction.NAME, cat.getFunctionName());
assertTrue(cat.isAction());
} finally {
c.close();
}
assertValueRange(ApprovalCategory.SUBMIT, 1);
}
public void testCreateSchema_ApprovalCategory_PushTag() throws OrmException {
final ReviewDb c = db.create().open();
try {
final ApprovalCategory cat;
cat = c.approvalCategories().get(ApprovalCategory.PUSH_TAG);
assertNotNull(cat);
assertEquals(ApprovalCategory.PUSH_TAG, cat.getId());
assertEquals("Push Tag", cat.getName());
assertNull(cat.getAbbreviatedName());
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
assertTrue(cat.isAction());
} finally {
c.close();
}
assertValueRange(ApprovalCategory.PUSH_TAG, //
ApprovalCategory.PUSH_TAG_SIGNED, //
ApprovalCategory.PUSH_TAG_ANNOTATED);
}
public void testCreateSchema_ApprovalCategory_PushHead() throws OrmException {
final ReviewDb c = db.create().open();
try {
final ApprovalCategory cat;
cat = c.approvalCategories().get(ApprovalCategory.PUSH_HEAD);
assertNotNull(cat);
assertEquals(ApprovalCategory.PUSH_HEAD, cat.getId());
assertEquals("Push Branch", cat.getName());
assertNull(cat.getAbbreviatedName());
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
assertTrue(cat.isAction());
} finally {
c.close();
}
assertValueRange(ApprovalCategory.PUSH_HEAD, //
ApprovalCategory.PUSH_HEAD_UPDATE, //
ApprovalCategory.PUSH_HEAD_CREATE, //
ApprovalCategory.PUSH_HEAD_REPLACE);
}
public void testCreateSchema_ApprovalCategory_Owner() throws OrmException {
final ReviewDb c = db.create().open();
try {
final ApprovalCategory cat;
cat = c.approvalCategories().get(ApprovalCategory.OWN);
assertNotNull(cat);
assertEquals(ApprovalCategory.OWN, cat.getId());
assertEquals("Owner", cat.getName());
assertNull(cat.getAbbreviatedName());
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
assertTrue(cat.isAction());
} finally {
c.close();
}
assertValueRange(ApprovalCategory.OWN, 1);
}
private void assertValueRange(ApprovalCategory.Id cat, int... range) private void assertValueRange(ApprovalCategory.Id cat, int... range)
throws OrmException { throws OrmException {
final HashSet<ApprovalCategoryValue.Id> act = final HashSet<ApprovalCategoryValue.Id> act =
@@ -295,55 +196,4 @@ public class SchemaCreatorTest extends TestCase {
fail("Category " + cat + " has additional values: " + act); fail("Category " + cat + " has additional values: " + act);
} }
} }
public void testCreateSchema_DefaultAccess_AnonymousUsers()
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight("refs/*", config.anonymousGroupId,
ApprovalCategory.READ, 1, 1);
}
public void testCreateSchema_DefaultAccess_RegisteredUsers()
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight("refs/*", config.registeredGroupId,
ApprovalCategory.READ, 1, 2);
assertDefaultRight("refs/heads/*", config.registeredGroupId, codeReview,
-1, 1);
}
public void testCreateSchema_DefaultAccess_Administrators()
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight("refs/*", config.adminGroupId, ApprovalCategory.READ, 1,
1);
}
private void assertDefaultRight(final String pattern,
final AccountGroup.Id group, final ApprovalCategory.Id category, int min,
int max) throws OrmException {
final ReviewDb c = db.open();
try {
final SystemConfig cfg;
final RefRight right;
cfg = c.systemConfig().get(new SystemConfig.Key());
right =
c.refRights().get(
new RefRight.Key(cfg.wildProjectName, new RefRight.RefPattern(
pattern), category, group));
assertNotNull(right);
assertEquals(cfg.wildProjectName, right.getProjectNameKey());
assertEquals(group, right.getAccountGroupId());
assertEquals(category, right.getApprovalCategoryId());
assertEquals(min, right.getMinValue());
assertEquals(max, right.getMaxValue());
} finally {
c.close();
}
}
} }

View File

@@ -135,10 +135,6 @@ final class AdminSetParent extends BaseCommand {
} }
} }
// Invalidate all projects in cache since inherited rights were changed.
//
projectCache.evictAll();
if (err.length() > 0) { if (err.length() > 0) {
while (err.charAt(err.length() - 1) == '\n') { while (err.charAt(err.length() - 1) == '\n') {
err.setLength(err.length() - 1); err.setLength(err.length() - 1);

View File

@@ -15,11 +15,12 @@
package com.google.gerrit.sshd.commands; package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.CollectionsUtil; import com.google.gerrit.common.CollectionsUtil;
import com.google.gerrit.common.data.AccessSection;
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.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.SubmitType; import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@@ -33,7 +34,6 @@ import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl; import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.sshd.BaseCommand; import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
@@ -96,9 +96,6 @@ final class CreateProject extends BaseCommand {
@Option(name = "--empty-commit", usage = "to create initial empty commit") @Option(name = "--empty-commit", usage = "to create initial empty commit")
private boolean createEmptyCommit; private boolean createEmptyCommit;
@Inject
private ReviewDb db;
@Inject @Inject
private GitRepositoryManager repoManager; private GitRepositoryManager repoManager;
@@ -202,27 +199,11 @@ final class CreateProject extends BaseCommand {
} }
} }
private void createProject() throws OrmException, IOException, private void createProject() throws IOException, ConfigInvalidException {
ConfigInvalidException {
List<RefRight> access = new ArrayList<RefRight>();
for (AccountGroup.UUID ownerId : ownerIds) {
AccountGroup group = groupCache.get(ownerId);
final RefRight.Key prk =
new RefRight.Key(nameKey, new RefRight.RefPattern(
RefRight.ALL), ApprovalCategory.OWN, group.getId());
final RefRight pr = new RefRight(prk);
pr.setMaxValue((short) 1);
pr.setMinValue((short) 1);
access.add(pr);
}
db.refRights().insert(access);
Project.NameKey nameKey = new Project.NameKey(projectName);
MetaDataUpdate md = metaDataUpdateFactory.create(nameKey); MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
try { try {
ProjectConfig config = ProjectConfig.read(md); ProjectConfig config = ProjectConfig.read(md);
config.load(md);
Project newProject = config.getProject(); Project newProject = config.getProject();
newProject.setDescription(projectDescription); newProject.setDescription(projectDescription);
@@ -235,6 +216,16 @@ final class CreateProject extends BaseCommand {
newProject.setParentName(newParent.getProject().getName()); newProject.setParentName(newParent.getProject().getName());
} }
if (!ownerIds.isEmpty()) {
AccessSection all = config.getAccessSection(AccessSection.ALL, true);
for (AccountGroup.UUID ownerId : ownerIds) {
AccountGroup accountGroup = groupCache.get(ownerId);
GroupReference group = config.resolve(accountGroup);
all.getPermission(Permission.OWNER, true).add(
new PermissionRule(group));
}
}
md.setMessage("Created project\n"); md.setMessage("Created project\n");
if (!config.commit(md)) { if (!config.commit(md)) {
throw new IOException("Cannot create " + projectName); throw new IOException("Cannot create " + projectName);

View File

@@ -331,7 +331,7 @@ public class ReviewCommand extends BaseCommand {
functionStateFactory.create(changeControl.getChange(), patchSetId, functionStateFactory.create(changeControl.getChange(), patchSetId,
Collections.<PatchSetApproval> emptyList()); Collections.<PatchSetApproval> emptyList());
psa.setValue(v); psa.setValue(v);
fs.normalize(approvalTypes.getApprovalType(psa.getCategoryId()), psa); fs.normalize(approvalTypes.byId(psa.getCategoryId()), psa);
if (v != psa.getValue()) { if (v != psa.getValue()) {
throw error(ao.name() + "=" + ao.value() + " not permitted"); throw error(ao.name() + "=" + ao.value() + " not permitted");
} }