Merge "Support retrieving project access rights via REST"

This commit is contained in:
David Pursehouse
2013-06-26 01:49:50 +00:00
committed by Gerrit Code Review
10 changed files with 837 additions and 0 deletions

View File

@@ -0,0 +1,376 @@
Gerrit Code Review - /access/ REST API
======================================
This page describes the access rights related REST endpoints.
Please also take note of the general information on the
link:rest-api.html[REST API].
[[access-endpoints]]
Access Rights Endpoints
-----------------------
[[list-access]]
List Access Rights
~~~~~~~~~~~~~~~~~~
[verse]
'GET /access/?project=link:rest-api-projects.html#project-name[\{project-name\}]'
Lists the access rights for projects. The projects for which the access
rights should be returned must be specified as `project` options. The
`project` can be specified multiple times.
As result a map is returned that maps the project name to
link:#project-access-info[ProjectAccessInfo] entities.
The entries in the map are sorted by project name.
.Request
----
GET /access/?project=MyProject&project=All-Projects HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
)]}'
{
"All-Projects": {
"revision": "edd453d18e08640e67a8c9a150cec998ed0ac9aa",
"local": {
"GLOBAL_CAPABILITIES": {
"permissions": {
"priority": {
"rules": {
"15bfcd8a6de1a69c50b30cedcdcc951c15703152": {
"action": "BATCH"
}
}
},
"streamEvents": {
"rules": {
"15bfcd8a6de1a69c50b30cedcdcc951c15703152": {
"action": "ALLOW"
}
}
},
"administrateServer": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
}
}
}
}
},
"refs/meta/config": {
"permissions": {
"submit": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"label-Code-Review": {
"label": "Code-Review",
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW",
"min": -2,
"max": 2
},
"global:Project-Owners": {
"action": "ALLOW",
"min": -2,
"max": 2
}
}
},
"read": {
"exclusive": true,
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"push": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
}
}
},
"refs/for/refs/*": {
"permissions": {
"pushMerge": {
"rules": {
"global:Registered-Users": {
"action": "ALLOW"
}
}
},
"push": {
"rules": {
"global:Registered-Users": {
"action": "ALLOW"
}
}
}
}
},
"refs/tags/*": {
"permissions": {
"pushSignedTag": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"pushTag": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
}
}
},
"refs/heads/*": {
"permissions": {
"forgeCommitter": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"forgeAuthor": {
"rules": {
"global:Registered-Users": {
"action": "ALLOW"
}
}
},
"submit": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"editTopicName": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW",
"force": true
},
"global:Project-Owners": {
"action": "ALLOW",
"force": true
}
}
},
"label-Code-Review": {
"label": "Code-Review",
"rules": {
"global:Registered-Users": {
"action": "ALLOW",
"min": -1,
"max": 1
},
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW",
"min": -2,
"max": 2
},
"global:Project-Owners": {
"action": "ALLOW",
"min": -2,
"max": 2
}
}
},
"create": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
},
"push": {
"rules": {
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
},
"global:Project-Owners": {
"action": "ALLOW"
}
}
}
}
},
"refs/*": {
"permissions": {
"read": {
"rules": {
"global:Anonymous-Users": {
"action": "ALLOW"
},
"53a4f647a89ea57992571187d8025f830625192a": {
"action": "ALLOW"
}
}
}
}
}
},
"owner_of": [
"GLOBAL_CAPABILITIES",
"refs/meta/config",
"refs/for/refs/*",
"refs/tags/*",
"refs/heads/*",
"refs/*"
],
"can_upload": true,
"can_add": true,
"config_visible": true
},
"MyProject": {
"revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
"inherits_from": {
"kind": "gerritcodereview#project",
"id": "All-Projects",
"name": "All-Projects",
"description": "Access inherited by all other projects."
},
"local": {},
"owner_of": [
"refs/*"
],
"can_upload": true,
"can_add": true,
"config_visible": true
}
}
----
[[access-section-info]]
AccessSectionInfo
~~~~~~~~~~~~~~~~~
The `AccessSectionInfo` describes the access rights that are assigned
on a ref.
[options="header",width="50%",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`permissions` ||
The permissions assigned on the ref of this access section as a map
that maps the permission names to link:#permission-info[PermissionInfo]
entities.
|==================================
[[permission-info]]
PermissionInfo
~~~~~~~~~~~~~~
The `PermissionInfo` entity contains information about an assigned
permission.
[options="header",width="50%",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`label` |optional|
The name of the label. Not set if it's not a label permission.
|`exclusive` |not set if `false`|
Whether this permission is assigned exclusively.
|`rules` ||
The rules assigned for this permission as a map that maps the UUIDs of
the groups for which the permission are assigned to
link:#permission-info[PermissionRuleInfo] entities.
|==================================
[[permission-rule-info]]
PermissionRuleInfo
~~~~~~~~~~~~~~~~~~
The `PermissionRuleInfo` entity contains information about a permission
rule that is assigned to group.
[options="header",width="50%",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`action` ||
The action of this rule. For normal permissions this can be `ALLOW`,
`DENY` or `BLOCK`. Special values for global capabilities are
`INTERACTIVE` and `BATCH`.
|`force` |not set if `false`|
Whether the force flag is set.
|`min` |
not set if range if empty (from `0` to `0`) or not set|
The min value of the permission range.
|`max` |
not set if range if empty (from `0` to `0`) or not set|
The max value of the permission range.
|==================================
[[project-access-info]]
ProjectAccessInfo
~~~~~~~~~~~~~~~~~
The `ProjectAccessInfo` entity contains information about the access
rights for a project.
[options="header",width="50%",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`revision` ||
The revision of the `refs/meta/config` branch from which the access
rights were loaded.
|`inherits_from` |not set for the `All-Project` project|
The parent project from which permissions are inherited as a
link:rest-api-projects.html#project-info[ProjectInfo] entity.
|`local` ||
The local access rights of the project as a map that maps the refs to
link:#access-section-info[AccessSectionInfo] entities.
|`owner_of` ||The list of refs owned by the calling user.
|`can_upload` |not set if `false`|
Whether the calling user can upload to any ref.
|`can_add` |not set if `false`|
Whether the calling user can add any ref.
|`config_visible` |not set if `false`|
Whether the calling user can see the `refs/meta/config` branch of the
project.
|==================================
GERRIT
------
Part of link:index.html[Gerrit Code Review]

View File

@@ -9,6 +9,8 @@ See also: link:dev-rest-api.html[REST API Developers' Notes].
Endpoints
---------
link:rest-api-access.html[/access/]::
Access Right related REST endpoints
link:rest-api-accounts.html[/accounts/]::
Account related REST endpoints
link:rest-api-changes.html[/changes/]::

View File

@@ -24,6 +24,7 @@ import com.google.gerrit.httpd.raw.LegacyGerritServlet;
import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
import com.google.gerrit.httpd.rpc.access.AccessRestApiServlet;
import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
@@ -101,6 +102,7 @@ class UrlModule extends ServletModule {
filter("/a/*").through(RequireIdentifiedUserFilter.class);
serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
serveRegex("^/(?:a/)?access/(.*)$").with(AccessRestApiServlet.class);
serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
serveRegex("^/(?:a/)?config/(.*)$").with(ConfigRestApiServlet.class);

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2013 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.httpd.rpc.access;
import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.gerrit.server.access.AccessCollection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
public class AccessRestApiServlet extends RestApiServlet {
private static final long serialVersionUID = 1L;
@Inject
AccessRestApiServlet(RestApiServlet.Globals globals,
Provider<AccessCollection> access) {
super(globals, access);
}
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2013 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.access;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class AccessCollection implements
RestCollection<TopLevelResource, AccessResource> {
private final Provider<ListAccess> list;
private final DynamicMap<RestView<AccessResource>> views;
@Inject
AccessCollection(Provider<ListAccess> list,
DynamicMap<RestView<AccessResource>> views) {
this.list = list;
this.views = views;
}
@Override
public RestView<TopLevelResource> list() {
return list.get();
}
@Override
public AccessResource parse(TopLevelResource parent, IdString id)
throws ResourceNotFoundException {
throw new ResourceNotFoundException(id);
}
@Override
public DynamicMap<RestView<AccessResource>> views() {
return views;
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2013 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.access;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.TypeLiteral;
public class AccessResource implements RestResource {
public static final TypeLiteral<RestView<AccessResource>> ACCESS_KIND =
new TypeLiteral<RestView<AccessResource>>() {};
}

View File

@@ -0,0 +1,306 @@
// Copyright (C) 2013 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.access;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
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.common.data.RefConfigSection;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.GroupJson;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ListAccess implements RestReadView<TopLevelResource> {
@Option(name = "--project", aliases = {"-p"}, metaVar = "PROJECT",
usage = "projects for which the access rights should be returned")
private List<String> projects = Lists.newArrayList();
private final Provider<CurrentUser> self;
private final ProjectControl.GenericFactory projectControlFactory;
private final ProjectCache projectCache;
private final ProjectJson projectJson;
private final MetaDataUpdate.Server metaDataUpdateFactory;
private final GroupControl.Factory groupControlFactory;
private final GroupBackend groupBackend;
private final AllProjectsName allProjectsName;
@Inject
public ListAccess(Provider<CurrentUser> self,
ProjectControl.GenericFactory projectControlFactory,
ProjectCache projectCache, ProjectJson projectJson,
MetaDataUpdate.Server metaDataUpdateFactory,
GroupControl.Factory groupControlFactory, GroupBackend groupBackend,
GroupJson groupJson, AllProjectsName allProjectsName) {
this.self = self;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.projectJson = projectJson;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.groupControlFactory = groupControlFactory;
this.groupBackend = groupBackend;
this.allProjectsName = allProjectsName;
}
@Override
public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
throws ResourceNotFoundException, ResourceConflictException, IOException {
Map<String, ProjectAccessInfo> access = Maps.newTreeMap();
for (String p: projects) {
Project.NameKey projectName = new Project.NameKey(p);
ProjectControl pc = open(projectName);
ProjectConfig config;
try {
// Load the current configuration from the repository, ensuring it's the most
// recent version available. If it differs from what was in the project
// state, force a cache flush now.
//
MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
try {
config = ProjectConfig.read(md);
if (config.updateGroupNames(groupBackend)) {
md.setMessage("Update group names\n");
config.commit(md);
projectCache.evict(config.getProject());
pc = open(projectName);
} else if (config.getRevision() != null
&& !config.getRevision().equals(
pc.getProjectState().getConfig().getRevision())) {
projectCache.evict(config.getProject());
pc = open(projectName);
}
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
} finally {
md.close();
}
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(p);
}
access.put(p, new ProjectAccessInfo(pc, config));
}
return access;
}
private ProjectControl open(Project.NameKey projectName)
throws ResourceNotFoundException, IOException {
try {
return projectControlFactory.validateFor(projectName,
ProjectControl.OWNER | ProjectControl.VISIBLE, self.get());
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(projectName.get());
}
}
public class ProjectAccessInfo {
public String revision;
public ProjectInfo inheritsFrom;
public Map<String, AccessSectionInfo> local;
public Set<String> ownerOf;
public Boolean canUpload;
public Boolean canAdd;
public Boolean configVisible;
public ProjectAccessInfo(ProjectControl pc, ProjectConfig config) {
final RefControl metaConfigControl =
pc.controlForRef(GitRepositoryManager.REF_CONFIG);
local = Maps.newHashMap();
ownerOf = Sets.newHashSet();
Map<AccountGroup.UUID, Boolean> visibleGroups =
new HashMap<AccountGroup.UUID, Boolean>();
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (pc.isOwner()) {
local.put(name, new AccessSectionInfo(section));
ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
local.put(section.getName(), new AccessSectionInfo(section));
}
} else if (RefConfigSection.isValid(name)) {
RefControl rc = pc.controlForRef(name);
if (rc.isOwner()) {
local.put(name, new AccessSectionInfo(section));
ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
local.put(name, new AccessSectionInfo(section));
} 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.
AccessSection dst = null;
for (Permission srcPerm : section.getPermissions()) {
Permission dstPerm = null;
for (PermissionRule srcRule : srcPerm.getRules()) {
AccountGroup.UUID group = srcRule.getGroup().getUUID();
if (group == null) {
continue;
}
Boolean canSeeGroup = visibleGroups.get(group);
if (canSeeGroup == null) {
try {
canSeeGroup = groupControlFactory.controlFor(group).isVisible();
} catch (NoSuchGroupException e) {
canSeeGroup = Boolean.FALSE;
}
visibleGroups.put(group, canSeeGroup);
}
if (canSeeGroup) {
if (dstPerm == null) {
if (dst == null) {
dst = new AccessSection(name);
local.put(name, new AccessSectionInfo(dst));
}
dstPerm = dst.getPermission(srcPerm.getName(), true);
}
dstPerm.add(srcRule);
}
}
}
}
}
}
if (ownerOf.isEmpty() && pc.isOwnerAnyRef()) {
// Special case: If the section list is empty, this project has no current
// access control information. Rely on what ProjectControl determines
// is ownership, which probably means falling back to site administrators.
ownerOf.add(AccessSection.ALL);
}
if (config.getRevision() != null) {
revision = config.getRevision().name();
}
ProjectState parent =
Iterables.getFirst(pc.getProjectState().parents(), null);
if (parent != null) {
inheritsFrom = projectJson.format(parent.getProject());
}
if (pc.getProject().getNameKey().equals(allProjectsName)) {
if (pc.isOwner()) {
ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
}
canUpload = toBoolean(pc.isOwner()
|| (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
canAdd = toBoolean(pc.canAddRefs());
configVisible = pc.isOwner() || metaConfigControl.isVisible();
}
}
public class AccessSectionInfo {
public Map<String, PermissionInfo> permissions;
public AccessSectionInfo(AccessSection section) {
permissions = Maps.newHashMap();
for (Permission p : section.getPermissions()) {
permissions.put(p.getName(), new PermissionInfo(p));
}
}
}
public class PermissionInfo {
public String label;
public Boolean exclusive;
public Map<String, PermissionRuleInfo> rules;
public PermissionInfo(Permission permission) {
label = permission.getLabel();
exclusive = toBoolean(permission.getExclusiveGroup());
rules = Maps.newHashMap();
for (PermissionRule r : permission.getRules()) {
rules.put(r.getGroup().getUUID().get(), new PermissionRuleInfo(r));
}
}
}
public class PermissionRuleInfo {
public PermissionRule.Action action;
public Boolean force;
public Integer min;
public Integer max;
public PermissionRuleInfo(PermissionRule rule) {
action = rule.getAction();
force = toBoolean(rule.getForce());
if (hasRange(rule)) {
min = rule.getMin();
max = rule.getMax();
}
}
private boolean hasRange(PermissionRule rule) {
return (!(rule.getMin() == null || rule.getMin() == 0))
|| (!(rule.getMax() == null || rule.getMax() == 0));
}
}
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2013 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.access;
import static com.google.gerrit.server.access.AccessResource.ACCESS_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
public class Module extends RestApiModule {
@Override
protected void configure() {
bind(AccessCollection.class);
DynamicMap.mapOf(binder(), ACCESS_KIND);
}
}

View File

@@ -238,6 +238,7 @@ public class GerritGlobalModule extends FactoryModule {
bind(AccountControl.Factory.class);
install(new AuditModule());
install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.change.Module());
install(new com.google.gerrit.server.config.Module());

View File

@@ -70,6 +70,18 @@ public class ProjectControl {
}
return p.controlFor(user);
}
public ProjectControl validateFor(Project.NameKey nameKey, int need,
CurrentUser user) throws NoSuchProjectException, IOException {
final ProjectControl c = controlFor(nameKey, user);
if ((need & VISIBLE) == VISIBLE && c.isVisible()) {
return c;
}
if ((need & OWNER) == OWNER && c.isOwner()) {
return c;
}
throw new NoSuchProjectException(nameKey);
}
}
public static class Factory {