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.group.db;
16 :
17 : import com.google.auto.value.AutoValue;
18 : import com.google.common.annotations.VisibleForTesting;
19 : import com.google.common.base.Throwables;
20 : import com.google.common.collect.ImmutableSet;
21 : import com.google.common.collect.Sets;
22 : import com.google.common.flogger.FluentLogger;
23 : import com.google.gerrit.common.Nullable;
24 : import com.google.gerrit.entities.Account;
25 : import com.google.gerrit.entities.AccountGroup;
26 : import com.google.gerrit.entities.InternalGroup;
27 : import com.google.gerrit.entities.Project;
28 : import com.google.gerrit.exceptions.DuplicateKeyException;
29 : import com.google.gerrit.exceptions.NoSuchGroupException;
30 : import com.google.gerrit.git.RefUpdateUtil;
31 : import com.google.gerrit.server.GerritPersonIdent;
32 : import com.google.gerrit.server.IdentifiedUser;
33 : import com.google.gerrit.server.account.AccountCache;
34 : import com.google.gerrit.server.account.GroupBackend;
35 : import com.google.gerrit.server.account.GroupCache;
36 : import com.google.gerrit.server.account.GroupIncludeCache;
37 : import com.google.gerrit.server.config.AllUsersName;
38 : import com.google.gerrit.server.config.GerritServerId;
39 : import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
40 : import com.google.gerrit.server.git.GitRepositoryManager;
41 : import com.google.gerrit.server.git.meta.MetaDataUpdate;
42 : import com.google.gerrit.server.group.GroupAuditService;
43 : import com.google.gerrit.server.index.group.GroupIndexer;
44 : import com.google.gerrit.server.logging.Metadata;
45 : import com.google.gerrit.server.logging.TraceContext;
46 : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
47 : import com.google.gerrit.server.update.RetryHelper;
48 : import com.google.gerrit.server.util.time.TimeUtil;
49 : import com.google.inject.Provider;
50 : import com.google.inject.assistedinject.Assisted;
51 : import com.google.inject.assistedinject.AssistedInject;
52 : import java.io.IOException;
53 : import java.time.Instant;
54 : import java.util.Objects;
55 : import java.util.Optional;
56 : import java.util.Set;
57 : import java.util.concurrent.Future;
58 : import java.util.concurrent.TimeUnit;
59 : import org.eclipse.jgit.errors.ConfigInvalidException;
60 : import org.eclipse.jgit.lib.BatchRefUpdate;
61 : import org.eclipse.jgit.lib.PersonIdent;
62 : import org.eclipse.jgit.lib.Repository;
63 :
64 : /**
65 : * A database accessor for write calls related to groups.
66 : *
67 : * <p>All calls which write group related details to the database are gathered here. Other classes
68 : * should always use this class instead of accessing the database directly. There are a few
69 : * exceptions though: schema classes, wrapper classes, and classes executed during init. The latter
70 : * ones should use {@link com.google.gerrit.pgm.init.GroupsOnInit} instead.
71 : *
72 : * <p>If not explicitly stated, all methods of this class refer to <em>internal</em> groups.
73 : */
74 : public class GroupsUpdate {
75 : public interface Factory {
76 : /**
77 : * Creates a {@link GroupsUpdate} which uses the identity of the specified user to mark database
78 : * modifications executed by it. For NoteDb, this identity is used as author and committer for
79 : * all related commits.
80 : *
81 : * <p><strong>Note</strong>: Please use this method with care and consider using the {@link
82 : * com.google.gerrit.server.UserInitiated} annotation on the provider of a {@link GroupsUpdate}
83 : * instead.
84 : *
85 : * @param currentUser the user to which modifications should be attributed
86 : */
87 : GroupsUpdate create(IdentifiedUser currentUser);
88 :
89 : /**
90 : * Creates a {@link GroupsUpdate} which uses the server identity to mark database modifications
91 : * executed by it. For NoteDb, this identity is used as author and committer for all related
92 : * commits.
93 : *
94 : * <p><strong>Note</strong>: Please use this method with care and consider using the {@link
95 : * com.google.gerrit.server.ServerInitiated} annotation on the provider of a {@link
96 : * GroupsUpdate} instead.
97 : */
98 : GroupsUpdate createWithServerIdent();
99 : }
100 :
101 151 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
102 :
103 : private final GitRepositoryManager repoManager;
104 : private final AllUsersName allUsersName;
105 : private final GroupCache groupCache;
106 : private final GroupIncludeCache groupIncludeCache;
107 : private final Provider<GroupIndexer> indexer;
108 : private final GroupAuditService groupAuditService;
109 : private final RenameGroupOp.Factory renameGroupOpFactory;
110 : private final Optional<IdentifiedUser> currentUser;
111 : private final AuditLogFormatter auditLogFormatter;
112 : private final PersonIdent authorIdent;
113 : private final MetaDataUpdateFactory metaDataUpdateFactory;
114 : private final GitReferenceUpdated gitRefUpdated;
115 : private final RetryHelper retryHelper;
116 :
117 : @AssistedInject
118 : @SuppressWarnings("BindingAnnotationWithoutInject")
119 : GroupsUpdate(
120 : GitRepositoryManager repoManager,
121 : AllUsersName allUsersName,
122 : GroupBackend groupBackend,
123 : GroupCache groupCache,
124 : GroupIncludeCache groupIncludeCache,
125 : Provider<GroupIndexer> indexer,
126 : GroupAuditService auditService,
127 : AccountCache accountCache,
128 : RenameGroupOp.Factory renameGroupOpFactory,
129 : @GerritServerId String serverId,
130 : @GerritPersonIdent PersonIdent serverIdent,
131 : MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
132 : GitReferenceUpdated gitRefUpdated,
133 : RetryHelper retryHelper) {
134 140 : this(
135 : repoManager,
136 : allUsersName,
137 : groupBackend,
138 : groupCache,
139 : groupIncludeCache,
140 : indexer,
141 : auditService,
142 : accountCache,
143 : renameGroupOpFactory,
144 : serverId,
145 : serverIdent,
146 : metaDataUpdateInternalFactory,
147 : gitRefUpdated,
148 : retryHelper,
149 140 : Optional.empty());
150 140 : }
151 :
152 : @AssistedInject
153 : @SuppressWarnings("BindingAnnotationWithoutInject")
154 : GroupsUpdate(
155 : GitRepositoryManager repoManager,
156 : AllUsersName allUsersName,
157 : GroupBackend groupBackend,
158 : GroupCache groupCache,
159 : GroupIncludeCache groupIncludeCache,
160 : Provider<GroupIndexer> indexer,
161 : GroupAuditService auditService,
162 : AccountCache accountCache,
163 : RenameGroupOp.Factory renameGroupOpFactory,
164 : @GerritServerId String serverId,
165 : @GerritPersonIdent PersonIdent serverIdent,
166 : MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
167 : GitReferenceUpdated gitRefUpdated,
168 : RetryHelper retryHelper,
169 : @Assisted IdentifiedUser currentUser) {
170 35 : this(
171 : repoManager,
172 : allUsersName,
173 : groupBackend,
174 : groupCache,
175 : groupIncludeCache,
176 : indexer,
177 : auditService,
178 : accountCache,
179 : renameGroupOpFactory,
180 : serverId,
181 : serverIdent,
182 : metaDataUpdateInternalFactory,
183 : gitRefUpdated,
184 : retryHelper,
185 35 : Optional.of(currentUser));
186 35 : }
187 :
188 : @SuppressWarnings("BindingAnnotationWithoutInject")
189 : private GroupsUpdate(
190 : GitRepositoryManager repoManager,
191 : AllUsersName allUsersName,
192 : GroupBackend groupBackend,
193 : GroupCache groupCache,
194 : GroupIncludeCache groupIncludeCache,
195 : Provider<GroupIndexer> indexer,
196 : GroupAuditService auditService,
197 : AccountCache accountCache,
198 : RenameGroupOp.Factory renameGroupOpFactory,
199 : @GerritServerId String serverId,
200 : @GerritPersonIdent PersonIdent serverIdent,
201 : MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
202 : GitReferenceUpdated gitRefUpdated,
203 : RetryHelper retryHelper,
204 151 : Optional<IdentifiedUser> currentUser) {
205 151 : this.repoManager = repoManager;
206 151 : this.allUsersName = allUsersName;
207 151 : this.groupCache = groupCache;
208 151 : this.groupIncludeCache = groupIncludeCache;
209 151 : this.indexer = indexer;
210 151 : this.groupAuditService = auditService;
211 151 : this.renameGroupOpFactory = renameGroupOpFactory;
212 151 : this.gitRefUpdated = gitRefUpdated;
213 151 : this.retryHelper = retryHelper;
214 151 : this.currentUser = currentUser;
215 :
216 151 : auditLogFormatter = AuditLogFormatter.createBackedBy(accountCache, groupBackend, serverId);
217 151 : metaDataUpdateFactory =
218 151 : getMetaDataUpdateFactory(
219 : metaDataUpdateInternalFactory, currentUser, serverIdent, auditLogFormatter);
220 151 : authorIdent = getAuthorIdent(serverIdent, currentUser);
221 151 : }
222 :
223 : private static MetaDataUpdateFactory getMetaDataUpdateFactory(
224 : MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
225 : Optional<IdentifiedUser> currentUser,
226 : PersonIdent serverIdent,
227 : AuditLogFormatter auditLogFormatter) {
228 151 : return (projectName, repository, batchRefUpdate) -> {
229 151 : MetaDataUpdate metaDataUpdate =
230 151 : metaDataUpdateInternalFactory.create(projectName, repository, batchRefUpdate);
231 151 : metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
232 : PersonIdent authorIdent;
233 151 : if (currentUser.isPresent()) {
234 35 : metaDataUpdate.setAuthor(currentUser.get());
235 35 : authorIdent =
236 35 : auditLogFormatter.getParsableAuthorIdent(currentUser.get().getAccount(), serverIdent);
237 : } else {
238 140 : authorIdent = serverIdent;
239 : }
240 151 : metaDataUpdate.getCommitBuilder().setAuthor(authorIdent);
241 151 : return metaDataUpdate;
242 : };
243 : }
244 :
245 : private static PersonIdent getAuthorIdent(
246 : PersonIdent serverIdent, Optional<IdentifiedUser> currentUser) {
247 151 : return currentUser.map(user -> createPersonIdent(serverIdent, user)).orElse(serverIdent);
248 : }
249 :
250 : private static PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
251 35 : return user.newCommitterIdent(ident);
252 : }
253 :
254 : /**
255 : * Creates the specified group for the specified members (accounts).
256 : *
257 : * @param groupCreation an {@link InternalGroupCreation} which specifies all mandatory properties
258 : * of the group
259 : * @param groupDelta a {@link GroupDelta} which specifies optional properties of the group. If
260 : * this {@link GroupDelta} updates a property which was already specified by the {@link
261 : * InternalGroupCreation}, the value of this {@link GroupDelta} wins.
262 : * @throws DuplicateKeyException if a group with the chosen name already exists
263 : * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb
264 : * @return the created {@link InternalGroup}
265 : */
266 : public InternalGroup createGroup(InternalGroupCreation groupCreation, GroupDelta groupDelta)
267 : throws DuplicateKeyException, IOException, ConfigInvalidException {
268 37 : try (TraceTimer ignored =
269 37 : TraceContext.newTimer(
270 : "Creating group",
271 37 : Metadata.builder()
272 37 : .groupName(groupDelta.getName().orElseGet(groupCreation::getNameKey).get())
273 37 : .build())) {
274 37 : InternalGroup createdGroup = createGroupInNoteDbWithRetry(groupCreation, groupDelta);
275 37 : evictCachesOnGroupCreation(createdGroup);
276 37 : dispatchAuditEventsOnGroupCreation(createdGroup);
277 37 : return createdGroup;
278 : }
279 : }
280 :
281 : /**
282 : * Updates the specified group.
283 : *
284 : * @param groupUuid the UUID of the group to update
285 : * @param groupDelta a {@link GroupDelta} which indicates the desired updates on the group
286 : * @throws DuplicateKeyException if the new name of the group is used by another group
287 : * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb
288 : * @throws NoSuchGroupException if the specified group doesn't exist
289 : */
290 : public void updateGroup(AccountGroup.UUID groupUuid, GroupDelta groupDelta)
291 : throws DuplicateKeyException, IOException, NoSuchGroupException, ConfigInvalidException {
292 151 : try (TraceTimer ignored =
293 151 : TraceContext.newTimer(
294 151 : "Updating group", Metadata.builder().groupUuid(groupUuid.get()).build())) {
295 151 : Optional<Instant> updatedOn = groupDelta.getUpdatedOn();
296 151 : if (!updatedOn.isPresent()) {
297 151 : updatedOn = Optional.of(TimeUtil.now());
298 151 : groupDelta = groupDelta.toBuilder().setUpdatedOn(updatedOn.get()).build();
299 : }
300 :
301 151 : UpdateResult result = updateGroupInNoteDbWithRetry(groupUuid, groupDelta);
302 151 : updateNameInProjectConfigsIfNecessary(result);
303 151 : evictCachesOnGroupUpdate(result);
304 151 : dispatchAuditEventsOnGroupUpdate(result, updatedOn.get());
305 : }
306 151 : }
307 :
308 : private InternalGroup createGroupInNoteDbWithRetry(
309 : InternalGroupCreation groupCreation, GroupDelta groupDelta)
310 : throws IOException, ConfigInvalidException, DuplicateKeyException {
311 : try {
312 37 : return retryHelper
313 37 : .groupUpdate("createGroup", () -> createGroupInNoteDb(groupCreation, groupDelta))
314 37 : .call();
315 1 : } catch (Exception e) {
316 0 : Throwables.throwIfUnchecked(e);
317 0 : Throwables.throwIfInstanceOf(e, IOException.class);
318 0 : Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
319 0 : Throwables.throwIfInstanceOf(e, DuplicateKeyException.class);
320 0 : throw new IOException(e);
321 : }
322 : }
323 :
324 : @VisibleForTesting
325 : public InternalGroup createGroupInNoteDb(
326 : InternalGroupCreation groupCreation, GroupDelta groupDelta)
327 : throws IOException, ConfigInvalidException, DuplicateKeyException {
328 37 : try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
329 37 : AccountGroup.NameKey groupName = groupDelta.getName().orElseGet(groupCreation::getNameKey);
330 37 : GroupNameNotes groupNameNotes =
331 37 : GroupNameNotes.forNewGroup(
332 37 : allUsersName, allUsersRepo, groupCreation.getGroupUUID(), groupName);
333 :
334 37 : GroupConfig groupConfig =
335 37 : GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
336 37 : groupConfig.setGroupDelta(groupDelta, auditLogFormatter);
337 :
338 37 : commit(allUsersRepo, groupConfig, groupNameNotes);
339 :
340 37 : return groupConfig
341 37 : .getLoadedGroup()
342 37 : .orElseThrow(
343 0 : () -> new IllegalStateException("Created group wasn't automatically loaded"));
344 : }
345 : }
346 :
347 : private UpdateResult updateGroupInNoteDbWithRetry(
348 : AccountGroup.UUID groupUuid, GroupDelta groupDelta)
349 : throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException {
350 : try {
351 151 : return retryHelper
352 151 : .groupUpdate("updateGroup", () -> updateGroupInNoteDb(groupUuid, groupDelta))
353 151 : .call();
354 1 : } catch (Exception e) {
355 1 : Throwables.throwIfUnchecked(e);
356 1 : Throwables.throwIfInstanceOf(e, IOException.class);
357 1 : Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
358 1 : Throwables.throwIfInstanceOf(e, DuplicateKeyException.class);
359 0 : Throwables.throwIfInstanceOf(e, NoSuchGroupException.class);
360 0 : throw new IOException(e);
361 : }
362 : }
363 :
364 : @VisibleForTesting
365 : public UpdateResult updateGroupInNoteDb(AccountGroup.UUID groupUuid, GroupDelta groupDelta)
366 : throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException {
367 151 : try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
368 151 : GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid);
369 151 : groupConfig.setGroupDelta(groupDelta, auditLogFormatter);
370 151 : if (!groupConfig.getLoadedGroup().isPresent()) {
371 1 : throw new NoSuchGroupException(groupUuid);
372 : }
373 :
374 151 : InternalGroup originalGroup = groupConfig.getLoadedGroup().get();
375 151 : GroupNameNotes groupNameNotes = null;
376 151 : if (groupDelta.getName().isPresent()) {
377 3 : AccountGroup.NameKey oldName = originalGroup.getNameKey();
378 3 : AccountGroup.NameKey newName = groupDelta.getName().get();
379 3 : groupNameNotes =
380 3 : GroupNameNotes.forRename(allUsersName, allUsersRepo, groupUuid, oldName, newName);
381 : }
382 :
383 151 : commit(allUsersRepo, groupConfig, groupNameNotes);
384 :
385 151 : InternalGroup updatedGroup =
386 : groupConfig
387 151 : .getLoadedGroup()
388 151 : .orElseThrow(
389 0 : () -> new IllegalStateException("Updated group wasn't automatically loaded"));
390 151 : return getUpdateResult(originalGroup, updatedGroup);
391 : }
392 : }
393 :
394 : private static UpdateResult getUpdateResult(
395 : InternalGroup originalGroup, InternalGroup updatedGroup) {
396 151 : Set<Account.Id> addedMembers =
397 151 : Sets.difference(updatedGroup.getMembers(), originalGroup.getMembers());
398 151 : Set<Account.Id> deletedMembers =
399 151 : Sets.difference(originalGroup.getMembers(), updatedGroup.getMembers());
400 151 : Set<AccountGroup.UUID> addedSubgroups =
401 151 : Sets.difference(updatedGroup.getSubgroups(), originalGroup.getSubgroups());
402 151 : Set<AccountGroup.UUID> deletedSubgroups =
403 151 : Sets.difference(originalGroup.getSubgroups(), updatedGroup.getSubgroups());
404 :
405 : UpdateResult.Builder resultBuilder =
406 151 : UpdateResult.builder()
407 151 : .setGroupUuid(updatedGroup.getGroupUUID())
408 151 : .setGroupId(updatedGroup.getId())
409 151 : .setGroupName(updatedGroup.getNameKey())
410 151 : .setAddedMembers(addedMembers)
411 151 : .setDeletedMembers(deletedMembers)
412 151 : .setAddedSubgroups(addedSubgroups)
413 151 : .setDeletedSubgroups(deletedSubgroups);
414 151 : if (!Objects.equals(originalGroup.getNameKey(), updatedGroup.getNameKey())) {
415 3 : resultBuilder.setPreviousGroupName(originalGroup.getNameKey());
416 : }
417 151 : return resultBuilder.build();
418 : }
419 :
420 : private void commit(
421 : Repository allUsersRepo, GroupConfig groupConfig, @Nullable GroupNameNotes groupNameNotes)
422 : throws IOException {
423 151 : BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
424 151 : try (MetaDataUpdate metaDataUpdate =
425 151 : metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) {
426 151 : groupConfig.commit(metaDataUpdate);
427 : }
428 151 : if (groupNameNotes != null) {
429 : // MetaDataUpdates unfortunately can't be reused. -> Create a new one.
430 37 : try (MetaDataUpdate metaDataUpdate =
431 37 : metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) {
432 37 : groupNameNotes.commit(metaDataUpdate);
433 : }
434 : }
435 :
436 151 : RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo);
437 151 : gitRefUpdated.fire(
438 151 : allUsersName, batchRefUpdate, currentUser.map(user -> user.state()).orElse(null));
439 151 : }
440 :
441 : private void evictCachesOnGroupCreation(InternalGroup createdGroup) {
442 37 : logger.atFine().log("evict caches on creation of group %s", createdGroup.getGroupUUID());
443 : // By UUID is used for the index and hence should be evicted before refreshing the index.
444 37 : groupCache.evict(createdGroup.getGroupUUID());
445 37 : indexer.get().index(createdGroup.getGroupUUID());
446 : // These caches use the result from the index and hence must be evicted after refreshing the
447 : // index.
448 37 : groupCache.evict(createdGroup.getId());
449 37 : groupCache.evict(createdGroup.getNameKey());
450 37 : createdGroup.getMembers().forEach(groupIncludeCache::evictGroupsWithMember);
451 37 : createdGroup.getSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
452 37 : }
453 :
454 : private void evictCachesOnGroupUpdate(UpdateResult result) {
455 151 : logger.atFine().log("evict caches on update of group %s", result.getGroupUuid());
456 : // By UUID is used for the index and hence should be evicted before refreshing the index.
457 151 : groupCache.evict(result.getGroupUuid());
458 151 : indexer.get().index(result.getGroupUuid());
459 : // These caches use the result from the index and hence must be evicted after refreshing the
460 : // index.
461 151 : groupCache.evict(result.getGroupId());
462 151 : groupCache.evict(result.getGroupName());
463 151 : result.getPreviousGroupName().ifPresent(groupCache::evict);
464 :
465 151 : result.getAddedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
466 151 : result.getDeletedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
467 151 : result.getAddedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
468 151 : result.getDeletedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
469 151 : }
470 :
471 : private void updateNameInProjectConfigsIfNecessary(UpdateResult result) {
472 151 : if (result.getPreviousGroupName().isPresent()) {
473 3 : AccountGroup.NameKey previousName = result.getPreviousGroupName().get();
474 :
475 : @SuppressWarnings("unused")
476 3 : Future<?> possiblyIgnoredError =
477 : renameGroupOpFactory
478 3 : .create(
479 : authorIdent,
480 3 : result.getGroupUuid(),
481 3 : previousName.get(),
482 3 : result.getGroupName().get())
483 3 : .start(0, TimeUnit.MILLISECONDS);
484 : }
485 151 : }
486 :
487 : private void dispatchAuditEventsOnGroupCreation(InternalGroup createdGroup) {
488 37 : if (!currentUser.isPresent()) {
489 14 : return;
490 : }
491 :
492 29 : if (!createdGroup.getMembers().isEmpty()) {
493 28 : groupAuditService.dispatchAddMembers(
494 28 : currentUser.get().getAccountId(),
495 28 : createdGroup.getGroupUUID(),
496 28 : createdGroup.getMembers(),
497 28 : createdGroup.getCreatedOn());
498 : }
499 29 : if (!createdGroup.getSubgroups().isEmpty()) {
500 0 : groupAuditService.dispatchAddSubgroups(
501 0 : currentUser.get().getAccountId(),
502 0 : createdGroup.getGroupUUID(),
503 0 : createdGroup.getSubgroups(),
504 0 : createdGroup.getCreatedOn());
505 : }
506 29 : }
507 :
508 : private void dispatchAuditEventsOnGroupUpdate(UpdateResult result, Instant updatedOn) {
509 151 : if (!currentUser.isPresent()) {
510 138 : return;
511 : }
512 :
513 19 : if (!result.getAddedMembers().isEmpty()) {
514 18 : groupAuditService.dispatchAddMembers(
515 18 : currentUser.get().getAccountId(),
516 18 : result.getGroupUuid(),
517 18 : result.getAddedMembers(),
518 : updatedOn);
519 : }
520 19 : if (!result.getDeletedMembers().isEmpty()) {
521 5 : groupAuditService.dispatchDeleteMembers(
522 5 : currentUser.get().getAccountId(),
523 5 : result.getGroupUuid(),
524 5 : result.getDeletedMembers(),
525 : updatedOn);
526 : }
527 19 : if (!result.getAddedSubgroups().isEmpty()) {
528 4 : groupAuditService.dispatchAddSubgroups(
529 4 : currentUser.get().getAccountId(),
530 4 : result.getGroupUuid(),
531 4 : result.getAddedSubgroups(),
532 : updatedOn);
533 : }
534 19 : if (!result.getDeletedSubgroups().isEmpty()) {
535 4 : groupAuditService.dispatchDeleteSubgroups(
536 4 : currentUser.get().getAccountId(),
537 4 : result.getGroupUuid(),
538 4 : result.getDeletedSubgroups(),
539 : updatedOn);
540 : }
541 19 : }
542 :
543 : @FunctionalInterface
544 : private interface MetaDataUpdateFactory {
545 : MetaDataUpdate create(
546 : Project.NameKey projectName, Repository repository, BatchRefUpdate batchRefUpdate)
547 : throws IOException;
548 : }
549 :
550 : @AutoValue
551 151 : abstract static class UpdateResult {
552 : abstract AccountGroup.UUID getGroupUuid();
553 :
554 : abstract AccountGroup.Id getGroupId();
555 :
556 : abstract AccountGroup.NameKey getGroupName();
557 :
558 : abstract Optional<AccountGroup.NameKey> getPreviousGroupName();
559 :
560 : abstract ImmutableSet<Account.Id> getAddedMembers();
561 :
562 : abstract ImmutableSet<Account.Id> getDeletedMembers();
563 :
564 : abstract ImmutableSet<AccountGroup.UUID> getAddedSubgroups();
565 :
566 : abstract ImmutableSet<AccountGroup.UUID> getDeletedSubgroups();
567 :
568 : static Builder builder() {
569 151 : return new AutoValue_GroupsUpdate_UpdateResult.Builder();
570 : }
571 :
572 : @AutoValue.Builder
573 151 : abstract static class Builder {
574 : abstract Builder setGroupUuid(AccountGroup.UUID groupUuid);
575 :
576 : abstract Builder setGroupId(AccountGroup.Id groupId);
577 :
578 : abstract Builder setGroupName(AccountGroup.NameKey name);
579 :
580 : abstract Builder setPreviousGroupName(AccountGroup.NameKey previousName);
581 :
582 : abstract Builder setAddedMembers(Set<Account.Id> addedMembers);
583 :
584 : abstract Builder setDeletedMembers(Set<Account.Id> deletedMembers);
585 :
586 : abstract Builder setAddedSubgroups(Set<AccountGroup.UUID> addedSubgroups);
587 :
588 : abstract Builder setDeletedSubgroups(Set<AccountGroup.UUID> deletedSubgroups);
589 :
590 : abstract UpdateResult build();
591 : }
592 : }
593 : }
|