Line data Source code
1 : // Copyright (C) 2009 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.permissions;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.gerrit.entities.AccessSection.ALL;
19 : import static com.google.gerrit.entities.AccessSection.REGEX_PREFIX;
20 : import static com.google.gerrit.entities.RefNames.REFS_TAGS;
21 : import static com.google.gerrit.server.util.MagicBranch.NEW_CHANGE;
22 :
23 : import com.google.common.collect.Sets;
24 : import com.google.gerrit.common.Nullable;
25 : import com.google.gerrit.entities.AccessSection;
26 : import com.google.gerrit.entities.AccountGroup;
27 : import com.google.gerrit.entities.BranchNameKey;
28 : import com.google.gerrit.entities.Change;
29 : import com.google.gerrit.entities.Permission;
30 : import com.google.gerrit.entities.PermissionRule;
31 : import com.google.gerrit.entities.Project;
32 : import com.google.gerrit.entities.RefNames;
33 : import com.google.gerrit.exceptions.StorageException;
34 : import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
35 : import com.google.gerrit.extensions.api.access.PluginProjectPermission;
36 : import com.google.gerrit.extensions.conditions.BooleanCondition;
37 : import com.google.gerrit.extensions.restapi.AuthException;
38 : import com.google.gerrit.server.CurrentUser;
39 : import com.google.gerrit.server.account.GroupMembership;
40 : import com.google.gerrit.server.config.AllUsersName;
41 : import com.google.gerrit.server.config.GitReceivePackGroups;
42 : import com.google.gerrit.server.config.GitUploadPackGroups;
43 : import com.google.gerrit.server.git.GitRepositoryManager;
44 : import com.google.gerrit.server.group.SystemGroupBackend;
45 : import com.google.gerrit.server.notedb.ChangeNotes;
46 : import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
47 : import com.google.gerrit.server.permissions.PermissionBackend.ForProject;
48 : import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
49 : import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
50 : import com.google.gerrit.server.project.ProjectState;
51 : import com.google.gerrit.server.project.SectionMatcher;
52 : import com.google.gerrit.server.query.change.ChangeData;
53 : import com.google.inject.Inject;
54 : import com.google.inject.assistedinject.Assisted;
55 : import java.util.Collection;
56 : import java.util.Collections;
57 : import java.util.HashMap;
58 : import java.util.HashSet;
59 : import java.util.List;
60 : import java.util.Map;
61 : import java.util.Set;
62 : import org.eclipse.jgit.lib.Ref;
63 : import org.eclipse.jgit.lib.Repository;
64 :
65 : /** Access control management for a user accessing a project's data. */
66 : class ProjectControl {
67 : interface Factory {
68 : ProjectControl create(CurrentUser who, ProjectState ps);
69 : }
70 :
71 : private final Set<AccountGroup.UUID> uploadGroups;
72 : private final Set<AccountGroup.UUID> receiveGroups;
73 : private final PermissionBackend permissionBackend;
74 : private final RefVisibilityControl refVisibilityControl;
75 : private final GitRepositoryManager repositoryManager;
76 : private final CurrentUser user;
77 : private final ProjectState state;
78 : private final PermissionCollection.Factory permissionFilter;
79 : private final DefaultRefFilter.Factory refFilterFactory;
80 : private final ChangeData.Factory changeDataFactory;
81 : private final AllUsersName allUsersName;
82 :
83 : private List<SectionMatcher> allSections;
84 : private Map<String, RefControl> refControls;
85 : private Boolean declaredOwner;
86 :
87 : @Inject
88 : ProjectControl(
89 : @GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
90 : @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
91 : PermissionCollection.Factory permissionFilter,
92 : PermissionBackend permissionBackend,
93 : RefVisibilityControl refVisibilityControl,
94 : GitRepositoryManager repositoryManager,
95 : DefaultRefFilter.Factory refFilterFactory,
96 : ChangeData.Factory changeDataFactory,
97 : AllUsersName allUsersName,
98 : @Assisted CurrentUser who,
99 145 : @Assisted ProjectState ps) {
100 145 : this.uploadGroups = uploadGroups;
101 145 : this.receiveGroups = receiveGroups;
102 145 : this.permissionFilter = permissionFilter;
103 145 : this.permissionBackend = permissionBackend;
104 145 : this.refVisibilityControl = refVisibilityControl;
105 145 : this.repositoryManager = repositoryManager;
106 145 : this.refFilterFactory = refFilterFactory;
107 145 : this.changeDataFactory = changeDataFactory;
108 145 : this.allUsersName = allUsersName;
109 145 : user = who;
110 145 : state = ps;
111 145 : }
112 :
113 : ForProject asForProject() {
114 145 : return new ForProjectImpl();
115 : }
116 :
117 : ChangeControl controlFor(ChangeData cd) {
118 103 : return new ChangeControl(controlForRef(cd.change().getDest()), cd);
119 : }
120 :
121 : RefControl controlForRef(BranchNameKey ref) {
122 103 : return controlForRef(ref.branch());
123 : }
124 :
125 : public RefControl controlForRef(String refName) {
126 145 : if (refControls == null) {
127 145 : refControls = new HashMap<>();
128 : }
129 145 : RefControl ctl = refControls.get(refName);
130 145 : if (ctl == null) {
131 145 : PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
132 145 : ctl =
133 : new RefControl(
134 : changeDataFactory, refVisibilityControl, this, repositoryManager, refName, relevant);
135 145 : refControls.put(refName, ctl);
136 : }
137 145 : return ctl;
138 : }
139 :
140 : CurrentUser getUser() {
141 145 : return user;
142 : }
143 :
144 : ProjectState getProjectState() {
145 141 : return state;
146 : }
147 :
148 : Project getProject() {
149 145 : return state.getProject();
150 : }
151 :
152 : /** Is this user a project owner? */
153 : boolean isOwner() {
154 144 : return (isDeclaredOwner() && controlForRef(ALL).canPerform(Permission.OWNER)) || isAdmin();
155 : }
156 :
157 : /**
158 : * Returns {@code Capable.OK} if the user can upload to at least one reference. Does not check
159 : * Contributor Agreements.
160 : */
161 : boolean canPushToAtLeastOneRef() {
162 98 : return canPerformOnAnyRef(Permission.PUSH)
163 1 : || canPerformOnAnyRef(Permission.CREATE_TAG)
164 98 : || isOwner();
165 : }
166 :
167 : boolean isAdmin() {
168 : try {
169 144 : return permissionBackend.user(user).test(GlobalPermission.ADMINISTRATE_SERVER);
170 0 : } catch (PermissionBackendException e) {
171 0 : return false;
172 : }
173 : }
174 :
175 : boolean match(PermissionRule rule, boolean isChangeOwner) {
176 145 : return match(rule.getGroup().getUUID(), isChangeOwner);
177 : }
178 :
179 : boolean allRefsAreVisible(Set<String> ignore) {
180 136 : return user.isInternalUser()
181 136 : || (!getProject().getNameKey().equals(allUsersName)
182 136 : && canPerformOnAllRefs(Permission.READ, ignore));
183 : }
184 :
185 : /** Can the user run upload pack? */
186 : private boolean canRunUploadPack() {
187 134 : for (AccountGroup.UUID group : uploadGroups) {
188 134 : if (match(group)) {
189 134 : return true;
190 : }
191 3 : }
192 0 : return false;
193 : }
194 :
195 : /** Can the user run receive pack? */
196 : private boolean canRunReceivePack() {
197 97 : for (AccountGroup.UUID group : receiveGroups) {
198 97 : if (match(group)) {
199 97 : return true;
200 : }
201 0 : }
202 0 : return false;
203 : }
204 :
205 : private boolean canAddRefs() {
206 9 : return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
207 : }
208 :
209 : private boolean canAddTagRefs() {
210 9 : return (canPerformOnTagRef(Permission.CREATE) || isAdmin());
211 : }
212 :
213 : private boolean canCreateChanges() {
214 57 : for (SectionMatcher matcher : access()) {
215 57 : AccessSection section = matcher.getSection();
216 57 : if (section.getName().startsWith(NEW_CHANGE)
217 56 : || section.getName().startsWith(REGEX_PREFIX + NEW_CHANGE)) {
218 57 : Permission permission = section.getPermission(Permission.PUSH);
219 57 : if (permission != null && controlForRef(section.getName()).canPerform(Permission.PUSH)) {
220 57 : return true;
221 : }
222 : }
223 56 : }
224 0 : return false;
225 : }
226 :
227 : private boolean isDeclaredOwner() {
228 145 : if (declaredOwner == null) {
229 145 : GroupMembership effectiveGroups = user.getEffectiveGroups();
230 145 : declaredOwner = effectiveGroups.containsAnyOf(state.getAllOwners());
231 : }
232 145 : return declaredOwner;
233 : }
234 :
235 : private boolean canPerformOnTagRef(String permissionName) {
236 9 : for (SectionMatcher matcher : access()) {
237 9 : AccessSection section = matcher.getSection();
238 :
239 9 : if (section.getName().startsWith(REFS_TAGS)
240 9 : || section.getName().startsWith(REGEX_PREFIX + REFS_TAGS)) {
241 9 : Permission permission = section.getPermission(permissionName);
242 9 : if (permission == null) {
243 0 : continue;
244 : }
245 :
246 9 : Boolean can = canPerform(permissionName, section, permission);
247 9 : if (can != null) {
248 9 : return can;
249 : }
250 : }
251 9 : }
252 :
253 1 : return false;
254 : }
255 :
256 : private boolean canPerformOnAnyRef(String permissionName) {
257 118 : for (SectionMatcher matcher : access()) {
258 118 : AccessSection section = matcher.getSection();
259 118 : Permission permission = section.getPermission(permissionName);
260 118 : if (permission == null) {
261 118 : continue;
262 : }
263 :
264 118 : Boolean can = canPerform(permissionName, section, permission);
265 118 : if (can != null) {
266 118 : return can;
267 : }
268 44 : }
269 :
270 12 : return false;
271 : }
272 :
273 : @Nullable
274 : private Boolean canPerform(String permissionName, AccessSection section, Permission permission) {
275 118 : for (PermissionRule rule : permission.getRules()) {
276 118 : if (rule.isBlock() || rule.isDeny() || !match(rule)) {
277 44 : continue;
278 : }
279 :
280 : // Being in a group that was granted this permission is only an
281 : // approximation. There might be overrides and doNotInherit
282 : // that would render this to be false.
283 : //
284 118 : if (controlForRef(section.getName()).canPerform(permissionName)) {
285 118 : return true;
286 : }
287 : break;
288 : }
289 44 : return null;
290 : }
291 :
292 : private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
293 135 : boolean canPerform = false;
294 135 : Set<String> patterns = allRefPatterns(permission);
295 135 : if (patterns.contains(ALL)) {
296 : // Only possible if granted on the pattern that
297 : // matches every possible reference. Check all
298 : // patterns also have the permission.
299 : //
300 135 : for (String pattern : patterns) {
301 135 : if (controlForRef(pattern).canPerform(permission)) {
302 135 : canPerform = true;
303 31 : } else if (ignore.contains(pattern)) {
304 28 : continue;
305 : } else {
306 31 : return false;
307 : }
308 135 : }
309 : }
310 135 : return canPerform;
311 : }
312 :
313 : private Set<String> allRefPatterns(String permissionName) {
314 135 : Set<String> all = new HashSet<>();
315 135 : for (SectionMatcher matcher : access()) {
316 135 : AccessSection section = matcher.getSection();
317 135 : Permission permission = section.getPermission(permissionName);
318 135 : if (permission != null) {
319 135 : all.add(section.getName());
320 : }
321 135 : }
322 135 : return all;
323 : }
324 :
325 : private List<SectionMatcher> access() {
326 145 : if (allSections == null) {
327 145 : allSections = state.getAllSections();
328 : }
329 145 : return allSections;
330 : }
331 :
332 : private boolean match(PermissionRule rule) {
333 118 : return match(rule.getGroup().getUUID());
334 : }
335 :
336 : private boolean match(AccountGroup.UUID uuid) {
337 139 : return match(uuid, false);
338 : }
339 :
340 : private boolean match(AccountGroup.UUID uuid, boolean isChangeOwner) {
341 145 : if (SystemGroupBackend.PROJECT_OWNERS.equals(uuid)) {
342 114 : return isDeclaredOwner();
343 145 : } else if (SystemGroupBackend.CHANGE_OWNER.equals(uuid)) {
344 9 : return isChangeOwner;
345 : } else {
346 145 : return user.getEffectiveGroups().contains(uuid);
347 : }
348 : }
349 :
350 145 : private class ForProjectImpl extends ForProject {
351 : private String resourcePath;
352 :
353 : @Override
354 : public String resourcePath() {
355 58 : if (resourcePath == null) {
356 58 : resourcePath = "/projects/" + getProjectState().getName();
357 : }
358 58 : return resourcePath;
359 : }
360 :
361 : @Override
362 : public ForRef ref(String ref) {
363 132 : return controlForRef(ref).asForRef();
364 : }
365 :
366 : @Override
367 : public ForChange change(ChangeData cd) {
368 : try {
369 19 : checkProject(cd.change());
370 19 : return super.change(cd);
371 0 : } catch (StorageException e) {
372 0 : return FailedPermissionBackend.change("unavailable", e);
373 : }
374 : }
375 :
376 : @Override
377 : public ForChange change(ChangeNotes notes) {
378 38 : checkProject(notes.getChange());
379 38 : return super.change(notes);
380 : }
381 :
382 : private void checkProject(Change change) {
383 47 : Project.NameKey project = getProject().getNameKey();
384 47 : checkArgument(
385 47 : project.equals(change.getProject()),
386 : "expected change in project %s, not %s",
387 : project,
388 47 : change.getProject());
389 47 : }
390 :
391 : @Override
392 : public void check(CoreOrPluginProjectPermission perm)
393 : throws AuthException, PermissionBackendException {
394 144 : if (!can(perm)) {
395 15 : throw new AuthException(perm.describeForException() + " not permitted");
396 : }
397 144 : }
398 :
399 : @Override
400 : public <T extends CoreOrPluginProjectPermission> Set<T> test(Collection<T> permSet)
401 : throws PermissionBackendException {
402 143 : Set<T> ok = Sets.newHashSetWithExpectedSize(permSet.size());
403 143 : for (T perm : permSet) {
404 143 : if (can(perm)) {
405 143 : ok.add(perm);
406 : }
407 143 : }
408 143 : return ok;
409 : }
410 :
411 : @Override
412 : public BooleanCondition testCond(CoreOrPluginProjectPermission perm) {
413 58 : return new PermissionBackendCondition.ForProject(this, perm, getUser());
414 : }
415 :
416 : @Override
417 : public Collection<Ref> filter(Collection<Ref> refs, Repository repo, RefFilterOptions opts)
418 : throws PermissionBackendException {
419 135 : return refFilterFactory.create(ProjectControl.this).filter(refs, repo, opts);
420 : }
421 :
422 : private boolean can(CoreOrPluginProjectPermission perm) throws PermissionBackendException {
423 144 : if (perm instanceof ProjectPermission) {
424 144 : return can((ProjectPermission) perm);
425 0 : } else if (perm instanceof PluginProjectPermission) {
426 : // TODO(xchangcheng): implement for plugin defined project permissions.
427 0 : return false;
428 : }
429 :
430 0 : throw new PermissionBackendException(perm.describeForException() + " unsupported");
431 : }
432 :
433 : private boolean can(ProjectPermission perm) throws PermissionBackendException {
434 144 : switch (perm) {
435 : case ACCESS:
436 144 : return user.isInternalUser() || isOwner() || canPerformOnAnyRef(Permission.READ);
437 :
438 : case READ:
439 136 : return allRefsAreVisible(Collections.emptySet());
440 :
441 : case CREATE_REF:
442 9 : return canAddRefs();
443 : case CREATE_TAG_REF:
444 9 : return canAddTagRefs();
445 : case CREATE_CHANGE:
446 57 : return canCreateChanges();
447 :
448 : case RUN_RECEIVE_PACK:
449 97 : return canRunReceivePack();
450 : case RUN_UPLOAD_PACK:
451 134 : return canRunUploadPack();
452 :
453 : case PUSH_AT_LEAST_ONE_REF:
454 97 : return canPushToAtLeastOneRef();
455 :
456 : case READ_CONFIG:
457 12 : return controlForRef(RefNames.REFS_CONFIG).hasReadPermissionOnRef(false);
458 :
459 : case BAN_COMMIT:
460 : case READ_REFLOG:
461 : case WRITE_CONFIG:
462 40 : return isOwner();
463 : }
464 0 : throw new PermissionBackendException(perm + " unsupported");
465 : }
466 : }
467 : }
|