diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java index 44a00d5eea..12e504c512 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java @@ -24,6 +24,9 @@ import java.util.Set; /** Portion of a {@link Project} describing access rules. */ public class AccessSection implements Comparable { + /** Special name given to the global capabilities; not a valid reference. */ + public static final String GLOBAL_CAPABILITIES = "GLOBAL_CAPABILITIES"; + /** Pattern that matches all references in a project. */ public static final String ALL = "refs/*"; @@ -38,22 +41,22 @@ public class AccessSection implements Comparable { return name.startsWith("refs/") || name.startsWith("^refs/"); } - protected String refPattern; + protected String name; protected List permissions; protected AccessSection() { } public AccessSection(String refPattern) { - setRefPattern(refPattern); + setName(refPattern); } - public String getRefPattern() { - return refPattern; + public String getName() { + return name; } - public void setRefPattern(String refPattern) { - this.refPattern = refPattern; + public void setName(String name) { + this.name = name; } public List getPermissions() { @@ -127,14 +130,14 @@ public class AccessSection implements Comparable { } private String comparePattern() { - if (getRefPattern().startsWith(REGEX_PREFIX)) { - return getRefPattern().substring(REGEX_PREFIX.length()); + if (getName().startsWith(REGEX_PREFIX)) { + return getName().substring(REGEX_PREFIX.length()); } - return getRefPattern(); + return getName(); } @Override public String toString() { - return "AccessSection[" + getRefPattern() + "]"; + return "AccessSection[" + getName() + "]"; } } diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java new file mode 100644 index 0000000000..0afdd042c3 --- /dev/null +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java @@ -0,0 +1,55 @@ +// 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.common.data; + +import java.util.ArrayList; +import java.util.List; + +/** Server wide capabilities. Represented as {@link Permission} objects. */ +public class GlobalCapability { + public static final String QUERY_LIMIT = "queryLimit"; + + private static final List NAMES_LC; + + static { + NAMES_LC = new ArrayList(); + NAMES_LC.add(QUERY_LIMIT.toLowerCase()); + } + + /** @return true if the name is recognized as a capability name. */ + public static boolean isCapability(String varName) { + return NAMES_LC.contains(varName.toLowerCase()); + } + + /** @return true if the capability should have a range attached. */ + public static boolean hasRange(String varName) { + return QUERY_LIMIT.equalsIgnoreCase(varName); + } + + /** @return the valid range for the capability if it has one, otherwise null. */ + public static PermissionRange.WithDefaults getRange(String varName) { + if (QUERY_LIMIT.equalsIgnoreCase(varName)) { + return new PermissionRange.WithDefaults( + varName, + 0, Integer.MAX_VALUE, + 0, 500); + } + return null; + } + + private GlobalCapability() { + // Utility class, do not create instances. + } +} diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java index bc2dadde76..3490dd757d 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java @@ -14,7 +14,50 @@ package com.google.gerrit.common.data; +import java.util.ArrayList; +import java.util.List; + public class PermissionRange implements Comparable { + public static class WithDefaults extends PermissionRange { + protected int defaultMin; + protected int defaultMax; + + protected WithDefaults() { + } + + public WithDefaults(String name, int min, int max, int defMin, int defMax) { + super(name, min, max); + setDefaultRange(defMin, defMax); + } + + public int getDefaultMin() { + return defaultMin; + } + + public int getDefaultMax() { + return defaultMax; + } + + public void setDefaultRange(int min, int max) { + defaultMin = min; + defaultMax = max; + } + + /** @return all values between {@link #getMin()} and {@link #getMax()} */ + public List getValuesAsList() { + ArrayList r = new ArrayList(getRangeSize()); + for (int i = min; i <= max; i++) { + r.add(i); + } + return r; + } + + /** @return number of values between {@link #getMin()} and {@link #getMax()} */ + public int getRangeSize() { + return max - min; + } + } + protected String name; protected int min; protected int max; diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java index 5c73c52d5c..61f5d62fe3 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java @@ -170,7 +170,7 @@ public class PermissionRule implements Comparable { int sp = src.indexOf(' '); String range = src.substring(0, sp); - if (range.matches("^([+-]\\d+)\\.\\.([+-]\\d)$")) { + if (range.matches("^([+-]?\\d+)\\.\\.([+-]?\\d+)$")) { int dotdot = range.indexOf(".."); int min = parseInt(range.substring(0, dotdot)); int max = parseInt(range.substring(dotdot + 2)); diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java index 2fbf512002..168544dac7 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java @@ -52,8 +52,21 @@ public class ProjectAccess { local = as; } + public AccessSection getLocal(String name) { + for (AccessSection s : local) { + if (s.getName().equals(name)) { + return s; + } + } + return null; + } + public boolean isOwnerOf(AccessSection section) { - return getOwnerOf().contains(section.getRefPattern()); + return isOwnerOf(section.getName()); + } + + public boolean isOwnerOf(String name) { + return ownerOf.contains(name); } public Set getOwnerOf() { diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java index 4a66a416a7..53792e4284 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java @@ -18,6 +18,10 @@ package com.google.gerrit.common.errors; public class InvalidQueryException extends Exception { private static final long serialVersionUID = 1L; + public InvalidQueryException(String message) { + super(message); + } + public InvalidQueryException(String message, String query) { super("Invalid query: " + query + "\n\n" + message); } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java index fdcaa00829..a30629b9a2 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java @@ -55,7 +55,7 @@ public class AccessSectionEditor extends Composite implements private static final Binder uiBinder = GWT.create(Binder.class); @UiField - ValueEditor refPattern; + ValueEditor name; @UiField FlowPanel permissionContainer; @@ -78,6 +78,11 @@ public class AccessSectionEditor extends Composite implements @UiField DivElement deleted; + @UiField + SpanElement sectionType; + @UiField + SpanElement sectionName; + private final ProjectAccess projectAccess; private AccessSection value; private boolean editing; @@ -115,7 +120,18 @@ public class AccessSectionEditor extends Composite implements @UiHandler("deleteSection") void onDeleteSection(ClickEvent event) { isDeleted = true; - deletedName.setInnerText(refPattern.getValue()); + + if (name.isVisible() && AccessSection.isAccessSection(name.getValue())){ + deletedName.setInnerText(Util.M.deletedReference(name.getValue())); + + } else { + String name = Util.C.sectionNames().get(value.getName()); + if (name == null) { + name = value.getName(); + } + deletedName.setInnerText(Util.M.deletedSection(name)); + } + normal.getStyle().setDisplay(Display.NONE); deleted.getStyle().setDisplay(Display.BLOCK); } @@ -134,11 +150,11 @@ public class AccessSectionEditor extends Composite implements } void editRefPattern() { - refPattern.edit(); + name.edit(); Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { - refPattern.setFocus(true); + name.setFocus(true); }}); } @@ -157,9 +173,29 @@ public class AccessSectionEditor extends Composite implements this.value = value; this.readOnly = !editing || !projectAccess.isOwnerOf(value); - refPattern.setEnabled(!readOnly); + name.setEnabled(!readOnly); deleteSection.setVisible(!readOnly); + if (AccessSection.isAccessSection(value.getName())) { + name.setVisible(true); + name.setIgnoreEditorValue(false); + sectionType.setInnerText(Util.C.sectionTypeReference()); + + } else { + name.setVisible(false); + name.setIgnoreEditorValue(true); + + String name = Util.C.sectionNames().get(value.getName()); + if (name != null) { + sectionType.setInnerText(name); + sectionName.getStyle().setDisplay(Display.NONE); + } else { + sectionType.setInnerText(Util.C.sectionTypeSection()); + sectionName.setInnerText(value.getName()); + sectionName.getStyle().clearDisplay(); + } + } + if (readOnly) { addContainer.getStyle().setDisplay(Display.NONE); } else { @@ -173,16 +209,24 @@ public class AccessSectionEditor extends Composite implements private void rebuildPermissionSelector() { List perms = new ArrayList(); - for (ApprovalType t : Gerrit.getConfig().getApprovalTypes() - .getApprovalTypes()) { - String varName = Permission.LABEL + t.getCategory().getLabelName(); - if (value.getPermission(varName) == null) { - perms.add(varName); + + if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) { + for (String varName : Util.C.capabilityNames().keySet()) { + if (value.getPermission(varName) == null) { + perms.add(varName); + } } - } - for (String varName : Util.C.permissionNames().keySet()) { - if (value.getPermission(varName) == null) { - perms.add(varName); + } else if (AccessSection.isAccessSection(value.getName())) { + for (ApprovalType t : Gerrit.getConfig().getApprovalTypes().getApprovalTypes()) { + String varName = Permission.LABEL + t.getCategory().getLabelName(); + if (value.getPermission(varName) == null) { + perms.add(varName); + } + } + for (String varName : Util.C.permissionNames().keySet()) { + if (value.getPermission(varName) == null) { + perms.add(varName); + } } } if (perms.isEmpty()) { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml index fdae4edb9d..29ae2d6f81 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml @@ -66,10 +66,10 @@ limitations under the License. background-color: selectionColor; } - .refName { + .name { width: 100%; } - .refNameEdit { + .nameEdit { width: 100%; } @@ -107,17 +107,20 @@ limitations under the License.
- +
Reference: + + - + +
@@ -144,7 +147,7 @@ limitations under the License. ui:field='deleted' class='{style.deleted} {res.css.deleted}' style='display: none'> - Reference was deleted + capabilityNames(); + + String sectionTypeReference(); + String sectionTypeSection(); + Map sectionNames(); } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index e9575cfe80..563570f610 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties @@ -116,3 +116,15 @@ refErrorDoubleSlash = References cannot contain '//' refErrorNoSpace = References cannot contain spaces refErrorPrintable = References may contain only printable characters errorsMustBeFixed = Errors must be fixed before committing changes. + +# Capability Names +capabilityNames = \ + queryLimit +queryLimit = Query Limit + +# Section Names +sectionTypeReference = Reference: +sectionTypeSection = Section: +sectionNames = \ + GLOBAL_CAPABILITIES +GLOBAL_CAPABILITIES = Global Capabilities diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java index 9ce3ccf217..3b4d7d41ce 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java @@ -21,4 +21,7 @@ public interface AdminMessages extends Messages { String label(String name); String project(String name); String deletedGroup(int id); + + String deletedReference(String name); + String deletedSection(String name); } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties index 60d9c70a9b..7f8cd562e4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties @@ -2,3 +2,5 @@ group = Group {0} label = Label {0} project = Project {0} deletedGroup = Deleted Group {0} +deletedReference = Reference {0} was deleted +deletedSection = Section {0} was deleted diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java index c21844ce11..351d0463ad 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java @@ -19,8 +19,10 @@ import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.SuggestUtil; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.ApprovalType; +import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; @@ -98,7 +100,7 @@ public class PermissionEditor extends Composite implements Editor, private final boolean readOnly; private final AccessSection section; private Permission value; - private ApprovalType rangeType; + private PermissionRange.WithDefaults validRange; private boolean isDeleted; public PermissionEditor(boolean readOnly, AccessSection section) { @@ -196,9 +198,9 @@ public class PermissionEditor extends Composite implements Editor, if (ref.getUUID() != null) { if (value.getRule(ref) == null) { PermissionRule newRule = value.getRule(ref, true); - if (rangeType != null) { - int min = rangeType.getMin().getValue(); - int max = rangeType.getMax().getValue(); + if (validRange != null) { + int min = validRange.getDefaultMin(); + int max = validRange.getDefaultMax(); newRule.setRange(min, max); } rules.getList().add(newRule); @@ -238,11 +240,20 @@ public class PermissionEditor extends Composite implements Editor, @Override public void setValue(Permission value) { this.value = value; + if (value.isLabel()) { - rangeType = - Gerrit.getConfig().getApprovalTypes().byLabel(value.getLabel()); + ApprovalType at = Gerrit.getConfig().getApprovalTypes().byLabel(value.getLabel()); + if (at != null) { + validRange = new PermissionRange.WithDefaults( + value.getName(), + at.getMin().getValue(), at.getMax().getValue(), + at.getMin().getValue(), at.getMax().getValue()); + } + } else if (GlobalCapability.isCapability(value.getName())) { + validRange = GlobalCapability.getRange(value.getName()); + } else { - rangeType = null; + validRange = null; } if (value != null && Permission.OWNER.equals(value.getName())) { @@ -279,7 +290,7 @@ public class PermissionEditor extends Composite implements Editor, @Override public PermissionRuleEditor create(int index) { PermissionRuleEditor subEditor = - new PermissionRuleEditor(readOnly, section, value, rangeType); + new PermissionRuleEditor(readOnly, section, value, validRange); ruleContainer.insert(subEditor, index); return subEditor; } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java index 1dea1fb664..ad3473c796 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java @@ -24,7 +24,19 @@ import java.util.Map; class PermissionNameRenderer implements Renderer { static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer(); - private static Map LC; + private static final Map all; + + static { + all = new HashMap(); + for (Map.Entry e : Util.C.capabilityNames().entrySet()) { + all.put(e.getKey(), e.getValue()); + all.put(e.getKey().toLowerCase(), e.getValue()); + } + for (Map.Entry e : Util.C.permissionNames().entrySet()) { + all.put(e.getKey(), e.getValue()); + all.put(e.getKey().toLowerCase(), e.getValue()); + } + } @Override public String render(String varName) { @@ -32,16 +44,9 @@ class PermissionNameRenderer implements Renderer { return Util.M.label(new Permission(varName).getLabel()); } - Map m = Util.C.permissionNames(); - String desc = m.get(varName); + String desc = all.get(varName); if (desc == null) { - if (LC == null) { - LC = new HashMap(); - for (Map.Entry e : m.entrySet()) { - LC.put(e.getKey().toLowerCase(), e.getValue()); - } - } - desc = LC.get(varName.toLowerCase()); + desc = all.get(varName.toLowerCase()); } return desc != null ? desc : varName; } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java index 7ab9c22acf..b609630bb4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java @@ -20,9 +20,9 @@ import static com.google.gerrit.common.data.Permission.PUSH_TAG; import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.ui.Hyperlink; import com.google.gerrit.common.data.AccessSection; -import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; @@ -30,7 +30,9 @@ import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.editor.client.Editor; import com.google.gwt.editor.client.EditorDelegate; +import com.google.gwt.editor.client.IsEditor; import com.google.gwt.editor.client.ValueAwareEditor; +import com.google.gwt.editor.client.adapters.TakesValueEditor; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.text.shared.Renderer; import com.google.gwt.uibinder.client.UiBinder; @@ -41,11 +43,13 @@ import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.IntegerBox; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.ValueListBox; import java.io.IOException; import java.util.Arrays; +import java.util.List; public class PermissionRuleEditor extends Composite implements Editor, ValueAwareEditor { @@ -58,10 +62,10 @@ public class PermissionRuleEditor extends Composite implements ValueListBox action; @UiField(provided = true) - ValueListBox min; + RangeBox min; @UiField(provided = true) - ValueListBox max; + RangeBox max; @UiField CheckBox force; @@ -87,18 +91,30 @@ public class PermissionRuleEditor extends Composite implements private boolean isDeleted; public PermissionRuleEditor(boolean readOnly, AccessSection section, - Permission permission, ApprovalType labelRange) { + Permission permission, PermissionRange.WithDefaults validRange) { action = new ValueListBox(actionRenderer); - min = new ValueListBox(rangeRenderer); - max = new ValueListBox(rangeRenderer); - if (labelRange != null){ - min.setValue((int) labelRange.getMin().getValue()); - max.setValue((int) labelRange.getMax().getValue()); + if (validRange != null && 10 < validRange.getRangeSize()) { + min = new RangeBox.Box(); + max = new RangeBox.Box(); + + } else if (validRange != null) { + RangeBox.List minList = new RangeBox.List(); + RangeBox.List maxList = new RangeBox.List(); + List valueList = validRange.getValuesAsList(); + + minList.list.setValue(validRange.getMin()); + maxList.list.setValue(validRange.getMax()); + + minList.list.setAcceptableValues(valueList); + maxList.list.setAcceptableValues(valueList); + + min = minList; + max = maxList; - min.setAcceptableValues(labelRange.getValuesAsList()); - max.setAcceptableValues(labelRange.getValuesAsList()); } else { + min = new RangeBox.Box(); + max = new RangeBox.Box(); action.setValue(PermissionRule.Action.ALLOW); action.setAcceptableValues(Arrays.asList(PermissionRule.Action.values())); } @@ -108,16 +124,17 @@ public class PermissionRuleEditor extends Composite implements String name = permission.getName(); boolean canForce = PUSH.equals(name) || PUSH_TAG.equals(name); if (canForce) { - String ref = section.getRefPattern(); + String ref = section.getName(); canForce = !ref.startsWith("refs/for/") && !ref.startsWith("^refs/for/"); } force.setVisible(canForce); force.setEnabled(!readOnly); - if (labelRange != null) { + if (validRange != null) { + min.setEnabled(!readOnly); + max.setEnabled(!readOnly); action.getElement().getStyle().setDisplay(Display.NONE); - DOM.setElementPropertyBoolean(min.getElement(), "disabled", readOnly); - DOM.setElementPropertyBoolean(max.getElement(), "disabled", readOnly); + } else { rangeEditor.getStyle().setDisplay(Display.NONE); DOM.setElementPropertyBoolean(action.getElement(), "disabled", readOnly); @@ -188,23 +205,5 @@ public class PermissionRuleEditor extends Composite implements } } - private static class RangeRenderer implements Renderer { - @Override - public String render(Integer object) { - if (0 <= object) { - return "+" + object; - } else { - return String.valueOf(object); - } - } - - @Override - public void render(Integer object, Appendable appendable) - throws IOException { - appendable.append(render(object)); - } - } - private static final ActionRenderer actionRenderer = new ActionRenderer(); - private static final RangeRenderer rangeRenderer = new RangeRenderer(); } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml index 15e201e0c9..e60f3dfc18 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml @@ -74,8 +74,8 @@ limitations under the License.
- - + + diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java index c550d8c5ec..f3c133ba1e 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java @@ -80,6 +80,15 @@ public class ProjectAccessEditor extends Composite implements @Override public void setValue(ProjectAccess value) { + // If the owner can edit the Global Capabilities but they don't exist in this + // project, create an empty one at the beginning of the list making it + // possible to add permissions to it. + if (editing + && value.isOwnerOf(AccessSection.GLOBAL_CAPABILITIES) + && value.getLocal(AccessSection.GLOBAL_CAPABILITIES) == null) { + value.getLocal().add(0, new AccessSection(AccessSection.GLOBAL_CAPABILITIES)); + } + this.value = value; Project.NameKey parent = value.getInheritsFrom(); @@ -102,7 +111,7 @@ public class ProjectAccessEditor extends Composite implements for (int i = 0; i < src.size(); i++) { AccessSectionEditor e = (AccessSectionEditor) localContainer.getWidget(i); - if (!e.isDeleted()) { + if (!e.isDeleted() && !src.get(i).getPermissions().isEmpty()) { keep.add(src.get(i)); } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java index fc864de8d2..d684c25a64 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java @@ -18,6 +18,7 @@ import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.common.PageLinks; +import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.ProjectAccess; import com.google.gerrit.reviewdb.Project; import com.google.gwt.core.client.GWT; diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java new file mode 100644 index 0000000000..e07829ea3f --- /dev/null +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java @@ -0,0 +1,90 @@ +// 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.client.admin; + +import com.google.gwt.editor.client.IsEditor; +import com.google.gwt.editor.client.adapters.TakesValueEditor; +import com.google.gwt.text.shared.Renderer; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.IntegerBox; +import com.google.gwt.user.client.ui.ValueListBox; +import com.google.gwt.user.client.ui.ValueBoxBase.TextAlignment; + +import java.io.IOException; + +abstract class RangeBox extends Composite implements + IsEditor> { + static final RangeRenderer rangeRenderer = new RangeRenderer(); + + private static class RangeRenderer implements Renderer { + @Override + public String render(Integer object) { + if (0 <= object) { + return "+" + object; + } else { + return String.valueOf(object); + } + } + + @Override + public void render(Integer object, Appendable appendable) + throws IOException { + appendable.append(render(object)); + } + } + + static class List extends RangeBox { + final ValueListBox list; + + List() { + list = new ValueListBox(rangeRenderer); + initWidget(list); + } + + @Override + void setEnabled(boolean on) { + DOM.setElementPropertyBoolean(list.getElement(), "disabled", !on); + } + + @Override + public TakesValueEditor asEditor() { + return list.asEditor(); + } + } + + static class Box extends RangeBox { + private final IntegerBox box; + + Box() { + box = new IntegerBox(); + box.setVisibleLength(10); + box.setAlignment(TextAlignment.RIGHT); + initWidget(box); + } + + @Override + void setEnabled(boolean on) { + DOM.setElementPropertyBoolean(box.getElement(), "disabled", !on); + } + + @Override + public TakesValueEditor asEditor() { + return box.asEditor(); + } + } + + abstract void setEnabled(boolean on); +} diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java index d8b8c5d3fd..a7463b42f1 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java @@ -64,6 +64,8 @@ public class ValueEditor extends Composite implements HasEditorErrors, private ValueBoxBase editChild; private ValueBoxEditor editProxy; + private boolean ignoreEditorValue; + private T value; public ValueEditor() { startHandlers = new StartEditHandlers(); @@ -93,14 +95,19 @@ public class ValueEditor extends Composite implements HasEditorErrors, @Override public T getValue() { - return asEditor().getValue(); + return ignoreEditorValue ? value : asEditor().getValue(); } @Override public void setValue(T value) { + this.value = value; asEditor().setValue(value); } + void setIgnoreEditorValue(boolean off) { + ignoreEditorValue = off; + } + public void setEditTitle(String title) { editIcon.setTitle(title); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java index 5c19e117b5..09c2c8174f 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java @@ -17,6 +17,8 @@ package com.google.gerrit.httpd.rpc; import com.google.gerrit.common.data.AccountDashboardInfo; import com.google.gerrit.common.data.ChangeInfo; import com.google.gerrit.common.data.ChangeListService; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.SingleListChangeInfo; import com.google.gerrit.common.data.ToggleStarRequest; import com.google.gerrit.common.errors.InvalidQueryException; @@ -29,8 +31,10 @@ import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.StarredChange; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.account.AccountInfoCacheFactory; +import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.QueryParseException; import com.google.gerrit.server.query.change.ChangeData; @@ -79,15 +83,10 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements } }; - private static final int MAX_PER_PAGE = 100; - - private static int safePageSize(final int pageSize) { - return 0 < pageSize && pageSize <= MAX_PER_PAGE ? pageSize : MAX_PER_PAGE; - } - private final Provider currentUser; private final ChangeControl.Factory changeControlFactory; private final AccountInfoCacheFactory.Factory accountInfoCacheFactory; + private final CapabilityControl.Factory capabilityControlFactory; private final ChangeQueryBuilder.Factory queryBuilder; private final Provider queryRewriter; @@ -97,12 +96,14 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements final Provider currentUser, final ChangeControl.Factory changeControlFactory, final AccountInfoCacheFactory.Factory accountInfoCacheFactory, + final CapabilityControl.Factory capabilityControlFactory, final ChangeQueryBuilder.Factory queryBuilder, final Provider queryRewriter) { super(schema, currentUser); this.currentUser = currentUser; this.changeControlFactory = changeControlFactory; this.accountInfoCacheFactory = accountInfoCacheFactory; + this.capabilityControlFactory = capabilityControlFactory; this.queryBuilder = queryBuilder; this.queryRewriter = queryRewriter; } @@ -118,25 +119,33 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements @Override public void allQueryPrev(final String query, final String pos, final int pageSize, final AsyncCallback callback) { - run(callback, new QueryPrev(pageSize, pos) { - @Override - ResultSet query(ReviewDb db, int lim, String key) - throws OrmException, InvalidQueryException { - return searchQuery(db, query, lim, key, QUERY_PREV); - } - }); + try { + run(callback, new QueryPrev(pageSize, pos) { + @Override + ResultSet query(ReviewDb db, int lim, String key) + throws OrmException, InvalidQueryException { + return searchQuery(db, query, lim, key, QUERY_PREV); + } + }); + } catch (InvalidQueryException e) { + callback.onFailure(e); + } } @Override public void allQueryNext(final String query, final String pos, final int pageSize, final AsyncCallback callback) { - run(callback, new QueryNext(pageSize, pos) { - @Override - ResultSet query(ReviewDb db, int lim, String key) - throws OrmException, InvalidQueryException { - return searchQuery(db, query, lim, key, QUERY_NEXT); - } - }); + try { + run(callback, new QueryNext(pageSize, pos) { + @Override + ResultSet query(ReviewDb db, int lim, String key) + throws OrmException, InvalidQueryException { + return searchQuery(db, query, lim, key, QUERY_NEXT); + } + }); + } catch (InvalidQueryException e) { + callback.onFailure(e); + } } @SuppressWarnings("unchecked") @@ -290,6 +299,26 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements callback.onSuccess(currentUser.get().getStarredChanges()); } + private int safePageSize(final int pageSize) throws InvalidQueryException { + int maxLimit; + try { + PermissionRange range = capabilityControlFactory.controlFor() + .getRange(GlobalCapability.QUERY_LIMIT); + if (range != null) { + maxLimit = range.getMax(); + } else { + maxLimit = GlobalCapability.getRange(GlobalCapability.QUERY_LIMIT) + .getDefaultMax(); + } + } catch (NoSuchProjectException noConfig) { + maxLimit = 0; + } + if (maxLimit == 0) { + throw new InvalidQueryException("Search Disabled"); + } + return 0 < pageSize && pageSize <= maxLimit ? pageSize : maxLimit; + } + private List filter(final ResultSet rs, final Set starred, final AccountInfoCacheFactory accts) { final ArrayList r = new ArrayList(); @@ -309,7 +338,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements protected final int limit; protected final int slim; - QueryNext(final int pageSize, final String pos) { + QueryNext(final int pageSize, final String pos) throws InvalidQueryException { this.pos = pos; this.limit = safePageSize(pageSize); this.slim = limit + 1; @@ -353,7 +382,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements } private abstract class QueryPrev extends QueryNext { - QueryPrev(int pageSize, String pos) { + QueryPrev(int pageSize, String pos) throws InvalidQueryException { super(pageSize, pos); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java index ffe44a3cf5..09cfbb4fde 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java @@ -107,41 +107,48 @@ class ChangeProjectAccess extends Handler { Set toDelete = scanSectionNames(config); for (AccessSection section : mergeSections(sectionList)) { - final String name = section.getRefPattern(); - if (!projectControl.controlForRef(name).isOwner()) { - continue; - } + String name = section.getName(); - if (name.startsWith(AccessSection.REGEX_PREFIX)) { - if (!Repository.isValidRefName(RefControl.shortestExample(name))) { + if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) { + if (!projectControl.isOwner()) { + continue; + } + replace(config, toDelete, section); + + } else if (AccessSection.isAccessSection(name)) { + if (!projectControl.controlForRef(name).isOwner()) { + continue; + } + + if (name.startsWith(AccessSection.REGEX_PREFIX)) { + if (!Repository.isValidRefName(RefControl.shortestExample(name))) { + throw new InvalidNameException(); + } + + } else if (name.equals(AccessSection.ALL)) { + // This is a special case we have to allow, it fails below. + + } else if (name.endsWith("/*")) { + String prefix = name.substring(0, name.length() - 2); + if (!Repository.isValidRefName(prefix)) { + throw new InvalidNameException(); + } + + } else if (!Repository.isValidRefName(name)) { throw new InvalidNameException(); } - } else if (name.equals(AccessSection.ALL)) { - // This is a special case we have to allow, it fails below. - - } else if (name.endsWith("/*")) { - String prefix = name.substring(0, name.length() - 2); - if (!Repository.isValidRefName(prefix)) { - throw new InvalidNameException(); - } - - } else if (!Repository.isValidRefName(name)) { - throw new InvalidNameException(); + replace(config, toDelete, section); } - - for (Permission permission : section.getPermissions()) { - for (PermissionRule rule : permission.getRules()) { - lookupGroup(rule); - } - } - - config.replace(section); - toDelete.remove(section.getRefPattern()); } for (String name : toDelete) { - if (projectControl.controlForRef(name).isOwner()) { + if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) { + if (projectControl.isOwner()) { + config.remove(config.getAccessSection(name)); + } + + } else if (projectControl.controlForRef(name).isOwner()) { config.remove(config.getAccessSection(name)); } } @@ -167,6 +174,17 @@ class ChangeProjectAccess extends Handler { } } + private void replace(ProjectConfig config, Set toDelete, + AccessSection section) throws NoSuchGroupException { + for (Permission permission : section.getPermissions()) { + for (PermissionRule rule : permission.getRules()) { + lookupGroup(rule); + } + } + config.replace(section); + toDelete.remove(section.getName()); + } + private static List mergeSections(List src) { Map map = new LinkedHashMap(); for (AccessSection section : src) { @@ -174,11 +192,11 @@ class ChangeProjectAccess extends Handler { continue; } - AccessSection prior = map.get(section.getRefPattern()); + AccessSection prior = map.get(section.getName()); if (prior != null) { prior.mergeFrom(section); } else { - map.put(section.getRefPattern(), section); + map.put(section.getName(), section); } } return new ArrayList(map.values()); @@ -187,7 +205,7 @@ class ChangeProjectAccess extends Handler { private static Set scanSectionNames(ProjectConfig config) { Set names = new HashSet(); for (AccessSection section : config.getAccessSections()) { - names.add(section.getRefPattern()); + names.add(section.getName()); } return names; } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java index 132672bee5..44fda5601f 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java @@ -115,46 +115,55 @@ class ProjectAccessFactory extends Handler { new HashMap(); for (AccessSection section : config.getAccessSections()) { - RefControl rc = pc.controlForRef(section.getRefPattern()); - if (rc.isOwner()) { - local.add(section); - ownerOf.add(section.getRefPattern()); + String name = section.getName(); + if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) { + if (pc.isOwner()) { + local.add(section); + ownerOf.add(name); + } - } else if (rc.isVisible()) { - // Filter the section to only add rules describing groups that - // are visible to the current-user. This includes any group the - // user is a member of, as well as groups they own or that - // are visible to all users. + } else if (AccessSection.isAccessSection(name)) { + RefControl rc = pc.controlForRef(name); + if (rc.isOwner()) { + local.add(section); + ownerOf.add(name); - AccessSection dst = null; - for (Permission srcPerm : section.getPermissions()) { - Permission dstPerm = null; + } else if (rc.isVisible()) { + // Filter the section to only add rules describing groups that + // are visible to the current-user. This includes any group the + // user is a member of, as well as groups they own or that + // are visible to all users. - for (PermissionRule srcRule : srcPerm.getRules()) { - AccountGroup.UUID group = srcRule.getGroup().getUUID(); - if (group == null) { - continue; - } + AccessSection dst = null; + for (Permission srcPerm : section.getPermissions()) { + Permission dstPerm = null; - Boolean canSeeGroup = visibleGroups.get(group); - if (canSeeGroup == null) { - try { - canSeeGroup = groupControlFactory.controlFor(group).isVisible(); - } catch (NoSuchGroupException e) { - canSeeGroup = Boolean.FALSE; + for (PermissionRule srcRule : srcPerm.getRules()) { + AccountGroup.UUID group = srcRule.getGroup().getUUID(); + if (group == null) { + continue; } - visibleGroups.put(group, canSeeGroup); - } - if (canSeeGroup) { - if (dstPerm == null) { - if (dst == null) { - dst = new AccessSection(section.getRefPattern()); - local.add(dst); + Boolean canSeeGroup = visibleGroups.get(group); + if (canSeeGroup == null) { + try { + canSeeGroup = groupControlFactory.controlFor(group).isVisible(); + } catch (NoSuchGroupException e) { + canSeeGroup = Boolean.FALSE; } - dstPerm = dst.getPermission(srcPerm.getName(), true); + visibleGroups.put(group, canSeeGroup); + } + + if (canSeeGroup) { + if (dstPerm == null) { + if (dst == null) { + dst = new AccessSection(name); + local.add(dst); + } + dstPerm = dst.getPermission(srcPerm.getName(), true); + } + dstPerm.add(srcRule); } - dstPerm.add(srcRule); } } } @@ -170,17 +179,22 @@ class ProjectAccessFactory extends Handler { final ProjectAccess detail = new ProjectAccess(); detail.setRevision(config.getRevision().name()); - detail.setLocal(local); - detail.setOwnerOf(ownerOf); if (projectName.equals(wildProject)) { + if (pc.isOwner()) { + ownerOf.add(AccessSection.GLOBAL_CAPABILITIES); + } detail.setInheritsFrom(null); + } else if (config.getProject().getParent() != null) { detail.setInheritsFrom(config.getProject().getParent()); + } else { detail.setInheritsFrom(wildProject); } + detail.setLocal(local); + detail.setOwnerOf(ownerOf); return detail; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java new file mode 100644 index 0000000000..5fc996c929 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java @@ -0,0 +1,135 @@ +// 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.account; + +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.common.data.GlobalCapability; +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.Project; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.WildProjectName; +import com.google.gerrit.server.project.NoSuchProjectException; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Access control management for server-wide capabilities. */ +public class CapabilityControl { + public static class Factory { + private final Project.NameKey wildProject; + private final ProjectCache projectCache; + private final Provider user; + + @Inject + Factory(@WildProjectName Project.NameKey wp, ProjectCache pc, + Provider cu) { + wildProject = wp; + projectCache = pc; + user = cu; + } + + public CapabilityControl controlFor() throws NoSuchProjectException { + final ProjectState p = projectCache.get(wildProject); + if (p == null) { + throw new NoSuchProjectException(wildProject); + } + return new CapabilityControl(p, user.get()); + } + } + + private final ProjectState state; + private final CurrentUser user; + private Map> permissions; + + private CapabilityControl(ProjectState p, CurrentUser currentUser) { + state = p; + user = currentUser; + } + + /** Identity of the user the control will compute for. */ + public CurrentUser getCurrentUser() { + return user; + } + + /** True if the user has this permission. Works only for non labels. */ + public boolean canPerform(String permissionName) { + return !access(permissionName).isEmpty(); + } + + /** The range of permitted values associated with a label permission. */ + public PermissionRange getRange(String permission) { + if (GlobalCapability.hasRange(permission)) { + return toRange(permission, access(permission)); + } + return null; + } + + private static PermissionRange toRange(String permissionName, + List ruleList) { + int min = 0; + int max = 0; + for (PermissionRule rule : ruleList) { + min = Math.min(min, rule.getMin()); + max = Math.max(max, rule.getMax()); + } + return new PermissionRange(permissionName, min, max); + } + + /** Rules for the given permission, or the empty list. */ + private List access(String permissionName) { + List r = permissions().get(permissionName); + return r != null ? r : Collections. emptyList(); + } + + /** All rules that pertain to this user. */ + private Map> permissions() { + if (permissions == null) { + permissions = new HashMap>(); + AccessSection section = + state.getConfig().getAccessSection(AccessSection.GLOBAL_CAPABILITIES); + for (Permission permission : section.getPermissions()) { + for (PermissionRule rule : permission.getRules()) { + if (matchGroup(rule.getGroup().getUUID())) { + if (!rule.getDeny()) { + List r = permissions.get(permission.getName()); + if (r == null) { + r = new ArrayList(2); + permissions.put(permission.getName(), r); + } + r.add(rule); + } + } + } + } + } + return permissions; + } + + private boolean matchGroup(AccountGroup.UUID uuid) { + Set userGroups = getCurrentUser().getEffectiveGroups(); + return userGroups.contains(uuid); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java index 1aa311eb9d..7f455c9693 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java @@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.RequestCleanup; import com.google.gerrit.server.account.AccountResolver; +import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.account.PerformCreateGroup; import com.google.gerrit.server.git.CreateCodeReviewNotes; @@ -56,6 +57,7 @@ public class GerritRequestModule extends FactoryModule { bind(AccountResolver.class); bind(ChangeQueryRewriter.class); + bind(CapabilityControl.Factory.class).in(SINGLETON); bind(ChangeControl.Factory.class).in(SINGLETON); bind(GroupControl.Factory.class).in(SINGLETON); bind(ProjectControl.Factory.class).in(SINGLETON); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index 6f022d3214..85b74bbdcc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java @@ -18,6 +18,7 @@ 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.GlobalCapability; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRule; @@ -54,6 +55,8 @@ public class ProjectConfig extends VersionedMetaData { private static final String KEY_INHERIT_FROM = "inheritFrom"; private static final String KEY_GROUP_PERMISSIONS = "exclusiveGroupPermissions"; + private static final String CAPABILITY = "capability"; + private static final String RECEIVE = "receive"; private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy"; private static final String KEY_REQUIRE_CHANGE_ID = "requireChangeId"; @@ -115,7 +118,7 @@ public class ProjectConfig extends VersionedMetaData { public void remove(AccessSection section) { if (section != null) { - accessSections.remove(section.getRefPattern()); + accessSections.remove(section.getName()); } } @@ -126,7 +129,7 @@ public class ProjectConfig extends VersionedMetaData { } } - accessSections.put(section.getRefPattern(), section); + accessSections.put(section.getName(), section); } public GroupReference resolve(AccountGroup group) { @@ -234,38 +237,59 @@ public class ProjectConfig extends VersionedMetaData { 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) { - error(new ValidationError(PROJECT_CONFIG, "Invalid rule in " + ACCESS - + "." + refName + "." + varName + ": " - + notRule.getMessage())); - continue; - } - - 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); - error(new ValidationError(PROJECT_CONFIG, "group \"" - + rule.getGroup().getName() + "\" not in " + GROUP_LIST)); - } - - rule.setGroup(ref); - perm.add(rule); - } + loadPermissionRules(rc, ACCESS, refName, varName, groupsByName, + perm, perm.isLabel()); } } } } + + AccessSection capability = null; + for (String varName : rc.getNames(CAPABILITY)) { + if (GlobalCapability.isCapability(varName)) { + if (capability == null) { + capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES); + accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability); + } + Permission perm = capability.getPermission(varName, true); + loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm, + GlobalCapability.hasRange(varName)); + } + } + } + + private void loadPermissionRules(Config rc, String section, + String subsection, String varName, + Map groupsByName, Permission perm, + boolean useRange) { + for (String ruleString : rc.getStringList(section, subsection, varName)) { + PermissionRule rule; + try { + rule = PermissionRule.fromString(ruleString, useRange); + } catch (IllegalArgumentException notRule) { + error(new ValidationError(PROJECT_CONFIG, "Invalid rule in " + + section + + (subsection != null ? "." + subsection : "") + + "." + varName + ": " + + notRule.getMessage())); + continue; + } + + 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); + error(new ValidationError(PROJECT_CONFIG, + "group \"" + ref.getName() + "\" not in " + GROUP_LIST)); + } + + rule.setGroup(ref); + perm.add(rule); + } } private Map readGroupList() throws IOException { @@ -321,8 +345,38 @@ public class ProjectConfig extends VersionedMetaData { set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.isUseContentMerge()); Set keepGroups = new HashSet(); + AccessSection capability = accessSections.get(AccessSection.GLOBAL_CAPABILITIES); + if (capability != null) { + Set have = new HashSet(); + for (Permission permission : sort(capability.getPermissions())) { + have.add(permission.getName().toLowerCase()); + + boolean needRange = GlobalCapability.hasRange(permission.getName()); + List rules = new ArrayList(); + 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(CAPABILITY, null, permission.getName(), rules); + } + for (String varName : rc.getNames(CAPABILITY)) { + if (GlobalCapability.isCapability(varName) + && !have.contains(varName.toLowerCase())) { + rc.unset(CAPABILITY, null, varName); + } + } + } else { + rc.unsetSection(CAPABILITY, null); + } + for (AccessSection as : sort(accessSections.values())) { - String refName = as.getRefPattern(); + String refName = as.getName(); + if (AccessSection.GLOBAL_CAPABILITIES.equals(refName)) { + continue; + } StringBuilder doNotInherit = new StringBuilder(); for (Permission perm : sort(as.getPermissions())) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index 74ee893e0c..baa57b538f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -234,7 +234,7 @@ public class ProjectControl { // that would render this to be false. // if (groups.contains(rule.getGroup().getUUID()) - && controlForRef(section.getRefPattern()).canPerform(permissionName)) { + && controlForRef(section.getName()).canPerform(permissionName)) { return true; } } @@ -267,7 +267,7 @@ public class ProjectControl { for (AccessSection section : access()) { Permission permission = section.getPermission(permissionName); if (permission != null) { - all.add(section.getRefPattern()); + all.add(section.getName()); } } return all; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java index ff1d09fd53..35157805ac 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java @@ -389,7 +389,7 @@ public class RefControl { } private boolean appliesToRef(AccessSection section) { - String refPattern = section.getRefPattern(); + String refPattern = section.getName(); if (isTemplate(refPattern)) { ParamertizedString template = new ParamertizedString(refPattern); @@ -476,7 +476,7 @@ public class RefControl { final AccountGroup.UUID group; SeenRule(AccessSection section, Permission permission, PermissionRule rule) { - refPattern = section.getRefPattern(); + refPattern = section.getName(); permissionName = permission.getName(); group = rule.getGroup().getUUID(); } @@ -540,7 +540,7 @@ public class RefControl { } public int compare(AccessSection a, AccessSection b) { - return compare(a.getRefPattern(), b.getRefPattern()); + return compare(a.getName(), b.getName()); } private int compare(final String pattern1, final String pattern2) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java index a975f1b85b..3acc63dce8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java @@ -14,13 +14,17 @@ package com.google.gerrit.server.query.change; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.events.ChangeAttribute; import com.google.gerrit.server.events.EventFactory; import com.google.gerrit.server.events.QueryStats; +import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.QueryParseException; import com.google.gson.Gson; @@ -65,7 +69,7 @@ public class QueryProcessor { private final ChangeQueryRewriter queryRewriter; private final Provider db; - private int defaultLimit = 500; + private int defaultLimit; private OutputFormat outputFormat = OutputFormat.TEXT; private boolean includePatchSets; private boolean includeCurrentPatchSet; @@ -77,11 +81,19 @@ public class QueryProcessor { @Inject QueryProcessor(EventFactory eventFactory, ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser, - ChangeQueryRewriter queryRewriter, Provider db) { + ChangeQueryRewriter queryRewriter, Provider db, + CapabilityControl.Factory ctl) throws NoSuchProjectException { this.eventFactory = eventFactory; this.queryBuilder = queryBuilder.create(currentUser); this.queryRewriter = queryRewriter; this.db = db; + + PermissionRange range = ctl.controlFor().getRange(GlobalCapability.QUERY_LIMIT); + if (range != null) { + defaultLimit = range.getMax(); + } else { + defaultLimit = GlobalCapability.getRange(GlobalCapability.QUERY_LIMIT).getDefaultMax(); + } } public void setIncludePatchSets(boolean on) { @@ -106,6 +118,13 @@ public class QueryProcessor { new BufferedWriter( // new OutputStreamWriter(outputStream, "UTF-8"))); try { + if (defaultLimit == 0) { + ErrorMessage m = new ErrorMessage(); + m.message = "query disabled"; + show(m); + return; + } + try { final QueryStats stats = new QueryStats(); stats.runTimeMilliseconds = System.currentTimeMillis(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java index 9b6812fe5d..d5ba4c35fb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java @@ -16,7 +16,9 @@ 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.GlobalCapability; 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.AccountGroupName; @@ -235,6 +237,11 @@ public class SchemaCreator { p.setDescription("Rights inherited by all other projects"); p.setUseContributorAgreements(false); + AccessSection capabilities = config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true); + PermissionRange.WithDefaults queryLimit = GlobalCapability.getRange(GlobalCapability.QUERY_LIMIT); + capabilities.getPermission(GlobalCapability.QUERY_LIMIT, true) + .add(rule(config, anonymous, queryLimit.getDefaultMax(), queryLimit.getDefaultMax())); + AccessSection all = config.getAccessSection(AccessSection.ALL, true); AccessSection heads = config.getAccessSection(AccessSection.HEADS, true); AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true); @@ -270,6 +277,13 @@ public class SchemaCreator { return new PermissionRule(config.resolve(group)); } + private PermissionRule rule(ProjectConfig config, AccountGroup group, + int min, int max) { + PermissionRule rule = new PermissionRule(config.resolve(group)); + rule.setRange(min, max); + return rule; + } + private void initVerifiedCategory(final ReviewDb c) throws OrmException { final ApprovalCategory cat; final ArrayList vals;