From 87340e6bb776dc566b867b58bfbf4168acc8a1a4 Mon Sep 17 00:00:00 2001 From: Edwin Kempin Date: Mon, 24 Jun 2013 16:46:34 +0200 Subject: [PATCH] Support retrieving project access rights via REST By GET on /access/?project= it is now possible to retrieve the access rights of a project via REST. Having /access/ instead of /projects//access allows to retrieve the access rights for multiple projects at once by specifying the 'project' option multiple times. In future this REST endpoint may be extended to only return the access rights for a certain user or group by supporting a 'user' and 'group' option. Change-Id: Ibc57b2c006472a9f70699ae69e72f60317819bef Signed-off-by: Edwin Kempin --- Documentation/rest-api-access.txt | 376 ++++++++++++++++++ Documentation/rest-api.txt | 2 + .../com/google/gerrit/httpd/UrlModule.java | 2 + .../rpc/access/AccessRestApiServlet.java | 32 ++ .../server/access/AccessCollection.java | 53 +++ .../gerrit/server/access/AccessResource.java | 24 ++ .../gerrit/server/access/ListAccess.java | 306 ++++++++++++++ .../google/gerrit/server/access/Module.java | 29 ++ .../server/config/GerritGlobalModule.java | 1 + .../gerrit/server/project/ProjectControl.java | 12 + 10 files changed, 837 insertions(+) create mode 100644 Documentation/rest-api-access.txt create mode 100644 gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt new file mode 100644 index 0000000000..409e0bcb0a --- /dev/null +++ b/Documentation/rest-api-access.txt @@ -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] diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt index c0dde1c71e..b0721731ef 100644 --- a/Documentation/rest-api.txt +++ b/Documentation/rest-api.txt @@ -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/]:: diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java index 47219cad4b..05b059b8cf 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java @@ -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); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java new file mode 100644 index 0000000000..fda64166ce --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java @@ -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 access) { + super(globals, access); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java new file mode 100644 index 0000000000..58f93d811f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java @@ -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 { + private final Provider list; + private final DynamicMap> views; + + @Inject + AccessCollection(Provider list, + DynamicMap> views) { + this.list = list; + this.views = views; + } + + @Override + public RestView list() { + return list.get(); + } + + @Override + public AccessResource parse(TopLevelResource parent, IdString id) + throws ResourceNotFoundException { + throw new ResourceNotFoundException(id); + } + + @Override + public DynamicMap> views() { + return views; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java new file mode 100644 index 0000000000..22888b85eb --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java @@ -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> ACCESS_KIND = + new TypeLiteral>() {}; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java new file mode 100644 index 0000000000..36350951d5 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java @@ -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 { + + @Option(name = "--project", aliases = {"-p"}, metaVar = "PROJECT", + usage = "projects for which the access rights should be returned") + private List projects = Lists.newArrayList(); + + private final Provider 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 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 apply(TopLevelResource resource) + throws ResourceNotFoundException, ResourceConflictException, IOException { + Map 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 local; + public Set 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 visibleGroups = + new HashMap(); + + 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 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 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; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java new file mode 100644 index 0000000000..cd0d334b7a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java @@ -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); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 160b33fd56..7b242d95ec 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -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()); 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 3f107ec015..ed4346a416 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 @@ -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 {