Line data Source code
1 : // Copyright (C) 2014 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 : package com.google.gerrit.server.git.validators; 15 : 16 : import static com.google.common.collect.ImmutableList.toImmutableList; 17 : 18 : import com.google.common.collect.ImmutableList; 19 : import com.google.common.collect.ImmutableListMultimap; 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.entities.Account; 22 : import com.google.gerrit.entities.Project; 23 : import com.google.gerrit.entities.RefNames; 24 : import com.google.gerrit.extensions.restapi.AuthException; 25 : import com.google.gerrit.server.IdentifiedUser; 26 : import com.google.gerrit.server.config.AllUsersName; 27 : import com.google.gerrit.server.events.RefReceivedEvent; 28 : import com.google.gerrit.server.permissions.GlobalPermission; 29 : import com.google.gerrit.server.permissions.PermissionBackend; 30 : import com.google.gerrit.server.permissions.PermissionBackendException; 31 : import com.google.gerrit.server.plugincontext.PluginSetContext; 32 : import com.google.gerrit.server.validators.ValidationException; 33 : import com.google.inject.Inject; 34 : import com.google.inject.assistedinject.Assisted; 35 : import java.util.ArrayList; 36 : import java.util.List; 37 : import java.util.Locale; 38 : import org.eclipse.jgit.lib.RefUpdate; 39 : import org.eclipse.jgit.transport.ReceiveCommand; 40 : 41 : /** 42 : * Collection of validation listeners that are called before a ref update is performed with the 43 : * command to be run. This is called from the git push path as well as Gerrit's handlers for 44 : * creating or deleting refs. Calls out to {@link RefOperationValidationListener} provided by 45 : * plugins. 46 : */ 47 : public class RefOperationValidators { 48 59 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 49 : 50 : public interface Factory { 51 : RefOperationValidators create( 52 : Project project, 53 : IdentifiedUser user, 54 : ReceiveCommand cmd, 55 : ImmutableListMultimap<String, String> pushOptions); 56 : } 57 : 58 : public static ReceiveCommand getCommand(RefUpdate update, ReceiveCommand.Type type) { 59 31 : return new ReceiveCommand( 60 31 : update.getExpectedOldObjectId(), update.getNewObjectId(), update.getName(), type); 61 : } 62 : 63 : private final PermissionBackend.WithUser perm; 64 : private final AllUsersName allUsersName; 65 : private final PluginSetContext<RefOperationValidationListener> refOperationValidationListeners; 66 : private final RefReceivedEvent event; 67 : 68 : @Inject 69 : RefOperationValidators( 70 : PermissionBackend permissionBackend, 71 : AllUsersName allUsersName, 72 : PluginSetContext<RefOperationValidationListener> refOperationValidationListeners, 73 : @Assisted Project project, 74 : @Assisted IdentifiedUser user, 75 : @Assisted ReceiveCommand cmd, 76 59 : @Assisted ImmutableListMultimap<String, String> pushOptions) { 77 59 : this.perm = permissionBackend.user(user); 78 59 : this.allUsersName = allUsersName; 79 59 : this.refOperationValidationListeners = refOperationValidationListeners; 80 59 : event = new RefReceivedEvent(); 81 59 : event.command = cmd; 82 59 : event.project = project; 83 59 : event.user = user; 84 59 : event.pushOptions = pushOptions; 85 59 : } 86 : 87 : /** 88 : * Returns informational validation messages and throws a {@link RefOperationValidationException} 89 : * when the first validator fails. Will not process any more validators after the first failure 90 : * was encountered. 91 : */ 92 : public List<ValidationMessage> validateForRefOperation() throws RefOperationValidationException { 93 59 : List<ValidationMessage> messages = new ArrayList<>(); 94 59 : boolean withException = false; 95 : try { 96 59 : messages.addAll( 97 : new DisallowCreationAndDeletionOfGerritMaintainedBranches(perm, allUsersName) 98 59 : .onRefOperation(event)); 99 59 : refOperationValidationListeners.runEach( 100 2 : l -> messages.addAll(l.onRefOperation(event)), ValidationException.class); 101 6 : } catch (ValidationException e) { 102 6 : messages.add(new ValidationMessage(e.getMessage(), true)); 103 6 : withException = true; 104 59 : } 105 : 106 59 : if (withException) { 107 0 : throwException(messages, event); 108 : } 109 : 110 59 : return messages; 111 : } 112 : 113 : private void throwException(List<ValidationMessage> messages, RefReceivedEvent event) 114 : throws RefOperationValidationException { 115 6 : String header = 116 6 : String.format( 117 : "Validation for %s of ref '%s' in project %s failed:", 118 6 : formatReceiveCommandType(event.command.getType()), 119 6 : event.command.getRefName(), 120 6 : event.project.getName()); 121 6 : logger.atSevere().log("%s", header); 122 6 : throw new RefOperationValidationException( 123 6 : header, messages.stream().filter(ValidationMessage::isError).collect(toImmutableList())); 124 : } 125 : 126 : private static String formatReceiveCommandType(ReceiveCommand.Type type) { 127 6 : switch (type) { 128 : case CREATE: 129 4 : return "creation"; 130 : case DELETE: 131 4 : return "deletion"; 132 : case UPDATE: 133 1 : return "update"; 134 : case UPDATE_NONFASTFORWARD: 135 1 : return "non-fast-forward update"; 136 : default: 137 0 : return type.toString().toLowerCase(Locale.US); 138 : } 139 : } 140 : 141 : private static class DisallowCreationAndDeletionOfGerritMaintainedBranches 142 : implements RefOperationValidationListener { 143 : private final PermissionBackend.WithUser perm; 144 : private final AllUsersName allUsersName; 145 : 146 : DisallowCreationAndDeletionOfGerritMaintainedBranches( 147 59 : PermissionBackend.WithUser perm, AllUsersName allUsersName) { 148 59 : this.perm = perm; 149 59 : this.allUsersName = allUsersName; 150 59 : } 151 : 152 : @Override 153 : public List<ValidationMessage> onRefOperation(RefReceivedEvent refEvent) 154 : throws ValidationException { 155 59 : if (refEvent.project.getNameKey().equals(allUsersName)) { 156 6 : if (refEvent.command.getRefName().startsWith(RefNames.REFS_USERS) 157 4 : && !refEvent.command.getRefName().equals(RefNames.REFS_USERS_DEFAULT)) { 158 4 : if (refEvent.command.getType().equals(ReceiveCommand.Type.CREATE)) { 159 : try { 160 1 : if (!perm.test(GlobalPermission.ACCESS_DATABASE)) { 161 1 : throw new ValidationException("Not allowed to create user branch."); 162 : } 163 0 : } catch (PermissionBackendException e) { 164 0 : throw new ValidationException("Not allowed to create user branch.", e); 165 1 : } 166 1 : if (Account.Id.fromRef(refEvent.command.getRefName()) == null) { 167 1 : throw new ValidationException( 168 1 : String.format( 169 : "Not allowed to create non-user branch under %s.", RefNames.REFS_USERS)); 170 : } 171 4 : } else if (refEvent.command.getType().equals(ReceiveCommand.Type.DELETE)) { 172 : try { 173 1 : perm.check(GlobalPermission.ACCESS_DATABASE); 174 2 : } catch (AuthException | PermissionBackendException e) { 175 2 : throw new ValidationException("Not allowed to delete user branch.", e); 176 1 : } 177 : } 178 : } 179 : 180 6 : if (RefNames.isGroupRef(refEvent.command.getRefName())) { 181 2 : if (refEvent.command.getType().equals(ReceiveCommand.Type.CREATE)) { 182 1 : throw new ValidationException("Not allowed to create group branch."); 183 2 : } else if (refEvent.command.getType().equals(ReceiveCommand.Type.DELETE)) { 184 2 : throw new ValidationException("Not allowed to delete group branch."); 185 : } 186 : } 187 : } 188 59 : return ImmutableList.of(); 189 : } 190 : } 191 : }