Line data Source code
1 : // Copyright (C) 2017 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.project; 16 : 17 : import com.google.common.collect.ImmutableList; 18 : import com.google.common.collect.Iterables; 19 : import com.google.gerrit.common.data.GlobalCapability; 20 : import com.google.gerrit.entities.AccessSection; 21 : import com.google.gerrit.entities.AccountGroup; 22 : import com.google.gerrit.entities.GroupDescription; 23 : import com.google.gerrit.entities.GroupReference; 24 : import com.google.gerrit.entities.LabelType; 25 : import com.google.gerrit.entities.Permission; 26 : import com.google.gerrit.entities.PermissionRule; 27 : import com.google.gerrit.entities.Project; 28 : import com.google.gerrit.exceptions.InvalidNameException; 29 : import com.google.gerrit.extensions.api.access.AccessSectionInfo; 30 : import com.google.gerrit.extensions.api.access.PermissionInfo; 31 : import com.google.gerrit.extensions.api.access.PermissionRuleInfo; 32 : import com.google.gerrit.extensions.restapi.AuthException; 33 : import com.google.gerrit.extensions.restapi.BadRequestException; 34 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 35 : import com.google.gerrit.extensions.restapi.UnprocessableEntityException; 36 : import com.google.gerrit.server.IdentifiedUser; 37 : import com.google.gerrit.server.config.AllProjectsName; 38 : import com.google.gerrit.server.group.GroupResolver; 39 : import com.google.gerrit.server.permissions.PermissionBackendException; 40 : import com.google.gerrit.server.permissions.PluginPermissionsUtil; 41 : import com.google.gerrit.server.project.ProjectConfig; 42 : import com.google.gerrit.server.project.RefPattern; 43 : import com.google.inject.Inject; 44 : import com.google.inject.Provider; 45 : import com.google.inject.Singleton; 46 : import java.util.List; 47 : import java.util.Map; 48 : import java.util.Set; 49 : 50 : @Singleton 51 : public class SetAccessUtil { 52 : private final GroupResolver groupResolver; 53 : private final AllProjectsName allProjects; 54 : private final Provider<SetParent> setParent; 55 : private final PluginPermissionsUtil pluginPermissionsUtil; 56 : 57 : @Inject 58 : private SetAccessUtil( 59 : GroupResolver groupResolver, 60 : AllProjectsName allProjects, 61 : Provider<SetParent> setParent, 62 146 : PluginPermissionsUtil pluginPermissionsUtil) { 63 146 : this.groupResolver = groupResolver; 64 146 : this.allProjects = allProjects; 65 146 : this.setParent = setParent; 66 146 : this.pluginPermissionsUtil = pluginPermissionsUtil; 67 146 : } 68 : 69 : ImmutableList<AccessSection> getAccessSections( 70 : Map<String, AccessSectionInfo> sectionInfos, boolean rejectNonResolvableGroups) 71 : throws UnprocessableEntityException { 72 8 : if (sectionInfos == null) { 73 7 : return ImmutableList.of(); 74 : } 75 : 76 7 : ImmutableList.Builder<AccessSection> sections = 77 7 : ImmutableList.builderWithExpectedSize(sectionInfos.size()); 78 7 : for (Map.Entry<String, AccessSectionInfo> entry : sectionInfos.entrySet()) { 79 7 : if (entry.getValue().permissions == null) { 80 0 : continue; 81 : } 82 : 83 7 : AccessSection.Builder accessSection = AccessSection.builder(entry.getKey()); 84 : for (Map.Entry<String, PermissionInfo> permissionEntry : 85 7 : entry.getValue().permissions.entrySet()) { 86 7 : if (permissionEntry.getValue().rules == null) { 87 0 : continue; 88 : } 89 : 90 7 : Permission.Builder p = Permission.builder(permissionEntry.getKey()); 91 7 : if (permissionEntry.getValue().exclusive != null) { 92 1 : p.setExclusiveGroup(permissionEntry.getValue().exclusive); 93 : } 94 : 95 : for (Map.Entry<String, PermissionRuleInfo> permissionRuleInfoEntry : 96 7 : permissionEntry.getValue().rules.entrySet()) { 97 7 : GroupDescription.Basic group = groupResolver.parseId(permissionRuleInfoEntry.getKey()); 98 : GroupReference groupReference; 99 7 : if (group != null) { 100 7 : groupReference = GroupReference.forGroup(group); 101 : } else { 102 1 : if (rejectNonResolvableGroups) { 103 0 : throw new UnprocessableEntityException( 104 0 : permissionRuleInfoEntry.getKey() + " is not a valid group ID"); 105 : } 106 1 : AccountGroup.UUID uuid = AccountGroup.UUID.parse(permissionRuleInfoEntry.getKey()); 107 1 : groupReference = GroupReference.create(uuid, uuid.get()); 108 : } 109 : 110 7 : PermissionRuleInfo pri = permissionRuleInfoEntry.getValue(); 111 7 : PermissionRule.Builder r = PermissionRule.builder(groupReference); 112 7 : if (pri != null) { 113 7 : if (pri.max != null) { 114 1 : r.setMax(pri.max); 115 : } 116 7 : if (pri.min != null) { 117 1 : r.setMin(pri.min); 118 : } 119 7 : if (pri.action != null) { 120 7 : r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action)); 121 : } 122 7 : if (pri.force != null) { 123 7 : r.setForce(pri.force); 124 : } 125 : } 126 7 : p.add(r); 127 7 : } 128 7 : accessSection.addPermission(p); 129 7 : } 130 7 : sections.add(accessSection.build()); 131 7 : } 132 7 : return sections.build(); 133 : } 134 : 135 : /** 136 : * Checks that the removals and additions are logically valid, but doesn't check current user's 137 : * permission. 138 : */ 139 : void validateChanges( 140 : ProjectConfig config, List<AccessSection> removals, List<AccessSection> additions) 141 : throws BadRequestException, InvalidNameException { 142 : // Perform permission checks 143 8 : for (AccessSection section : Iterables.concat(additions, removals)) { 144 7 : boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(section.getName()); 145 7 : if (isGlobalCapabilities) { 146 3 : if (!allProjects.equals(config.getName())) { 147 1 : throw new BadRequestException( 148 1 : "Cannot edit global capabilities for projects other than " + allProjects.get()); 149 : } 150 : } 151 7 : } 152 : 153 : // Perform addition checks 154 8 : for (AccessSection section : additions) { 155 7 : String name = section.getName(); 156 7 : boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(name); 157 : 158 7 : if (!isGlobalCapabilities) { 159 6 : if (!AccessSection.isValidRefSectionName(name)) { 160 0 : throw new BadRequestException("invalid section name"); 161 : } 162 6 : RefPattern.validate(name); 163 : 164 : // Check all permissions for soundness 165 6 : for (Permission p : section.getPermissions()) { 166 6 : if (!isPermission(p.getName())) { 167 1 : throw new BadRequestException("Unknown permission: " + p.getName()); 168 : } 169 6 : } 170 : } else { 171 : // Check all permissions for soundness 172 3 : for (Permission p : section.getPermissions()) { 173 3 : if (!isCapability(p.getName())) { 174 1 : throw new BadRequestException("Unknown global capability: " + p.getName()); 175 : } 176 3 : } 177 : } 178 7 : } 179 8 : } 180 : 181 : void applyChanges( 182 : ProjectConfig config, List<AccessSection> removals, List<AccessSection> additions) { 183 : // Apply removals 184 8 : for (AccessSection section : removals) { 185 1 : if (section.getPermissions().isEmpty()) { 186 : // Remove entire section 187 0 : config.remove(config.getAccessSection(section.getName())); 188 0 : continue; 189 : } 190 : 191 : // Remove specific permissions 192 1 : for (Permission p : section.getPermissions()) { 193 1 : if (p.getRules().isEmpty()) { 194 1 : config.remove(config.getAccessSection(section.getName()), p); 195 : } else { 196 1 : for (PermissionRule r : p.getRules()) { 197 1 : config.remove(config.getAccessSection(section.getName()), p, r); 198 1 : } 199 : } 200 1 : } 201 1 : } 202 : 203 : // Apply additions 204 8 : for (AccessSection section : additions) { 205 7 : config.upsertAccessSection( 206 7 : section.getName(), 207 : existingAccessSection -> { 208 7 : for (Permission p : section.getPermissions()) { 209 7 : Permission currentPermission = 210 7 : existingAccessSection.build().getPermission(p.getName()); 211 7 : if (currentPermission == null) { 212 : // Add Permission 213 7 : existingAccessSection.addPermission(p.toBuilder()); 214 : } else { 215 1 : for (PermissionRule r : p.getRules()) { 216 : // AddPermissionRule 217 1 : existingAccessSection.upsertPermission(p.getName()).add(r.toBuilder()); 218 1 : } 219 : } 220 7 : } 221 7 : }); 222 7 : } 223 8 : } 224 : 225 : /** 226 : * Updates the parent project in the given config. 227 : * 228 : * @param identifiedUser the user 229 : * @param config the config to modify 230 : * @param projectName the project for which to change access. 231 : * @param newParentProjectName the new parent to set; passing null will make this a nop 232 : * @param checkAdmin if set, verify that user has administrateServer permission 233 : */ 234 : public void setParentName( 235 : IdentifiedUser identifiedUser, 236 : ProjectConfig config, 237 : Project.NameKey projectName, 238 : Project.NameKey newParentProjectName, 239 : boolean checkAdmin) 240 : throws ResourceConflictException, AuthException, PermissionBackendException, 241 : BadRequestException { 242 8 : if (newParentProjectName != null 243 1 : && !config.getProject().getNameKey().equals(allProjects) 244 1 : && !config.getProject().getParent(allProjects).equals(newParentProjectName)) { 245 : try { 246 1 : setParent 247 1 : .get() 248 1 : .validateParentUpdate( 249 1 : projectName, identifiedUser, newParentProjectName.get(), checkAdmin); 250 0 : } catch (UnprocessableEntityException e) { 251 0 : throw new ResourceConflictException(e.getMessage(), e); 252 1 : } 253 1 : config.updateProject(p -> p.setParent(newParentProjectName)); 254 : } 255 8 : } 256 : 257 : private boolean isPermission(String name) { 258 6 : if (Permission.isPermission(name)) { 259 5 : if (Permission.isLabel(name) || Permission.isLabelAs(name)) { 260 1 : String labelName = Permission.extractLabel(name); 261 : try { 262 1 : LabelType.checkName(labelName); 263 1 : } catch (IllegalArgumentException e) { 264 1 : return false; 265 1 : } 266 : } 267 5 : return true; 268 : } 269 2 : Set<String> pluginPermissions = 270 2 : pluginPermissionsUtil.collectPluginProjectPermissions().keySet(); 271 2 : return pluginPermissions.contains(name); 272 : } 273 : 274 : private boolean isCapability(String name) { 275 3 : if (GlobalCapability.isGlobalCapability(name)) { 276 1 : return true; 277 : } 278 3 : Set<String> pluginCapabilities = pluginPermissionsUtil.collectPluginCapabilities().keySet(); 279 3 : return pluginCapabilities.contains(name); 280 : } 281 : }