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 {