summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/git/validators/MergeValidators.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/git/validators/MergeValidators.java')
-rw-r--r--java/com/google/gerrit/server/git/validators/MergeValidators.java354
1 files changed, 354 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
new file mode 100644
index 0000000000..0422c51f1b
--- /dev/null
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -0,0 +1,354 @@
+// Copyright (C) 2013 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.git.validators;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountProperties;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class MergeValidators {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final PluginSetContext<MergeValidationListener> mergeValidationListeners;
+ private final ProjectConfigValidator.Factory projectConfigValidatorFactory;
+ private final AccountMergeValidator.Factory accountValidatorFactory;
+ private final GroupMergeValidator.Factory groupValidatorFactory;
+
+ public interface Factory {
+ MergeValidators create();
+ }
+
+ @Inject
+ MergeValidators(
+ PluginSetContext<MergeValidationListener> mergeValidationListeners,
+ ProjectConfigValidator.Factory projectConfigValidatorFactory,
+ AccountMergeValidator.Factory accountValidatorFactory,
+ GroupMergeValidator.Factory groupValidatorFactory) {
+ this.mergeValidationListeners = mergeValidationListeners;
+ this.projectConfigValidatorFactory = projectConfigValidatorFactory;
+ this.accountValidatorFactory = accountValidatorFactory;
+ this.groupValidatorFactory = groupValidatorFactory;
+ }
+
+ public void validatePreMerge(
+ Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ List<MergeValidationListener> validators =
+ ImmutableList.of(
+ new PluginMergeValidationListener(mergeValidationListeners),
+ projectConfigValidatorFactory.create(),
+ accountValidatorFactory.create(),
+ groupValidatorFactory.create());
+
+ for (MergeValidationListener validator : validators) {
+ validator.onPreMerge(repo, commit, destProject, destBranch, patchSetId, caller);
+ }
+ }
+
+ public static class ProjectConfigValidator implements MergeValidationListener {
+ private static final String INVALID_CONFIG =
+ "Change contains an invalid project configuration.";
+ private static final String PARENT_NOT_FOUND =
+ "Change contains an invalid project configuration:\nParent project does not exist.";
+ private static final String PLUGIN_VALUE_NOT_EDITABLE =
+ "Change contains an invalid project configuration:\n"
+ + "One of the plugin configuration parameters is not editable.";
+ private static final String PLUGIN_VALUE_NOT_PERMITTED =
+ "Change contains an invalid project configuration:\n"
+ + "One of the plugin configuration parameters has a value that is not"
+ + " permitted.";
+ private static final String ROOT_NO_PARENT =
+ "Change contains an invalid project configuration:\n"
+ + "The root project cannot have a parent.";
+ private static final String SET_BY_ADMIN =
+ "Change contains a project configuration that changes the parent"
+ + " project.\n"
+ + "The change must be submitted by a Gerrit administrator.";
+ private static final String SET_BY_OWNER =
+ "Change contains a project configuration that changes the parent"
+ + " project.\n"
+ + "The change must be submitted by a Gerrit administrator or the project owner.";
+
+ private final AllProjectsName allProjectsName;
+ private final AllUsersName allUsersName;
+ private final ProjectCache projectCache;
+ private final PermissionBackend permissionBackend;
+ private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+ private final boolean allowProjectOwnersToChangeParent;
+
+ public interface Factory {
+ ProjectConfigValidator create();
+ }
+
+ @Inject
+ public ProjectConfigValidator(
+ AllProjectsName allProjectsName,
+ AllUsersName allUsersName,
+ ProjectCache projectCache,
+ PermissionBackend permissionBackend,
+ DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+ @GerritServerConfig Config config) {
+ this.allProjectsName = allProjectsName;
+ this.allUsersName = allUsersName;
+ this.projectCache = projectCache;
+ this.permissionBackend = permissionBackend;
+ this.pluginConfigEntries = pluginConfigEntries;
+ this.allowProjectOwnersToChangeParent =
+ config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
+ }
+
+ @Override
+ public void onPreMerge(
+ final Repository repo,
+ final CodeReviewCommit commit,
+ final ProjectState destProject,
+ final Branch.NameKey destBranch,
+ final PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
+ final Project.NameKey newParent;
+ try {
+ ProjectConfig cfg = new ProjectConfig(destProject.getNameKey());
+ cfg.load(destProject.getNameKey(), repo, commit);
+ newParent = cfg.getProject().getParent(allProjectsName);
+ final Project.NameKey oldParent = destProject.getProject().getParent(allProjectsName);
+ if (oldParent == null) {
+ // update of the 'All-Projects' project
+ if (newParent != null) {
+ throw new MergeValidationException(ROOT_NO_PARENT);
+ }
+ } else {
+ if (!oldParent.equals(newParent)) {
+ if (!allowProjectOwnersToChangeParent) {
+ try {
+ permissionBackend.user(caller).check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (AuthException e) {
+ throw new MergeValidationException(SET_BY_ADMIN);
+ } catch (PermissionBackendException e) {
+ logger.atWarning().withCause(e).log("Cannot check ADMINISTRATE_SERVER");
+ throw new MergeValidationException("validation unavailable");
+ }
+ } else {
+ try {
+ permissionBackend
+ .user(caller)
+ .project(destProject.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ throw new MergeValidationException(SET_BY_OWNER);
+ } catch (PermissionBackendException e) {
+ logger.atWarning().withCause(e).log("Cannot check WRITE_CONFIG");
+ throw new MergeValidationException("validation unavailable");
+ }
+ }
+ if (allUsersName.equals(destProject.getNameKey())
+ && !allProjectsName.equals(newParent)) {
+ throw new MergeValidationException(
+ String.format(
+ " %s must inherit from %s", allUsersName.get(), allProjectsName.get()));
+ }
+ if (projectCache.get(newParent) == null) {
+ throw new MergeValidationException(PARENT_NOT_FOUND);
+ }
+ }
+ }
+
+ for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
+ PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
+ ProjectConfigEntry configEntry = e.getProvider().get();
+
+ String value = pluginCfg.getString(e.getExportName());
+ String oldValue =
+ destProject
+ .getConfig()
+ .getPluginConfig(e.getPluginName())
+ .getString(e.getExportName());
+
+ if ((value == null ? oldValue != null : !value.equals(oldValue))
+ && !configEntry.isEditable(destProject)) {
+ throw new MergeValidationException(PLUGIN_VALUE_NOT_EDITABLE);
+ }
+
+ if (ProjectConfigEntryType.LIST.equals(configEntry.getType())
+ && value != null
+ && !configEntry.getPermittedValues().contains(value)) {
+ throw new MergeValidationException(PLUGIN_VALUE_NOT_PERMITTED);
+ }
+ }
+ } catch (ConfigInvalidException | IOException e) {
+ throw new MergeValidationException(INVALID_CONFIG);
+ }
+ }
+ }
+ }
+
+ /** Execute merge validation plug-ins */
+ public static class PluginMergeValidationListener implements MergeValidationListener {
+ private final PluginSetContext<MergeValidationListener> mergeValidationListeners;
+
+ public PluginMergeValidationListener(
+ PluginSetContext<MergeValidationListener> mergeValidationListeners) {
+ this.mergeValidationListeners = mergeValidationListeners;
+ }
+
+ @Override
+ public void onPreMerge(
+ Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ mergeValidationListeners.runEach(
+ l -> l.onPreMerge(repo, commit, destProject, destBranch, patchSetId, caller),
+ MergeValidationException.class);
+ }
+ }
+
+ public static class AccountMergeValidator implements MergeValidationListener {
+ public interface Factory {
+ AccountMergeValidator create();
+ }
+
+ private final Provider<ReviewDb> dbProvider;
+ private final AllUsersName allUsersName;
+ private final ChangeData.Factory changeDataFactory;
+ private final AccountValidator accountValidator;
+
+ @Inject
+ public AccountMergeValidator(
+ Provider<ReviewDb> dbProvider,
+ AllUsersName allUsersName,
+ ChangeData.Factory changeDataFactory,
+ AccountValidator accountValidator) {
+ this.dbProvider = dbProvider;
+ this.allUsersName = allUsersName;
+ this.changeDataFactory = changeDataFactory;
+ this.accountValidator = accountValidator;
+ }
+
+ @Override
+ public void onPreMerge(
+ Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ Account.Id accountId = Account.Id.fromRef(destBranch.get());
+ if (!allUsersName.equals(destProject.getNameKey()) || accountId == null) {
+ return;
+ }
+
+ ChangeData cd =
+ changeDataFactory.create(
+ dbProvider.get(), destProject.getProject().getNameKey(), patchSetId.getParentKey());
+ try {
+ if (!cd.currentFilePaths().contains(AccountProperties.ACCOUNT_CONFIG)) {
+ return;
+ }
+ } catch (IOException | OrmException e) {
+ logger.atSevere().withCause(e).log("Cannot validate account update");
+ throw new MergeValidationException("account validation unavailable");
+ }
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ List<String> errorMessages = accountValidator.validate(accountId, repo, rw, null, commit);
+ if (!errorMessages.isEmpty()) {
+ throw new MergeValidationException(
+ "invalid account configuration: " + Joiner.on("; ").join(errorMessages));
+ }
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log("Cannot validate account update");
+ throw new MergeValidationException("account validation unavailable");
+ }
+ }
+ }
+
+ public static class GroupMergeValidator implements MergeValidationListener {
+ public interface Factory {
+ GroupMergeValidator create();
+ }
+
+ private final AllUsersName allUsersName;
+
+ @Inject
+ public GroupMergeValidator(AllUsersName allUsersName) {
+ this.allUsersName = allUsersName;
+ }
+
+ @Override
+ public void onPreMerge(
+ Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ // Groups are stored inside the 'All-Users' repository.
+ if (!allUsersName.equals(destProject.getNameKey())
+ || !RefNames.isGroupRef(destBranch.get())) {
+ return;
+ }
+
+ throw new MergeValidationException("group update not allowed");
+ }
+ }
+}