All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.permissions.PermissionCollection Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2011 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.permissions;

import static com.google.gerrit.entities.PermissionRule.Action.BLOCK;
import static com.google.gerrit.server.project.RefPattern.containsParameters;
import static com.google.gerrit.server.project.RefPattern.isRE;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

import com.google.auto.value.AutoValue;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.PermissionRule;
import com.google.gerrit.entities.PermissionRule.Action;
import com.google.gerrit.entities.Project;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.project.RefPatternMatcher.ExpandParameters;
import com.google.gerrit.server.project.SectionMatcher;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Effective permissions applied to a reference in a project.
 *
 * 

A collection may be user specific if a matching {@link AccessSection} uses "${username}" in * its name. The permissions granted in that section may only be granted to the username that * appears in the reference name, and also only if the user is a member of the relevant group. */ public class PermissionCollection { @Singleton public static class Factory { private final SectionSortCache sorter; // TODO(hiesel): Remove this once we got production data private final Timer0 filterLatency; @Inject Factory(SectionSortCache sorter, MetricMaker metricMaker) { this.sorter = sorter; this.filterLatency = metricMaker.newTimer( "permissions/permission_collection/filter_latency", new Description("Latency for access filter computations in PermissionCollection") .setCumulative() .setUnit(Units.NANOSECONDS)); } /** * Drop the SectionMatchers that don't apply to the current ref. The user is only used for * expanding per-user ref patterns, and not for checking group memberships. * * @param matcherList the input sections. * @param ref the ref name for which to filter. * @param user Only used for expanding per-user ref patterns. * @param out the filtered sections. * @return true if the result is only valid for this user. */ private static boolean filterRefMatchingSections( Iterable matcherList, String ref, CurrentUser user, Map out) { boolean perUser = false; for (SectionMatcher sm : matcherList) { // If the matcher has to expand parameters and its prefix matches the // reference there is a very good chance the reference is actually user // specific, even if the matcher does not match the reference. Since its // difficult to prove this is true all of the time, use an approximation // to prevent reuse of collections across users accessing the same // reference at the same time. // // This check usually gets caching right, as most per-user references // use a common prefix like "refs/sandbox/" or "refs/heads/users/" // that will never be shared with non-user references, and the per-user // references are usually less frequent than the non-user references. if (sm.getMatcher() instanceof ExpandParameters) { if (!((ExpandParameters) sm.getMatcher()).matchPrefix(ref)) { continue; } perUser = true; if (sm.match(ref, user)) { out.put(sm.getSection(), sm.getProject()); } } else if (sm.match(ref, null)) { out.put(sm.getSection(), sm.getProject()); } } return perUser; } /** * Get all permissions that apply to a reference. The user is only used for per-user ref names, * so the return value may include permissions for groups the user is not part of. * * @param matcherList collection of sections that should be considered, in priority order * (project specific definitions must appear before inherited ones). * @param ref reference being accessed. * @param user if the reference is a per-user reference, e.g. access sections using the * parameter variable "${username}" will have each username inserted into them to see if * they apply to the reference named by {@code ref}. * @return map of permissions that apply to this reference, keyed by permission name. */ PermissionCollection filter( Iterable matcherList, String ref, CurrentUser user) { try (Timer0.Context ignored = filterLatency.start()) { if (isRE(ref)) { if (!containsParameters(ref)) { ref = RefPattern.shortestExample(ref); } } else if (ref.endsWith("/*")) { ref = ref.substring(0, ref.length() - 1); } // LinkedHashMap to maintain input ordering. Map sectionToProject = new LinkedHashMap<>(); boolean perUser = filterRefMatchingSections(matcherList, ref, user, sectionToProject); List sections = Lists.newArrayList(sectionToProject.keySet()); // Sort by ref pattern specificity. For equally specific patterns, the sections from the // project closer to the current one come first. sorter.sort(ref, sections); // For block permissions, we want a different order: first, we want to go from parent to // child. List> accessDescending = Lists.reverse(Lists.newArrayList(sectionToProject.entrySet())); Map> accessByProject = accessDescending.stream() .collect( Collectors.groupingBy( Map.Entry::getValue, LinkedHashMap::new, mapping(Map.Entry::getKey, toList()))); // Within each project, sort by ref specificity. for (List secs : accessByProject.values()) { sorter.sort(ref, secs); } return new PermissionCollection( Lists.newArrayList(accessByProject.values()), sections, perUser); } } } /** Returns permissions in the right order for evaluating BLOCK status. */ List> getBlockRules(String perm) { List> ps = blockPerProjectByPermission.get(perm); if (ps == null) { ps = calculateBlockRules(perm); blockPerProjectByPermission.put(perm, ps); } return ps; } /** Returns permissions in the right order for evaluating ALLOW/DENY status. */ List getAllowRules(String perm) { List ps = rulesByPermission.get(perm); if (ps == null) { ps = calculateAllowRules(perm); rulesByPermission.put(perm, ps); } return ps; } /** calculates permissions for ALLOW processing. */ private List calculateAllowRules(String permName) { Set seen = new HashSet<>(); List r = new ArrayList<>(); for (AccessSection s : accessSectionsUpward) { Permission p = s.getPermission(permName); if (p == null) { continue; } for (PermissionRule pr : p.getRules()) { SeenRule sr = SeenRule.create(s, pr); if (seen.contains(sr)) { // We allow only one rule per (ref-pattern, group) tuple. This is used to implement DENY: // If we see a DENY before an ALLOW rule, that causes the ALLOW rule to be skipped here, // negating access. continue; } seen.add(sr); if (pr.getAction() == BLOCK) { // Block rules are handled elsewhere. continue; } if (pr.getAction() == PermissionRule.Action.DENY) { // DENY rules work by not adding ALLOW rules. Nothing else to do. continue; } r.add(pr); } if (p.getExclusiveGroup()) { // We found an exclusive permission, so no need to further go up the hierarchy. break; } } return r; } // Calculates the inputs for determining BLOCK status, grouped by project. private List> calculateBlockRules(String permName) { List> result = new ArrayList<>(); for (List secs : this.accessSectionsPerProjectDownward) { List perms = new ArrayList<>(); boolean blockFound = false; for (AccessSection sec : secs) { Permission p = sec.getPermission(permName); if (p == null) { continue; } for (PermissionRule pr : p.getRules()) { if (blockFound || pr.getAction() == Action.BLOCK) { blockFound = true; break; } } perms.add(p); } if (blockFound) { result.add(perms); } } return result; } private List> accessSectionsPerProjectDownward; private List accessSectionsUpward; private final Map> rulesByPermission; private final Map>> blockPerProjectByPermission; private final boolean perUser; private PermissionCollection( List> accessSectionsDownward, List accessSectionsUpward, boolean perUser) { this.accessSectionsPerProjectDownward = accessSectionsDownward; this.accessSectionsUpward = accessSectionsUpward; this.rulesByPermission = new HashMap<>(); this.blockPerProjectByPermission = new HashMap<>(); this.perUser = perUser; } /** * Returns true if a "${username}" pattern might need to be expanded to build this collection, * making the results user specific. */ public boolean isUserSpecific() { return perUser; } /** (ref, permission, group) tuple. */ @AutoValue abstract static class SeenRule { public abstract String refPattern(); @Nullable public abstract AccountGroup.UUID group(); static SeenRule create(AccessSection section, @Nullable PermissionRule rule) { AccountGroup.UUID group = rule != null && rule.getGroup() != null ? rule.getGroup().getUUID() : null; return new AutoValue_PermissionCollection_SeenRule(section.getName(), group); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy