LCOV - code coverage report
Current view: top level - server/project - ContributorAgreementsChecker.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 54 59 91.5 %
Date: 2022-11-19 15:00:39 Functions: 4 5 80.0 %

          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.project;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static java.util.Objects.requireNonNull;
      19             : 
      20             : import com.google.gerrit.entities.AccountGroup;
      21             : import com.google.gerrit.entities.AccountGroup.UUID;
      22             : import com.google.gerrit.entities.BooleanProjectConfig;
      23             : import com.google.gerrit.entities.ContributorAgreement;
      24             : import com.google.gerrit.entities.PermissionRule;
      25             : import com.google.gerrit.entities.PermissionRule.Action;
      26             : import com.google.gerrit.entities.Project;
      27             : import com.google.gerrit.extensions.registration.DynamicItem;
      28             : import com.google.gerrit.extensions.restapi.AuthException;
      29             : import com.google.gerrit.metrics.Counter0;
      30             : import com.google.gerrit.metrics.Description;
      31             : import com.google.gerrit.metrics.MetricMaker;
      32             : import com.google.gerrit.server.CurrentUser;
      33             : import com.google.gerrit.server.IdentifiedUser;
      34             : import com.google.gerrit.server.config.UrlFormatter;
      35             : import com.google.inject.Inject;
      36             : import com.google.inject.Singleton;
      37             : import java.io.IOException;
      38             : import java.util.ArrayList;
      39             : import java.util.Collection;
      40             : import java.util.List;
      41             : import java.util.regex.Pattern;
      42             : import java.util.regex.PatternSyntaxException;
      43             : 
      44             : @Singleton
      45             : public class ContributorAgreementsChecker {
      46             : 
      47             :   private final DynamicItem<UrlFormatter> urlFormatter;
      48             :   private final ProjectCache projectCache;
      49             :   private final Metrics metrics;
      50             : 
      51             :   @Singleton
      52             :   protected static class Metrics {
      53             :     final Counter0 claCheckCount;
      54             : 
      55             :     @Inject
      56         149 :     Metrics(MetricMaker metricMaker) {
      57         149 :       claCheckCount =
      58         149 :           metricMaker.newCounter(
      59             :               "license/cla_check_count",
      60         149 :               new Description("Total number of CLA check requests").setRate().setUnit("requests"));
      61         149 :     }
      62             :   }
      63             : 
      64             :   @Inject
      65             :   ContributorAgreementsChecker(
      66         149 :       DynamicItem<UrlFormatter> urlFormatter, ProjectCache projectCache, Metrics metrics) {
      67         149 :     this.urlFormatter = urlFormatter;
      68         149 :     this.projectCache = projectCache;
      69         149 :     this.metrics = metrics;
      70         149 :   }
      71             : 
      72             :   /**
      73             :    * Checks if the user has signed a contributor agreement for the project.
      74             :    *
      75             :    * @throws AuthException if the user has not signed a contributor agreement for the project
      76             :    * @throws IOException if project states could not be loaded
      77             :    */
      78             :   public void check(Project.NameKey project, CurrentUser user) throws IOException, AuthException {
      79         109 :     metrics.claCheckCount.increment();
      80             : 
      81         109 :     ProjectState projectState =
      82         109 :         projectCache.get(project).orElseThrow(() -> new IOException("Can't load " + project));
      83         109 :     if (!projectState.is(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS)) {
      84         109 :       return;
      85             :     }
      86             : 
      87           1 :     if (!user.isIdentifiedUser()) {
      88           0 :       throw new AuthException("Must be logged in to verify Contributor Agreement");
      89             :     }
      90             : 
      91           1 :     IdentifiedUser iUser = user.asIdentifiedUser();
      92           1 :     Collection<ContributorAgreement> contributorAgreements =
      93           1 :         projectCache.getAllProjects().getConfig().getContributorAgreements().values();
      94           1 :     List<UUID> okGroupIds = new ArrayList<>();
      95           1 :     for (ContributorAgreement ca : contributorAgreements) {
      96             :       List<AccountGroup.UUID> groupIds;
      97           1 :       groupIds = okGroupIds;
      98             : 
      99             :       // matchProjects defaults to match all projects when missing.
     100           1 :       List<String> matchProjectsRegexes = ca.getMatchProjectsRegexes();
     101           1 :       if (!matchProjectsRegexes.isEmpty()
     102           0 :           && !projectMatchesAnyPattern(project.get(), matchProjectsRegexes)) {
     103             :         // Doesn't match, isn't checked.
     104           0 :         continue;
     105             :       }
     106             :       // excludeProjects defaults to exclude no projects when missing.
     107           1 :       List<String> excludeProjectsRegexes = ca.getExcludeProjectsRegexes();
     108           1 :       if (!excludeProjectsRegexes.isEmpty()
     109           1 :           && projectMatchesAnyPattern(project.get(), excludeProjectsRegexes)) {
     110             :         // Matches, isn't checked.
     111           1 :         continue;
     112             :       }
     113           1 :       for (PermissionRule rule : ca.getAccepted()) {
     114           1 :         if ((rule.getAction() == Action.ALLOW)
     115           1 :             && (rule.getGroup() != null)
     116           1 :             && (rule.getGroup().getUUID() != null)) {
     117           1 :           groupIds.add(AccountGroup.uuid(rule.getGroup().getUUID().get()));
     118             :         }
     119           1 :       }
     120           1 :     }
     121             : 
     122           1 :     if (!okGroupIds.isEmpty() && !iUser.getEffectiveGroups().containsAnyOf(okGroupIds)) {
     123           1 :       final StringBuilder msg = new StringBuilder();
     124           1 :       msg.append("No Contributor Agreement on file for user ")
     125           1 :           .append(iUser.getNameEmail())
     126           1 :           .append(" (id=")
     127           1 :           .append(iUser.getAccountId())
     128           1 :           .append(")");
     129             : 
     130           1 :       msg.append(urlFormatter.get().getSettingsUrl("Agreements").orElse(""));
     131           1 :       throw new AuthException(msg.toString());
     132             :     }
     133           1 :   }
     134             : 
     135             :   private boolean projectMatchesAnyPattern(String projectName, List<String> regexes) {
     136           1 :     requireNonNull(regexes);
     137           1 :     checkArgument(!regexes.isEmpty());
     138           1 :     for (String patternString : regexes) {
     139             :       Pattern pattern;
     140             :       try {
     141           1 :         pattern = Pattern.compile(patternString);
     142           0 :       } catch (PatternSyntaxException e) {
     143             :         // Should never happen: Regular expressions validated when reading project.config.
     144           0 :         throw new IllegalStateException(
     145             :             "Invalid matchProjects or excludeProjects clause in project.config", e);
     146           1 :       }
     147           1 :       if (pattern.matcher(projectName).find()) {
     148           1 :         return true;
     149             :       }
     150           1 :     }
     151           1 :     return false;
     152             :   }
     153             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750