Added /projects/name/access as REST endpoint. Implemented GET.

We currently use RPC to view and edit access for projects. In an effort
to migrate to polygerrit this has to be ported to REST.

While the current /access endpoint is purely intended for listing access
rights for multiple (or a single) projects, what we want to
accomplish here is to have an API in place to change the access rights
for one project. As a first step I have implemented the
/projects/name/access endpoint as a way of GETing access rights for
a project. I'm currently writing logic to POST/change access rights
under the same endpoint in a different change set.
Having the GET implemented here is just a matter of consistency.

Change-Id: I5cc621ba28dc14bea15ae8e7a78a5ad3d0846c43
This commit is contained in:
Patrick Hiesel
2016-04-21 11:44:29 +02:00
parent 8a867d6414
commit 5a3475fe4b
15 changed files with 589 additions and 249 deletions

View File

@@ -2472,6 +2472,61 @@ The path to the `GerritSiteHeader.html` file.
The path to the `GerritSiteFooter.html` file.
|=============================
[[get-access]]
=== List Access Rights for Project
--
'GET //projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
--
Lists the access rights for a single project.
As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
.Request
----
GET /projects/MyProject/access HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
)]}'
{
"revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
"inherits_from": {
"id": "All-Projects",
"name": "All-Projects",
"description": "Access inherited by all other projects."
},
"local": {
"refs/*": {
"permissions": {
"read": {
"rules": {
"c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
"action": "ALLOW",
"force": false
},
"global:Anonymous-Users": {
"action": "ALLOW",
"force": false
}
}
}
}
}
},
"is_owner": true,
"owner_of": [
"refs/*"
],
"can_upload": true,
"can_add": true,
"config_visible": true
}
----
GERRIT
------

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2016 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.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import org.junit.Test;
public class AccessIT extends AbstractDaemonTest {
@Test
public void testGetDefaultInheritance() throws Exception {
String newProjectName = createProject("newProjectAccess").get();
String inheritedName = gApi.projects()
.name(newProjectName).access().inheritsFrom.name;
assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
}
}

View File

