Merge "Only accept pushes from service users if at least one owner is active"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserNotes.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserNotes.java
index 600a9b1..41551e3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserNotes.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserNotes.java
@@ -17,29 +17,13 @@
import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_CREATED_BY;
import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_OWNER;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountProjectWatch;
-import com.google.gerrit.reviewdb.client.Change.Id;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.git.NotesBranchUtil;
-import com.google.gerrit.server.group.ListMembers;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.serviceuser.GetServiceUser.ServiceUserInfo;
@@ -58,11 +42,6 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
class CreateServiceUserNotes {
private static final Logger log =
@@ -76,13 +55,8 @@
private final PersonIdent gerritServerIdent;
private final NotesBranchUtil.Factory notesBranchUtilFactory;
- private final AccountResolver resolver;
- private final IdentifiedUser.GenericFactory genericUserFactory;
- private final Provider<GetServiceUser> getServiceUser;
- private final Provider<ListMembers> listMembers;
+ private final ServiceUserResolver serviceUserResolver;
private final @AnonymousCowardName String anonymousCowardName;
- private final ThreadLocalRequestContext tl;
- private final SchemaFactory<ReviewDb> schema;
private final Project.NameKey project;
private final Repository git;
@@ -93,24 +67,14 @@
@Inject
CreateServiceUserNotes(@GerritPersonIdent PersonIdent gerritIdent,
NotesBranchUtil.Factory notesBranchUtilFactory,
- AccountResolver resolver,
- IdentifiedUser.GenericFactory genericUserFactory,
- Provider<GetServiceUser> getServiceUser,
- Provider<ListMembers> listMembers,
+ ServiceUserResolver serviceUserResolver,
@AnonymousCowardName String anonymousCowardName,
- ThreadLocalRequestContext tl,
- SchemaFactory<ReviewDb> schema,
@Assisted Project.NameKey project,
@Assisted Repository git) {
this.gerritServerIdent = gerritIdent;
this.notesBranchUtilFactory = notesBranchUtilFactory;
- this.resolver = resolver;
- this.genericUserFactory = genericUserFactory;
- this.getServiceUser = getServiceUser;
- this.listMembers = listMembers;
+ this.serviceUserResolver = serviceUserResolver;
this.anonymousCowardName = anonymousCowardName;
- this.tl = tl;
- this.schema = schema;
this.project = project;
this.git = git;
}
@@ -137,7 +101,8 @@
try {
for (RevCommit c : rw) {
- ServiceUserInfo serviceUser = getAsServiceUser(c.getCommitterIdent());
+ ServiceUserInfo serviceUser =
+ serviceUserResolver.getAsServiceUser(c.getCommitterIdent());
if (serviceUser != null) {
ObjectId content = createNoteContent(branch, serviceUser);
getNotes().set(c, content);
@@ -149,26 +114,6 @@
}
}
- private ServiceUserInfo getAsServiceUser(PersonIdent committerIdent)
- throws OrmException {
- StringBuilder committer = new StringBuilder();
- committer.append(committerIdent.getName());
- committer.append(" <");
- committer.append(committerIdent.getEmailAddress());
- committer.append("> ");
-
- Account account = resolver.find(committer.toString());
- if (account == null) {
- return null;
- }
- try {
- return getServiceUser.get().apply(
- new ServiceUserResource(genericUserFactory.create(account.getId())));
- } catch (ResourceNotFoundException e) {
- return null;
- }
- }
-
void commitNotes() throws IOException, ConcurrentRefUpdateException {
try {
if (serviceUserNotes == null) {
@@ -221,91 +166,12 @@
fmt.append("Project", project.get());
fmt.append("Branch", branch);
fmt.appendUser(KEY_CREATED_BY, serviceUser.createdBy);
- for (AccountInfo owner : listOwners(serviceUser)) {
+ for (AccountInfo owner : serviceUserResolver.listOwners(serviceUser)) {
fmt.appendUser(KEY_OWNER, owner);
}
return fmt.toString();
}
- private List<AccountInfo> listOwners(ServiceUserInfo serviceUser) throws OrmException {
- if (serviceUser.owner == null) {
- return Collections.emptyList();
- }
-
- final ReviewDb db = schema.open();
- try {
- RequestContext context = new RequestContext() {
- @Override
- public CurrentUser getCurrentUser() {
- return new CurrentUser(null) {
-
- @Override
- public Set<Id> getStarredChanges() {
- return null;
- }
-
- @Override
- public Collection<AccountProjectWatch> getNotificationFilters() {
- return null;
- }
-
- @Override
- public GroupMembership getEffectiveGroups() {
- return new GroupMembership() {
- @Override
- public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds) {
- return null;
- }
-
- @Override
- public Set<AccountGroup.UUID> getKnownGroups() {
- return null;
- }
-
- @Override
- public boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds) {
- return true;
- }
-
- @Override
- public boolean contains(AccountGroup.UUID groupId) {
- return true;
- }
- };
- }
- };
- }
-
- @Override
- public Provider<ReviewDb> getReviewDbProvider() {
- return new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- return db;
- }};
- }
- };
- RequestContext old = tl.setContext(context);
- try {
- ListMembers lm = listMembers.get();
- lm.setRecursive(true);
- List<AccountInfo> owners = new ArrayList<>();
- for (AccountInfo a : lm.apply(new AccountGroup.UUID(serviceUser.owner.id))) {
- owners.add(a);
- }
- return owners;
- } catch (MethodNotAllowedException e) {
- log.error(String.format("Failed to list members of owner group %s for service user %s.",
- serviceUser.owner.name, serviceUser.username));
- return Collections.emptyList();
- } finally {
- tl.setContext(old);
- }
- } finally {
- db.close();
- }
- }
-
private ObjectInserter getInserter() {
if (inserter == null) {
inserter = git.newObjectInserter();
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 085e05d..1282925 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
@@ -25,6 +25,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.git.validators.CommitValidationListener;
import com.google.inject.AbstractModule;
import com.google.inject.assistedinject.FactoryModuleBuilder;
@@ -38,6 +39,8 @@
DynamicSet.bind(binder(), TopMenu.class).to(ServiceUserMenu.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
.to(RefUpdateListener.class);
+ DynamicSet.bind(binder(), CommitValidationListener.class)
+ .to(ValidateServiceUserCommits.class);
install(new FactoryModuleBuilder().build(CreateServiceUserNotes.Factory.class));
install(new RestApiModule() {
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserResolver.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserResolver.java
new file mode 100644
index 0000000..e29c304
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserResolver.java
@@ -0,0 +1,188 @@
+// Copyright (C) 2014 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.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch;
+import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.group.ListMembers;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import com.googlesource.gerrit.plugins.serviceuser.GetServiceUser.ServiceUserInfo;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class ServiceUserResolver {
+ private static final Logger log =
+ LoggerFactory.getLogger(ServiceUserResolver.class);
+
+ private final AccountResolver resolver;
+ private final IdentifiedUser.GenericFactory genericUserFactory;
+ private final Provider<GetServiceUser> getServiceUser;
+ private final Provider<ListMembers> listMembers;
+ private final SchemaFactory<ReviewDb> schema;
+ private final ThreadLocalRequestContext tl;
+ private final AccountCache accountCache;
+
+ @Inject
+ ServiceUserResolver(AccountResolver resolver,
+ IdentifiedUser.GenericFactory genericUserFactory,
+ Provider<GetServiceUser> getServiceUser,
+ Provider<ListMembers> listMembers, SchemaFactory<ReviewDb> schema,
+ ThreadLocalRequestContext tl, AccountCache accountCache) {
+ this.resolver = resolver;
+ this.genericUserFactory = genericUserFactory;
+ this.getServiceUser = getServiceUser;
+ this.listMembers = listMembers;
+ this.schema = schema;
+ this.tl = tl;
+ this.accountCache = accountCache;
+ }
+
+ ServiceUserInfo getAsServiceUser(PersonIdent committerIdent)
+ throws OrmException {
+ StringBuilder committer = new StringBuilder();
+ committer.append(committerIdent.getName());
+ committer.append(" <");
+ committer.append(committerIdent.getEmailAddress());
+ committer.append("> ");
+
+ Account account = resolver.find(committer.toString());
+ if (account == null) {
+ return null;
+ }
+ try {
+ return getServiceUser.get().apply(
+ new ServiceUserResource(genericUserFactory.create(account.getId())));
+ } catch (ResourceNotFoundException e) {
+ return null;
+ }
+ }
+
+ List<AccountInfo> listOwners(ServiceUserInfo serviceUser) throws OrmException {
+ if (serviceUser.owner == null) {
+ return Collections.emptyList();
+ }
+
+ final ReviewDb db = schema.open();
+ try {
+ RequestContext context = new RequestContext() {
+ @Override
+ public CurrentUser getCurrentUser() {
+ return new CurrentUser(null) {
+
+ @Override
+ public Set<Id> getStarredChanges() {
+ return null;
+ }
+
+ @Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ return null;
+ }
+
+ @Override
+ public GroupMembership getEffectiveGroups() {
+ return new GroupMembership() {
+ @Override
+ public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds) {
+ return null;
+ }
+
+ @Override
+ public Set<AccountGroup.UUID> getKnownGroups() {
+ return null;
+ }
+
+ @Override
+ public boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds) {
+ return true;
+ }
+
+ @Override
+ public boolean contains(AccountGroup.UUID groupId) {
+ return true;
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return new Provider<ReviewDb>() {
+ @Override
+ public ReviewDb get() {
+ return db;
+ }};
+ }
+ };
+ RequestContext old = tl.setContext(context);
+ try {
+ ListMembers lm = listMembers.get();
+ lm.setRecursive(true);
+ List<AccountInfo> owners = new ArrayList<>();
+ for (AccountInfo a : lm.apply(new AccountGroup.UUID(serviceUser.owner.id))) {
+ owners.add(a);
+ }
+ return owners;
+ } catch (MethodNotAllowedException e) {
+ log.error(String.format("Failed to list members of owner group %s for service user %s.",
+ serviceUser.owner.name, serviceUser.username));
+ return Collections.emptyList();
+ } finally {
+ tl.setContext(old);
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ List<AccountInfo> listActiveOwners(ServiceUserInfo serviceUser)
+ throws OrmException {
+ List<AccountInfo> activeOwners = new ArrayList<>();
+ for (AccountInfo owner : listOwners(serviceUser)) {
+ AccountState accountState = accountCache.get(owner._id);
+ if (accountState != null && accountState.getAccount().isActive()) {
+ activeOwners.add(owner);
+ }
+ }
+ return activeOwners;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ValidateServiceUserCommits.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ValidateServiceUserCommits.java
new file mode 100644
index 0000000..4cabde3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ValidateServiceUserCommits.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2014 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.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.serviceuser.GetServiceUser.ServiceUserInfo;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ValidateServiceUserCommits implements CommitValidationListener {
+ private final ServiceUserResolver serviceUserResolver;
+ private final AccountCache accountCache;
+
+ @Inject
+ ValidateServiceUserCommits(ServiceUserResolver serviceUserResolver,
+ AccountCache accountCache) {
+ this.serviceUserResolver = serviceUserResolver;
+ this.accountCache = accountCache;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ try {
+ PersonIdent committer = receiveEvent.commit.getCommitterIdent();
+ ServiceUserInfo serviceUser =
+ serviceUserResolver.getAsServiceUser(committer);
+ if (serviceUser.owner != null
+ && serviceUserResolver.listActiveOwners(serviceUser).isEmpty()) {
+ throw new CommitValidationException(String.format(
+ "Commit %s of service user %s (%s) is rejected because "
+ + "all service user owner accounts are inactive.",
+ receiveEvent.commit.getId().getName(), committer.getName(),
+ committer.getEmailAddress()));
+ } else {
+ AccountState creator = accountCache.get(serviceUser.createdBy._id);
+ if (creator == null || !creator.getAccount().isActive()) {
+ throw new CommitValidationException(String.format(
+ "Commit %s of service user %s (%s) is rejected because "
+ + "the account of the service creator is inactive.",
+ receiveEvent.commit.getId().getName(), committer.getName(),
+ committer.getEmailAddress()));
+ }
+ }
+ } catch (OrmException e) {
+ throw new CommitValidationException(
+ "Internal error while checking for service user commits.", e);
+ }
+ return Collections.emptyList();
+ }
+}