Line data Source code
1 : // Copyright (C) 2022 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.restapi.change;
16 :
17 : import static com.google.gerrit.server.project.ProjectCache.illegalState;
18 : import static java.util.Objects.requireNonNull;
19 :
20 : import com.google.common.flogger.FluentLogger;
21 : import com.google.gerrit.entities.Account;
22 : import com.google.gerrit.entities.Change;
23 : import com.google.gerrit.entities.LabelTypes;
24 : import com.google.gerrit.entities.PatchSet;
25 : import com.google.gerrit.entities.PatchSetApproval;
26 : import com.google.gerrit.entities.Project;
27 : import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
28 : import com.google.gerrit.extensions.restapi.AuthException;
29 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
30 : import com.google.gerrit.server.ChangeMessagesUtil;
31 : import com.google.gerrit.server.CurrentUser;
32 : import com.google.gerrit.server.PatchSetUtil;
33 : import com.google.gerrit.server.account.AccountState;
34 : import com.google.gerrit.server.approval.ApprovalsUtil;
35 : import com.google.gerrit.server.change.NotifyResolver;
36 : import com.google.gerrit.server.extensions.events.VoteDeleted;
37 : import com.google.gerrit.server.mail.send.DeleteVoteSender;
38 : import com.google.gerrit.server.mail.send.MessageIdGenerator;
39 : import com.google.gerrit.server.mail.send.ReplyToChangeSender;
40 : import com.google.gerrit.server.permissions.PermissionBackendException;
41 : import com.google.gerrit.server.project.ProjectCache;
42 : import com.google.gerrit.server.project.RemoveReviewerControl;
43 : import com.google.gerrit.server.update.BatchUpdateOp;
44 : import com.google.gerrit.server.update.ChangeContext;
45 : import com.google.gerrit.server.update.PostUpdateContext;
46 : import com.google.gerrit.server.util.AccountTemplateUtil;
47 : import com.google.gerrit.server.util.LabelVote;
48 : import com.google.inject.Inject;
49 : import com.google.inject.assistedinject.Assisted;
50 : import java.io.IOException;
51 : import java.util.HashMap;
52 : import java.util.Map;
53 :
54 : /** Updates the storage to delete vote(s). */
55 : public class DeleteVoteOp implements BatchUpdateOp {
56 9 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
57 :
58 : /** Factory to create {@link DeleteVoteOp} instances. */
59 : public interface Factory {
60 : DeleteVoteOp create(
61 : Project.NameKey projectState,
62 : AccountState reviewerToDeleteVoteFor,
63 : String label,
64 : DeleteVoteInput input,
65 : boolean enforcePermissions);
66 : }
67 :
68 : private final Project.NameKey projectName;
69 : private final AccountState reviewerToDeleteVoteFor;
70 :
71 : private final ProjectCache projectCache;
72 : private final ApprovalsUtil approvalsUtil;
73 : private final PatchSetUtil psUtil;
74 : private final ChangeMessagesUtil cmUtil;
75 : private final VoteDeleted voteDeleted;
76 : private final DeleteVoteSender.Factory deleteVoteSenderFactory;
77 :
78 : private final RemoveReviewerControl removeReviewerControl;
79 : private final MessageIdGenerator messageIdGenerator;
80 :
81 : private final String label;
82 : private final DeleteVoteInput input;
83 : private final boolean enforcePermissions;
84 :
85 : private String mailMessage;
86 : private Change change;
87 : private PatchSet ps;
88 9 : private Map<String, Short> newApprovals = new HashMap<>();
89 9 : private Map<String, Short> oldApprovals = new HashMap<>();
90 :
91 : @Inject
92 : public DeleteVoteOp(
93 : ProjectCache projectCache,
94 : ApprovalsUtil approvalsUtil,
95 : PatchSetUtil psUtil,
96 : ChangeMessagesUtil cmUtil,
97 : VoteDeleted voteDeleted,
98 : DeleteVoteSender.Factory deleteVoteSenderFactory,
99 : RemoveReviewerControl removeReviewerControl,
100 : MessageIdGenerator messageIdGenerator,
101 : @Assisted Project.NameKey projectName,
102 : @Assisted AccountState reviewerToDeleteVoteFor,
103 : @Assisted String label,
104 : @Assisted DeleteVoteInput input,
105 9 : @Assisted boolean enforcePermissions) {
106 9 : this.projectCache = projectCache;
107 9 : this.approvalsUtil = approvalsUtil;
108 9 : this.psUtil = psUtil;
109 9 : this.cmUtil = cmUtil;
110 9 : this.voteDeleted = voteDeleted;
111 9 : this.deleteVoteSenderFactory = deleteVoteSenderFactory;
112 9 : this.removeReviewerControl = removeReviewerControl;
113 9 : this.messageIdGenerator = messageIdGenerator;
114 :
115 9 : this.projectName = projectName;
116 9 : this.reviewerToDeleteVoteFor = reviewerToDeleteVoteFor;
117 9 : this.label = label;
118 9 : this.input = input;
119 9 : this.enforcePermissions = enforcePermissions;
120 9 : }
121 :
122 : @Override
123 : public boolean updateChange(ChangeContext ctx)
124 : throws AuthException, ResourceNotFoundException, IOException, PermissionBackendException {
125 9 : change = ctx.getChange();
126 9 : PatchSet.Id psId = change.currentPatchSetId();
127 9 : ps = psUtil.current(ctx.getNotes());
128 :
129 9 : boolean found = false;
130 9 : LabelTypes labelTypes =
131 : projectCache
132 9 : .get(projectName)
133 9 : .orElseThrow(illegalState(projectName))
134 9 : .getLabelTypes(ctx.getNotes());
135 :
136 9 : Account.Id accountId = reviewerToDeleteVoteFor.account().id();
137 :
138 9 : for (PatchSetApproval a : approvalsUtil.byPatchSetUser(ctx.getNotes(), psId, accountId)) {
139 9 : if (!labelTypes.byLabel(a.labelId()).isPresent()) {
140 0 : continue; // Ignore undefined labels.
141 9 : } else if (!a.label().equals(label)) {
142 : // Populate map for non-matching labels, needed by VoteDeleted.
143 1 : newApprovals.put(a.label(), a.value());
144 1 : continue;
145 9 : } else if (enforcePermissions) {
146 : // For regular users, check if they are allowed to remove the vote.
147 : try {
148 9 : removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), a);
149 1 : } catch (AuthException e) {
150 1 : throw new AuthException("delete vote not permitted", e);
151 9 : }
152 : }
153 : // Set the approval to 0 if vote is being removed.
154 9 : newApprovals.put(a.label(), (short) 0);
155 9 : found = true;
156 :
157 : // Set old value, as required by VoteDeleted.
158 9 : oldApprovals.put(a.label(), a.value());
159 9 : break;
160 : }
161 9 : if (!found) {
162 0 : throw new ResourceNotFoundException();
163 : }
164 :
165 9 : ctx.getUpdate(psId).removeApprovalFor(accountId, label);
166 :
167 9 : StringBuilder msg = new StringBuilder();
168 9 : msg.append("Removed ");
169 9 : LabelVote.appendTo(msg, label, requireNonNull(oldApprovals.get(label)));
170 9 : msg.append(" by ").append(AccountTemplateUtil.getAccountTemplate(accountId));
171 9 : if (input.reason != null) {
172 1 : msg.append("\n\n" + input.reason);
173 : }
174 9 : msg.append("\n");
175 9 : mailMessage = cmUtil.setChangeMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_VOTE);
176 9 : return true;
177 : }
178 :
179 : @Override
180 : public void postUpdate(PostUpdateContext ctx) {
181 9 : if (mailMessage == null) {
182 0 : return;
183 : }
184 :
185 9 : CurrentUser user = ctx.getUser();
186 : try {
187 9 : NotifyResolver.Result notify = ctx.getNotify(change.getId());
188 9 : if (notify.shouldNotify()) {
189 9 : ReplyToChangeSender emailSender =
190 9 : deleteVoteSenderFactory.create(ctx.getProject(), change.getId());
191 9 : if (user.isIdentifiedUser()) {
192 9 : emailSender.setFrom(user.getAccountId());
193 : }
194 9 : emailSender.setChangeMessage(mailMessage, ctx.getWhen());
195 9 : emailSender.setNotify(notify);
196 9 : emailSender.setMessageId(
197 9 : messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), change.currentPatchSetId()));
198 9 : emailSender.send();
199 : }
200 0 : } catch (Exception e) {
201 0 : logger.atSevere().withCause(e).log("Cannot email update for change %s", change.getId());
202 9 : }
203 9 : voteDeleted.fire(
204 9 : ctx.getChangeData(change),
205 : ps,
206 : reviewerToDeleteVoteFor,
207 : newApprovals,
208 : oldApprovals,
209 : input.notify,
210 : mailMessage,
211 9 : user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null,
212 9 : ctx.getWhen());
213 9 : }
214 : }
|