@@ -264,6 +264,11 @@ public class PermissionRule implements Comparable<PermissionRule> {
return rule;
}
public boolean hasRange() {
return (!(getMin() == null || getMin() == 0))
|| (!(getMax() == null || getMax() == 0));
}
public static int parseInt(String value) {
if (value.startsWith("+")) {
value = value.substring(1);

View File

@@ -0,0 +1,20 @@
// Copyright (C) 2016 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.extensions.api.access;
import java.util.Map;
public class AccessSectionInfo {
public Map<String, PermissionInfo> permissions;
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2016 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.extensions.api.access;
import java.util.Map;
public class PermissionInfo {
public String label;
public Boolean exclusive;
public Map<String, PermissionRuleInfo> rules;
public PermissionInfo(String label, Boolean exclusive) {
this.label = label;
this.exclusive = exclusive;
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2016 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.extensions.api.access;
public class PermissionRuleInfo {
public enum Action {
ALLOW,
DENY,
BLOCK,
INTERACTIVE,
BATCH
}
public Action action;
public Boolean force;
public Integer min;
public Integer max;
public PermissionRuleInfo(Action action, Boolean force) {
this.action = action;
this.force = force;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2016 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.extensions.api.access;
import com.google.gerrit.extensions.common.ProjectInfo;
import java.util.Map;
import java.util.Set;
public class ProjectAccessInfo {
public String revision;
public ProjectInfo inheritsFrom;
public Map<String, AccessSectionInfo> local;
public Boolean isOwner;
public Set<String> ownerOf;
public Boolean canUpload;
public Boolean canAdd;
public Boolean configVisible;
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2016 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.extensions.api.projects;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
public interface AccessApi {
ProjectAccessInfo get() throws RestApiException;
/**
* A default implementation which allows source compatibility
* when adding new methods to the interface.
**/
class NotImplemented implements AccessApi {
@Override
public ProjectAccessInfo get() throws RestApiException {
throw new NotImplementedException();
}
}
}

View File

@@ -14,10 +14,12 @@
package com.google.gerrit.extensions.api.projects;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
import java.io.IOException;
import java.util.List;
public interface ProjectApi {
@@ -28,6 +30,8 @@ public interface ProjectApi {
String description() throws RestApiException;
void description(PutDescriptionInput in) throws RestApiException;
ProjectAccessInfo access() throws RestApiException;
ListRefsRequest<BranchInfo> branches();
ListRefsRequest<TagInfo> tags();
@@ -129,6 +133,11 @@ public interface ProjectApi {
throw new NotImplementedException();
}
@Override
public ProjectAccessInfo access() throws RestApiException {
throw new NotImplementedException();
}
@Override
public void description(PutDescriptionInput in)
throws RestApiException {

View File

@@ -14,47 +14,22 @@
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.common.ProjectInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
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.reviewdb.client.RefNames;
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.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
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.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.GetAccess;
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> {
@@ -62,237 +37,22 @@ public class ListAccess implements RestReadView<TopLevelResource> {
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;
private final GetAccess getAccess;
@Inject
public ListAccess(Provider<CurrentUser> self,
ProjectControl.GenericFactory projectControlFactory,
ProjectCache projectCache, ProjectJson projectJson,
MetaDataUpdate.Server metaDataUpdateFactory,
GroupControl.Factory groupControlFactory, GroupBackend groupBackend,
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;
public ListAccess(GetAccess getAccess) {
this.getAccess = getAccess;
}
@Override
public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
throws ResourceNotFoundException, ResourceConflictException, IOException {
Map<String, ProjectAccessInfo> access = Maps.newTreeMap();
for (String p: projects) {
// 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.
//
for (String p : projects) {
Project.NameKey projectName = new Project.NameKey(p);
try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
ProjectControl pc = open(projectName);
ProjectConfig 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);
}
access.put(p, new ProjectAccessInfo(pc, config));
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(p);
}
access.put(p, getAccess.apply(projectName));
}
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 Boolean isOwner;
public Set<String> ownerOf;
public Boolean canUpload;
public Boolean canAdd;
public Boolean configVisible;
public ProjectAccessInfo(ProjectControl pc, ProjectConfig config) {
final RefControl metaConfigControl =
pc.controlForRef(RefNames.REFS_CONFIG);
local = Maps.newHashMap();
ownerOf = Sets.newHashSet();
Map<AccountGroup.UUID, Boolean> 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);
}
}
isOwner = toBoolean(pc.isOwner());
canUpload = toBoolean(pc.isOwner()
|| (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
canAdd = toBoolean(pc.canAddRefs());
configVisible = pc.isOwner() || metaConfigControl.isVisible();
}
}
public static 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 static 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 static 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,50 @@
// Copyright (C) 2016 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.api.projects;
import com.google.gerrit.extensions.api.projects.AccessApi;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.project.GetAccess;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
public class AccessApiImpl implements AccessApi {
interface Factory {
AccessApiImpl create(ProjectResource project);
}
private final ProjectResource project;
private final GetAccess getAccess;
@Inject
AccessApiImpl(GetAccess getAccess,
@Assisted ProjectResource project) {
this.project = project;
this.getAccess = getAccess;
}
@Override
public ProjectAccessInfo get() throws RestApiException {
try {
return getAccess.apply(project);
} catch (IOException e) {
throw new RestApiException("Cannot get access rights", e);
}
}
}

View File

@@ -26,5 +26,6 @@ public class Module extends FactoryModule {
factory(TagApiImpl.Factory.class);
factory(ProjectApiImpl.Factory.class);
factory(ChildProjectApiImpl.Factory.class);
factory(AccessApiImpl.Factory.class);
}
}

View File

@@ -19,6 +19,7 @@ import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapa
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ChildProjectApi;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.PutDescriptionInput;
@@ -70,6 +71,7 @@ public class ProjectApiImpl implements ProjectApi {
private final String name;
private final BranchApiImpl.Factory branchApi;
private final TagApiImpl.Factory tagApi;
private final AccessApiImpl.Factory accessApi;
private final Provider<ListBranches> listBranchesProvider;
private final Provider<ListTags> listTagsProvider;
@@ -85,12 +87,14 @@ public class ProjectApiImpl implements ProjectApi {
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
AccessApiImpl.Factory accessApiFactory,
Provider<ListBranches> listBranchesProvider,
Provider<ListTags> listTagsProvider,
@Assisted ProjectResource project) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
tagApiFactory, listBranchesProvider, listTagsProvider, project, null);
tagApiFactory, accessApiFactory, listBranchesProvider, listTagsProvider,
project, null);
}
@AssistedInject
@@ -105,12 +109,14 @@ public class ProjectApiImpl implements ProjectApi {
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
AccessApiImpl.Factory accessApiFactory,
Provider<ListBranches> listBranchesProvider,
Provider<ListTags> listTagsProvider,
@Assisted String name) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
tagApiFactory, listBranchesProvider, listTagsProvider, null, name);
tagApiFactory, accessApiFactory, listBranchesProvider, listTagsProvider,
null, name);
}
private ProjectApiImpl(Provider<CurrentUser> user,
@@ -124,6 +130,7 @@ public class ProjectApiImpl implements ProjectApi {
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
AccessApiImpl.Factory accessApiFactory,
Provider<ListBranches> listBranchesProvider,
Provider<ListTags> listTagsProvider,
ProjectResource project,
@@ -141,6 +148,7 @@ public class ProjectApiImpl implements ProjectApi {
this.name = name;
this.branchApi = branchApiFactory;
this.tagApi = tagApiFactory;
this.accessApi = accessApiFactory;
this.listBranchesProvider = listBranchesProvider;
this.listTagsProvider = listTagsProvider;
}
@@ -181,6 +189,11 @@ public class ProjectApiImpl implements ProjectApi {
return getDescription.apply(checkExists());
}
@Override
public ProjectAccessInfo access() throws RestApiException {
return accessApi.create(checkExists()).get();
}
@Override
public void description(PutDescriptionInput in)
throws RestApiException {

View File

@@ -0,0 +1,270 @@
// Copyright (C) 2016 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.project;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
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.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
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.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
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.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Singleton
public class GetAccess implements RestReadView<ProjectResource> {
private static final ImmutableMap<PermissionRule.Action, PermissionRuleInfo.Action> ACTION_TYPE =
Maps.immutableEnumMap(
new ImmutableMap.Builder<PermissionRule.Action, PermissionRuleInfo.Action>()
.put(PermissionRule.Action.ALLOW, PermissionRuleInfo.Action.ALLOW)
.put(PermissionRule.Action.BATCH, PermissionRuleInfo.Action.BATCH)
.put(PermissionRule.Action.BLOCK, PermissionRuleInfo.Action.BLOCK)
.put(PermissionRule.Action.DENY, PermissionRuleInfo.Action.DENY)
.put(PermissionRule.Action.INTERACTIVE,
PermissionRuleInfo.Action.INTERACTIVE)
.build());
private final Provider<CurrentUser> self;
private final GroupControl.Factory groupControlFactory;
private final AllProjectsName allProjectsName;
private final ProjectJson projectJson;
private final ProjectCache projectCache;
private final MetaDataUpdate.Server metaDataUpdateFactory;
private final ProjectControl.GenericFactory projectControlFactory;
private final GroupBackend groupBackend;
@Inject
public GetAccess(Provider<CurrentUser> self,
GroupControl.Factory groupControlFactory,
AllProjectsName allProjectsName,
ProjectCache projectCache,
MetaDataUpdate.Server metaDataUpdateFactory,
ProjectJson projectJson,
ProjectControl.GenericFactory projectControlFactory,
GroupBackend groupBackend) {
this.self = self;
this.groupControlFactory = groupControlFactory;
this.allProjectsName = allProjectsName;
this.projectJson = projectJson;
this.projectCache = projectCache;
this.projectControlFactory = projectControlFactory;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.groupBackend = groupBackend;
}
public ProjectAccessInfo apply(Project.NameKey nameKey)
throws ResourceNotFoundException, ResourceConflictException, IOException {
try {
return this.apply(new ProjectResource(
projectControlFactory.controlFor(nameKey, self.get())));
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(nameKey.get());
}
}
@Override
public ProjectAccessInfo apply(ProjectResource rsrc)
throws ResourceNotFoundException, ResourceConflictException, IOException {
// 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.
//
Project.NameKey projectName = rsrc.getNameKey();
ProjectAccessInfo info = new ProjectAccessInfo();
ProjectConfig config;
ProjectControl pc = open(projectName);
RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
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());
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(rsrc.getName());
}
info.local = new HashMap<>();
info.ownerOf = Sets.newHashSet();
Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (pc.isOwner()) {
info.local.put(name, createAccessSection(section));
info.ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
info.local.put(section.getName(), createAccessSection(section));
}
} else if (RefConfigSection.isValid(name)) {
RefControl rc = pc.controlForRef(name);
if (rc.isOwner()) {
info.local.put(name, createAccessSection(section));
info.ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
info.local.put(name, createAccessSection(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);
info.local.put(name, createAccessSection(dst));
}
dstPerm = dst.getPermission(srcPerm.getName(), true);
}
dstPerm.add(srcRule);
}
}
}
}
}
}
if (info.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.
info.ownerOf.add(AccessSection.ALL);
}
if (config.getRevision() != null) {
info.revision = config.getRevision().name();
}
ProjectState parent =
Iterables.getFirst(pc.getProjectState().parents(), null);
if (parent != null) {
info.inheritsFrom = projectJson.format(parent.getProject());
}
if (pc.getProject().getNameKey().equals(allProjectsName)) {
if (pc.isOwner()) {
info.ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
}
info.isOwner = toBoolean(pc.isOwner());
info.canUpload = toBoolean(pc.isOwner()
|| (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
info.canAdd = toBoolean(pc.canAddRefs());
info.configVisible = pc.isOwner() || metaConfigControl.isVisible();
return info;
}
private AccessSectionInfo createAccessSection(AccessSection section) {
AccessSectionInfo accessSectionInfo = new AccessSectionInfo();
accessSectionInfo.permissions = new HashMap<>();
for (Permission p : section.getPermissions()) {
PermissionInfo pInfo = new PermissionInfo(p.getLabel(),
p.getExclusiveGroup() ? true : null);
pInfo.rules = new HashMap<>();
for (PermissionRule r : p.getRules()) {
PermissionRuleInfo info = new PermissionRuleInfo(
ACTION_TYPE.get(r.getAction()), r.getForce());
if (r.hasRange()) {
info.max = r.getMax();
info.min = r.getMin();
}
pInfo.rules.put(r.getGroup().getUUID().get(), info);
}
accessSectionInfo.permissions.put(p.getName(), pInfo);
}
return accessSectionInfo;
}
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());
}
}
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
}

View File

@@ -42,6 +42,7 @@ public class Module extends RestApiModule {
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
get(PROJECT_KIND, "description").to(GetDescription.class);
get(PROJECT_KIND, "access").to(GetAccess.class);
put(PROJECT_KIND, "description").to(PutDescription.class);
delete(PROJECT_KIND, "description").to(PutDescription.class);