Merge branch 'stable-3.9' * stable-3.9: Invalidate service user cache each time All-Projects is updated Fix missing group add of serviceuser for ssh register command Bug: Issue 372736304 Bug: Issue 367623920 Change-Id: I13a1b29290632f6433659b62ff1bcdbd3aa3055a
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CacheInvalidator.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CacheInvalidator.java new file mode 100644 index 0000000..fbf1eac --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CacheInvalidator.java
@@ -0,0 +1,51 @@ +// Copyright (C) 2024 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.googlesource.gerrit.plugins.serviceuser; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.EventListener; +import com.google.gerrit.server.events.RefUpdatedEvent; +import com.google.inject.Inject; + +class CacheInvalidator implements EventListener { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private final AllProjectsName allProjects; + private final StorageCache storageCache; + + @Inject + CacheInvalidator(AllProjectsName allProjects, StorageCache storageCache) { + this.allProjects = allProjects; + this.storageCache = storageCache; + } + + @Override + public void onEvent(Event event) { + // This is needed in a multi-site setup to make sure every Gerrit instance + // has the latest created serviceuser in cache. + if (event.getType().equals(RefUpdatedEvent.TYPE)) { + RefUpdatedEvent refUpdatedEvent = (RefUpdatedEvent) event; + if (refUpdatedEvent.getProjectNameKey().get().equals(allProjects.get()) + && refUpdatedEvent.getRefName().equals(RefNames.REFS_CONFIG)) { + logger.atFine().log( + "%s ref update triggered, invalidate serviceuser cache", allProjects.get()); + storageCache.invalidate(); + } + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java index 85eaae7..1baa0ee 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
@@ -26,6 +26,7 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestApiModule; import com.google.gerrit.extensions.webui.TopMenu; +import com.google.gerrit.server.events.EventListener; import com.google.gerrit.server.git.meta.VersionedConfigFile; import com.google.gerrit.server.git.validators.CommitValidationListener; import com.google.inject.AbstractModule; @@ -42,6 +43,7 @@ DynamicSet.bind(binder(), TopMenu.class).to(ServiceUserTopMenu.class); DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(RefUpdateListener.class); DynamicSet.bind(binder(), CommitValidationListener.class).to(ValidateServiceUserCommits.class); + DynamicSet.bind(binder(), EventListener.class).to(CacheInvalidator.class); install(new FactoryModuleBuilder().build(CreateServiceUserNotes.Factory.class)); install( new RestApiModule() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java index ef0d9e1..0d36a5e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java +++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java
@@ -14,6 +14,7 @@ package com.googlesource.gerrit.plugins.serviceuser; +import static com.google.gerrit.server.api.ApiUtil.asRestApiException; import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER; import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_CREATED_AT; import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_CREATED_BY; @@ -22,8 +23,13 @@ import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.USER; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.gerrit.entities.Account; +import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.Project; +import com.google.gerrit.exceptions.NoSuchGroupException; +import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.restapi.AuthException; @@ -36,14 +42,18 @@ import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.ServerInitiated; import com.google.gerrit.server.account.AccountLoader; import com.google.gerrit.server.account.AccountResolver; import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.ConfigResource; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.git.meta.MetaDataUpdate; import com.google.gerrit.server.git.meta.VersionedConfigFile; import com.google.gerrit.server.group.GroupResolver; +import com.google.gerrit.server.group.db.GroupDelta; +import com.google.gerrit.server.group.db.GroupsUpdate; import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.inject.Inject; @@ -83,6 +93,9 @@ private final StorageCache storageCache; private final PermissionBackend permissionBackend; private final BlockedNameFilter blockedNameFilter; + private final Provider<GroupsUpdate> groupsUpdateProvider; + private final Config config; + private final String pluginName; @Inject RegisterServiceUser( @@ -96,7 +109,10 @@ AccountLoader.Factory accountLoader, StorageCache storageCache, PermissionBackend permissionBackend, - BlockedNameFilter blockedNameFilter) { + BlockedNameFilter blockedNameFilter, + @ServerInitiated Provider<GroupsUpdate> groupsUpdateProvider, + @GerritServerConfig Config config, + @PluginName String pluginName) { this.configProvider = configProvider; this.accountResolver = accountResolver; this.groupResolver = groupResolver; @@ -110,6 +126,9 @@ this.storageCache = storageCache; this.permissionBackend = permissionBackend; this.blockedNameFilter = blockedNameFilter; + this.groupsUpdateProvider = groupsUpdateProvider; + this.config = config; + this.pluginName = pluginName; } @Override @@ -183,6 +202,16 @@ storageCache.invalidate(); } + Account.Id accountId = user.getAccountId(); + for (String groupName : config.getStringList("plugin", pluginName, "group")) { + AccountGroup.UUID groupUuid = groupResolver.parse(groupName).getGroupUUID(); + try { + addGroupMember(groupUuid, accountId); + } catch (NoSuchGroupException e) { + throw asRestApiException("Cannot add account: " + accountId + " to group: " + groupName, e); + } + } + ServiceUserInfo info = new ServiceUserInfo(new AccountInfo(user.getAccountId().get())); AccountLoader al = accountLoader.create(true); info.createdBy = al.get(creatorId); @@ -190,4 +219,13 @@ info.createdAt = creationDate; return Response.created(info); } + + private void addGroupMember(AccountGroup.UUID groupUuid, Account.Id accountId) + throws IOException, NoSuchGroupException, ConfigInvalidException { + GroupDelta groupDelta = + GroupDelta.builder() + .setMemberModification(memberIds -> Sets.union(memberIds, ImmutableSet.of(accountId))) + .build(); + groupsUpdateProvider.get().updateGroup(groupUuid, groupDelta); + } }