Merge "Update _computeMessageContent to use optional properties"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 7ab97e65..313092f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2512,6 +2512,18 @@
[[groups]]
=== Section groups
+[[groups.includeExternalUsersInRegisteredUsersGroup]]groups.includeExternalUsersInRegisteredUsersGroup::
++
+Controls whether external users (these are users we have sufficient
+knowledge about but who don't yet have a Gerrit account) are considered
+to be members of the `REGISTERED_USERS` group.
++
+This setting only makes sense if you run custom code (e.g. from a plugin
+or a custom authentication backend). By default, Gerrit core always requires
+users to register and doesn't use external users.
++
+By default, true.
+
[[groups.newGroupsVisibleToAll]]groups.newGroupsVisibleToAll::
+
Controls whether newly created groups should be by default visible to
diff --git a/WORKSPACE b/WORKSPACE
index f68ecf6..fd36850 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -137,36 +137,6 @@
sha1 = "83cd2cd674a217ade95a4bb83a8a14f351f48bd0",
)
-GUICE_VERS = "4.2.3"
-
-GUICE_LIBRARY_SHA256 = "5168f5e7383f978c1b4154ac777b78edd8ac214bb9f9afdb92921c8d156483d3"
-
-http_file(
- name = "guice-library-no-aop",
- canonical_id = "guice-library-no-aop-" + GUICE_VERS + ".jar-" + GUICE_LIBRARY_SHA256,
- downloaded_file_path = "guice-library-no-aop.jar",
- sha256 = GUICE_LIBRARY_SHA256,
- urls = [
- "https://repo1.maven.org/maven2/com/google/inject/guice/" +
- GUICE_VERS +
- "/guice-" +
- GUICE_VERS +
- "-no_aop.jar",
- ],
-)
-
-maven_jar(
- name = "guice-assistedinject",
- artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
- sha1 = "acbfddc556ee9496293ed1df250cc378f331d854",
-)
-
-maven_jar(
- name = "guice-servlet",
- artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
- sha1 = "8d6e7e35eac4fb5e7df19c55b3bc23fa51b10a11",
-)
-
maven_jar(
name = "javax_inject",
artifact = "javax.inject:javax.inject:1",
diff --git a/java/com/google/gerrit/server/CurrentUser.java b/java/com/google/gerrit/server/CurrentUser.java
index 825b34f..c85c389 100644
--- a/java/com/google/gerrit/server/CurrentUser.java
+++ b/java/com/google/gerrit/server/CurrentUser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -128,6 +129,14 @@
getClass().getSimpleName() + " is not an IdentifiedUser");
}
+ /**
+ * Returns all {@link ExternalId.Key}s associated with this user. For {@link AnonymousUser} and
+ * other users that don't represent a person user or service account, this set will be empty.
+ */
+ public ImmutableSet<ExternalId.Key> getExternalIdKeys() {
+ return ImmutableSet.of();
+ }
+
/** Check if the CurrentUser is an InternalUser. */
public boolean isInternalUser() {
return false;
diff --git a/java/com/google/gerrit/server/IdentifiedUser.java b/java/com/google/gerrit/server/IdentifiedUser.java
index 75c7cda..ca86434 100644
--- a/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/java/com/google/gerrit/server/IdentifiedUser.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.flogger.LazyArgs.lazy;
import com.google.common.annotations.VisibleForTesting;
@@ -31,6 +32,7 @@
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -388,6 +390,11 @@
return ImmutableSet.copyOf(validEmails);
}
+ @Override
+ public ImmutableSet<ExternalId.Key> getExternalIdKeys() {
+ return state().externalIds().stream().map(ExternalId::key).collect(toImmutableSet());
+ }
+
public String getName() {
return getAccount().getName();
}
diff --git a/java/com/google/gerrit/server/account/GroupBackend.java b/java/com/google/gerrit/server/account/GroupBackend.java
index 545da6e..d6360c5 100644
--- a/java/com/google/gerrit/server/account/GroupBackend.java
+++ b/java/com/google/gerrit/server/account/GroupBackend.java
@@ -19,7 +19,7 @@
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.ProjectState;
import java.util.Collection;
@@ -42,7 +42,7 @@
Collection<GroupReference> suggest(String name, @Nullable ProjectState project);
/** @return the group membership checker for the backend. */
- GroupMembership membershipsOf(IdentifiedUser user);
+ GroupMembership membershipsOf(CurrentUser user);
/** @return {@code true} if the group with the given UUID is visible to all registered users. */
boolean isVisibleToAll(AccountGroup.UUID uuid);
diff --git a/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index 6dc7976..9cb11a6 100644
--- a/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.entities.AccountGroup;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -40,18 +41,18 @@
*/
public class IncludingGroupMembership implements GroupMembership {
public interface Factory {
- IncludingGroupMembership create(IdentifiedUser user);
+ IncludingGroupMembership create(CurrentUser user);
}
private final GroupCache groupCache;
private final GroupIncludeCache includeCache;
- private final IdentifiedUser user;
+ private final CurrentUser user;
private final Map<AccountGroup.UUID, Boolean> memberOf;
private Set<AccountGroup.UUID> knownGroups;
@Inject
IncludingGroupMembership(
- GroupCache groupCache, GroupIncludeCache includeCache, @Assisted IdentifiedUser user) {
+ GroupCache groupCache, GroupIncludeCache includeCache, @Assisted CurrentUser user) {
this.groupCache = groupCache;
this.includeCache = includeCache;
this.user = user;
@@ -93,7 +94,7 @@
if (!group.isPresent()) {
continue;
}
- if (group.get().getMembers().contains(user.getAccountId())) {
+ if (user.isIdentifiedUser() && group.get().getMembers().contains(user.getAccountId())) {
memberOf.put(id, true);
return true;
}
@@ -124,7 +125,10 @@
private ImmutableSet<AccountGroup.UUID> computeKnownGroups() {
GroupMembership membership = user.getEffectiveGroups();
- Collection<AccountGroup.UUID> direct = includeCache.getGroupsWithMember(user.getAccountId());
+ Collection<AccountGroup.UUID> direct =
+ user.isIdentifiedUser()
+ ? includeCache.getGroupsWithMember(user.getAccountId())
+ : ImmutableList.of();
direct.forEach(groupUuid -> memberOf.put(groupUuid, true));
Set<AccountGroup.UUID> r = Sets.newHashSet(direct);
r.remove(null);
diff --git a/java/com/google/gerrit/server/account/InternalGroupBackend.java b/java/com/google/gerrit/server/account/InternalGroupBackend.java
index c520c96..8761081 100644
--- a/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -20,7 +20,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.InternalGroupDescription;
import com.google.gerrit.server.group.db.Groups;
@@ -97,7 +97,7 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
+ public GroupMembership membershipsOf(CurrentUser user) {
return groupMembershipFactory.create(user);
}
diff --git a/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index a35b0ac..5bd9bea 100644
--- a/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -27,7 +27,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.StartupCheck;
import com.google.gerrit.server.StartupException;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -94,14 +94,14 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
+ public GroupMembership membershipsOf(CurrentUser user) {
return new UniversalGroupMembership(user);
}
private class UniversalGroupMembership implements GroupMembership {
private final Map<GroupBackend, GroupMembership> memberships;
- private UniversalGroupMembership(IdentifiedUser user) {
+ private UniversalGroupMembership(CurrentUser user) {
ImmutableMap.Builder<GroupBackend, GroupMembership> builder = ImmutableMap.builder();
backends.runEach(g -> builder.put(g, g.membershipsOf(user)));
this.memberships = builder.build();
diff --git a/java/com/google/gerrit/server/auth/ldap/FakeLdapGroupBackend.java b/java/com/google/gerrit/server/auth/ldap/FakeLdapGroupBackend.java
index 63cd426..f806756 100644
--- a/java/com/google/gerrit/server/auth/ldap/FakeLdapGroupBackend.java
+++ b/java/com/google/gerrit/server/auth/ldap/FakeLdapGroupBackend.java
@@ -20,7 +20,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
@@ -76,7 +76,7 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
+ public GroupMembership membershipsOf(CurrentUser user) {
return new ListGroupMembership(Collections.emptyList());
}
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 180612c..d5da406 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -29,7 +29,6 @@
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -45,6 +44,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.naming.InvalidNameException;
@@ -178,21 +178,13 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
- String id = findId(user.state().externalIds());
- if (id == null) {
+ public GroupMembership membershipsOf(CurrentUser user) {
+ Optional<ExternalId.Key> id =
+ user.getExternalIdKeys().stream().filter(e -> e.isScheme(SCHEME_GERRIT)).findAny();
+ if (!id.isPresent()) {
return GroupMembership.EMPTY;
}
- return new LdapGroupMembership(membershipCache, projectCache, id, gerritConfig);
- }
-
- private static String findId(Collection<ExternalId> extIds) {
- for (ExternalId extId : extIds) {
- if (extId.isScheme(SCHEME_GERRIT)) {
- return extId.key().id();
- }
- }
- return null;
+ return new LdapGroupMembership(membershipCache, projectCache, id.get().id(), gerritConfig);
}
private Set<GroupReference> suggestLdap(String name) {
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b1b2711..d131cf5 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -179,6 +179,7 @@
import com.google.gerrit.server.query.change.ChangeIsVisibleToPredicate;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ConflictsCacheImpl;
+import com.google.gerrit.server.query.change.ExternalUser;
import com.google.gerrit.server.quota.QuotaEnforcer;
import com.google.gerrit.server.restapi.change.OnPostReview;
import com.google.gerrit.server.restapi.change.SuggestReviewers;
@@ -279,6 +280,7 @@
factory(ProjectState.Factory.class);
factory(RevisionJson.Factory.class);
factory(InboundEmailRejectionSender.Factory.class);
+ factory(ExternalUser.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
AccountDefaultDisplayName accountDefaultDisplayName =
diff --git a/java/com/google/gerrit/server/group/SystemGroupBackend.java b/java/com/google/gerrit/server/group/SystemGroupBackend.java
index b5ccb18..dfb42ff 100644
--- a/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -24,6 +24,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StartupCheck;
import com.google.gerrit.server.StartupException;
@@ -33,6 +34,7 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.db.Groups;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.ExternalUser;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -90,6 +92,7 @@
private final SortedMap<String, GroupReference> namesToGroups;
private final ImmutableSet<String> names;
private final ImmutableMap<AccountGroup.UUID, GroupReference> uuids;
+ private final ImmutableSet<AccountGroup.UUID> externalUserMemberships;
@Inject
@VisibleForTesting
@@ -114,6 +117,10 @@
ImmutableSet.copyOf(
namesToGroups.values().stream().map(GroupReference::getName).collect(toSet()));
uuids = u.build();
+ externalUserMemberships =
+ cfg.getBoolean("groups", null, "includeExternalUsersInRegisteredUsersGroup", true)
+ ? ImmutableSet.of(ANONYMOUS_USERS, REGISTERED_USERS)
+ : ImmutableSet.of(ANONYMOUS_USERS);
}
public GroupReference getGroup(AccountGroup.UUID uuid) {
@@ -182,8 +189,14 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
- return new ListGroupMembership(ImmutableSet.of(ANONYMOUS_USERS, REGISTERED_USERS));
+ public GroupMembership membershipsOf(CurrentUser user) {
+ if (user instanceof ExternalUser) {
+ return new ListGroupMembership(externalUserMemberships);
+ }
+ if (user instanceof IdentifiedUser) {
+ return new ListGroupMembership(ImmutableSet.of(ANONYMOUS_USERS, REGISTERED_USERS));
+ }
+ return new ListGroupMembership(ImmutableSet.of(ANONYMOUS_USERS));
}
public static class NameCheck implements StartupCheck {
diff --git a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
index 35f18a2..601ac59 100644
--- a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
+++ b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
@@ -22,7 +22,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.project.ProjectState;
@@ -122,7 +122,10 @@
}
@Override
- public GroupMembership membershipsOf(IdentifiedUser user) {
+ public GroupMembership membershipsOf(CurrentUser user) {
+ if (!user.isIdentifiedUser()) {
+ return GroupMembership.EMPTY;
+ }
return memberships.getOrDefault(user.getAccountId(), GroupMembership.EMPTY);
}
diff --git a/java/com/google/gerrit/server/query/change/ExternalUser.java b/java/com/google/gerrit/server/query/change/ExternalUser.java
new file mode 100644
index 0000000..f853a5c
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/ExternalUser.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2020 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.google.gerrit.server.query.change;
+
+import static com.google.common.flogger.LazyArgs.lazy;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Collection;
+
+/**
+ * Represents a user that does not have a Gerrit account.
+ *
+ * <p>This user is limited in what they can do on Gerrit. For now, we only guarantee that permission
+ * checking - including ref filtering works.
+ *
+ * <p>This class is thread-safe.
+ */
+public class ExternalUser extends CurrentUser {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ public interface Factory {
+ ExternalUser create(Collection<ExternalId.Key> externalIdKeys);
+ }
+
+ private final GroupBackend groupBackend;
+ private final ImmutableSet<ExternalId.Key> externalIdKeys;
+
+ private GroupMembership effectiveGroups;
+
+ @Inject
+ public ExternalUser(
+ GroupBackend groupBackend, @Assisted Collection<ExternalId.Key> externalIdKeys) {
+ this.groupBackend = groupBackend;
+ this.externalIdKeys = ImmutableSet.copyOf(externalIdKeys);
+ }
+
+ @Override
+ public ImmutableSet<ExternalId.Key> getExternalIdKeys() {
+ return externalIdKeys;
+ }
+
+ @Override
+ public GroupMembership getEffectiveGroups() {
+ synchronized (this) {
+ if (effectiveGroups == null) {
+ effectiveGroups = groupBackend.membershipsOf(this);
+ logger.atFinest().log(
+ "Known groups of %s: %s", getLoggableName(), lazy(effectiveGroups::getKnownGroups));
+ }
+ }
+ return effectiveGroups;
+ }
+
+ @Override
+ public Object getCacheKey() {
+ return this; // Caching is tied to this exact instance.
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/GroupBackedUser.java b/java/com/google/gerrit/server/query/change/GroupBackedUser.java
index 3960813..d0d5735 100644
--- a/java/com/google/gerrit/server/query/change/GroupBackedUser.java
+++ b/java/com/google/gerrit/server/query/change/GroupBackedUser.java
@@ -23,21 +23,13 @@
/**
* Representation of a user that does not have a Gerrit account.
*
- * <p>This user representation is intended to be used for two purposes:
+ * <p>This user representation is intended to be used to check permissions for groups:
*
- * <ol>
- * <li>Checking permissions for groups: There are occasions where we need to check if a resource -
- * such as a change - is accessible by a group. Our entire {@link
- * com.google.gerrit.server.permissions.PermissionBackend} works solely with {@link
- * CurrentUser}. This class can be used to check permissions on a synthetic user with the
- * given group memberships. Any real Gerrit user with the same group memberships would receive
- * the same permission check results.
- * <li>Checking permissions for an external user: In installations with external group systems,
- * one might want to check what Gerrit permissions a user has, before or even without creating
- * a Gerrit account. Such an external user has external group memberships only as well as
- * internal groups that contain the user's external groups as subgroups. This class can be
- * used to represent such an external user.
- * </ol>
+ * <p>There are occasions where we need to check if a resource - such as a change - is accessible by
+ * a group. Our entire {@link com.google.gerrit.server.permissions.PermissionBackend} works solely
+ * with {@link CurrentUser}. This class can be used to check permissions on a synthetic user with
+ * the given group memberships. Any real Gerrit user with the same group memberships would receive
+ * the same permission check results.
*/
public final class GroupBackedUser extends CurrentUser {
private final GroupMembership groups;
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index 74ca721..613c805 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -54,6 +54,7 @@
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
+import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
@@ -66,6 +67,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.TimeZone;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
@@ -75,7 +77,7 @@
public class CreateGroup
implements RestCollectionCreateView<TopLevelResource, GroupResource, GroupInput> {
private final Provider<IdentifiedUser> self;
- private final PersonIdent serverIdent;
+ private final TimeZone serverTimeZone;
private final Provider<GroupsUpdate> groupsUpdateProvider;
private final GroupCache groupCache;
private final GroupResolver groups;
@@ -89,7 +91,7 @@
@Inject
CreateGroup(
Provider<IdentifiedUser> self,
- @GerritPersonIdent PersonIdent serverIdent,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent,
@UserInitiated Provider<GroupsUpdate> groupsUpdateProvider,
GroupCache groupCache,
GroupResolver groups,
@@ -100,7 +102,7 @@
@GerritServerConfig Config cfg,
Sequences sequences) {
this.self = self;
- this.serverIdent = serverIdent;
+ this.serverTimeZone = serverIdent.get().getTimeZone();
this.groupsUpdateProvider = groupsUpdateProvider;
this.groupCache = groupCache;
this.groups = groups;
@@ -210,7 +212,7 @@
createGroupArgs.uuid,
GroupUuid.make(
createGroupArgs.getGroupName(),
- self.get().newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone())));
+ self.get().newCommitterIdent(TimeUtil.nowTs(), serverTimeZone)));
InternalGroupCreation groupCreation =
InternalGroupCreation.builder()
.setGroupUUID(uuid)
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
new file mode 100644
index 0000000..fc7ab2f
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
@@ -0,0 +1,298 @@
+// Copyright (C) 2020 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.google.gerrit.acceptance.server.permissions;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.GroupDescription;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.ExternalUser;
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import java.util.Collection;
+import java.util.Set;
+import javax.inject.Inject;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests that permission logic used by {@link ExternalUser} works as expected. */
+public class ExternalUserPermissionIT extends AbstractDaemonTest {
+ private static final AccountGroup.UUID EXTERNAL_GROUP =
+ AccountGroup.uuid("company-auth:it-department");
+
+ @Inject private ChangeOperations changeOperations;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private PermissionBackend permissionBackend;
+ @Inject private ChangeNotes.Factory changeNotesFactory;
+ @Inject private ExternalUser.Factory externalUserFactory;
+ @Inject private GroupOperations groupOperations;
+
+ @Before
+ public void setUp() {
+ // Allow only read on refs/heads/master by default
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .remove(permissionKey(Permission.READ).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/master").group(ANONYMOUS_USERS))
+ .update();
+ }
+
+ @Override
+ public Module createModule() {
+ /**
+ * Binding a {@link GroupBackend} that pretends a user is part of a group if the external ID
+ * starts with the group UUID.
+ *
+ * <p>Example: Users "company-auth:it-department-1" and "company-auth:it-department-2" are a
+ * member of the group "company-auth:it-department"
+ */
+ return new AbstractModule() {
+ @Override
+ protected void configure() {
+ DynamicSet.bind(binder(), GroupBackend.class)
+ .toInstance(
+ new GroupBackend() {
+ @Override
+ public boolean handles(AccountGroup.UUID uuid) {
+ return uuid.get().startsWith("company-auth:");
+ }
+
+ @Override
+ public GroupDescription.Basic get(AccountGroup.UUID uuid) {
+ return new GroupDescription.Basic() {
+ @Override
+ public AccountGroup.UUID getGroupUUID() {
+ return uuid;
+ }
+
+ @Override
+ public String getName() {
+ return uuid.get();
+ }
+
+ @Override
+ public String getEmailAddress() {
+ return uuid.get() + "@example.com";
+ }
+
+ @Override
+ public String getUrl() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public Collection<GroupReference> suggest(String name, ProjectState project) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public GroupMembership membershipsOf(CurrentUser user) {
+ return new GroupMembership() {
+ @Override
+ public boolean contains(AccountGroup.UUID groupId) {
+ return user.getExternalIdKeys().stream()
+ .anyMatch(e -> e.get().startsWith(groupId.get()));
+ }
+
+ @Override
+ public boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds) {
+ return ImmutableList.copyOf(groupIds).stream().anyMatch(g -> contains(g));
+ }
+
+ @Override
+ public Set<AccountGroup.UUID> intersection(
+ Iterable<AccountGroup.UUID> groupIds) {
+ return ImmutableList.copyOf(groupIds).stream()
+ .filter(g -> contains(g))
+ .collect(toImmutableSet());
+ }
+
+ @Override
+ public Set<AccountGroup.UUID> getKnownGroups() {
+ return ImmutableSet.of();
+ }
+ };
+ }
+
+ @Override
+ public boolean isVisibleToAll(AccountGroup.UUID uuid) {
+ return false;
+ }
+ });
+ }
+ };
+ }
+
+ @Test
+ public void defaultRefFilter_changeVisibilityIsAgnosticOfProvidedGroups() throws Exception {
+ ExternalUser user = createUserInGroup("1", "it-department");
+
+ Change.Id changeOnMaster = changeOperations.newChange().project(project).create();
+ Change.Id changeOnRefsMetaConfig =
+ changeOperations.newChange().project(project).branch("refs/meta/config").create();
+ // Check that only the change on the default branch is visible
+ assertThat(getVisibleRefNames(user))
+ .containsExactly(
+ "HEAD",
+ "refs/heads/master",
+ RefNames.changeMetaRef(changeOnMaster),
+ RefNames.patchSetRef(PatchSet.id(changeOnMaster, 1)));
+ // Grant access
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/meta/config").group(EXTERNAL_GROUP))
+ .update();
+ // Check that both changes are visible now
+ assertThat(getVisibleRefNames(user))
+ .containsExactly(
+ "HEAD",
+ "refs/heads/master",
+ "refs/meta/config",
+ RefNames.changeMetaRef(changeOnMaster),
+ RefNames.patchSetRef(PatchSet.id(changeOnMaster, 1)),
+ RefNames.changeMetaRef(changeOnRefsMetaConfig),
+ RefNames.patchSetRef(PatchSet.id(changeOnRefsMetaConfig, 1)));
+ }
+
+ @Test
+ public void defaultRefFilter_refVisibilityIsAgnosticOfProvidedGroups() throws Exception {
+ ExternalUser user = createUserInGroup("1", "it-department");
+ // Check that refs/meta/config isn't visible by default
+ assertThat(getVisibleRefNames(user)).containsExactly("HEAD", "refs/heads/master");
+ // Grant access
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/meta/config").group(EXTERNAL_GROUP))
+ .update();
+ // Check that refs/meta/config became visible
+ assertThat(getVisibleRefNames(user))
+ .containsExactly("HEAD", "refs/heads/master", "refs/meta/config");
+ }
+
+ @Test
+ public void changeVisibility_changeOnInvisibleBranchNotVisible() throws Exception {
+ // Create a change that is not visible to members of 'externalGroup'
+ Change.Id invisibleChange =
+ changeOperations.newChange().project(project).branch("refs/meta/config").create();
+ ExternalUser user = createUserInGroup("1", "it-department");
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ permissionBackend
+ .user(user)
+ .change(changeNotesFactory.create(project, invisibleChange))
+ .check(ChangePermission.READ));
+ assertThat(thrown).hasMessageThat().isEqualTo("read not permitted");
+ }
+
+ @Test
+ public void changeVisibility_changeOnBranchVisibleToAnonymousIsVisible() throws Exception {
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+ ExternalUser user = createUserInGroup("1", "it-department");
+ permissionBackend
+ .user(user)
+ .change(changeNotesFactory.create(project, changeId))
+ .check(ChangePermission.READ);
+ }
+
+ @Test
+ public void changeVisibility_changeOnBranchVisibleToRegisteredUsersIsVisible() throws Exception {
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+ ExternalUser user = createUserInGroup("1", "it-department");
+ blockAnonymousRead();
+ permissionBackend
+ .user(user)
+ .change(changeNotesFactory.create(project, changeId))
+ .check(ChangePermission.READ);
+ }
+
+ @Test
+ public void externalUser_isContainedInternalGroupThatContainsExternalGroup() {
+ AccountGroup.UUID internalGroup =
+ groupOperations.newGroup().addSubgroup(EXTERNAL_GROUP).create();
+ ExternalUser user = createUserInGroup("1", "it-department");
+ assertThat(user.getEffectiveGroups().contains(internalGroup)).isTrue();
+ assertThat(user.getEffectiveGroups().contains(EXTERNAL_GROUP)).isTrue();
+ assertThat(user.getEffectiveGroups().contains(REGISTERED_USERS)).isTrue();
+ assertThat(user.getEffectiveGroups().contains(ANONYMOUS_USERS)).isTrue();
+ }
+
+ @GerritConfig(name = "groups.includeExternalUsersInRegisteredUsersGroup", value = "true")
+ @Test
+ public void externalUser_isContainedInRegisteredUsersIfConfigured() {
+ ExternalUser user = createUserInGroup("1", "it-department");
+ assertThat(user.getEffectiveGroups().contains(REGISTERED_USERS)).isTrue();
+ }
+
+ @GerritConfig(name = "groups.includeExternalUsersInRegisteredUsersGroup", value = "false")
+ @Test
+ public void externalUser_isNotContainedInRegisteredUsersIfNotConfigured() {
+ ExternalUser user = createUserInGroup("1", "it-department");
+ assertThat(user.getEffectiveGroups().contains(REGISTERED_USERS)).isFalse();
+ }
+
+ private ImmutableList<String> getVisibleRefNames(CurrentUser user) throws Exception {
+ try (Repository repo = repoManager.openRepository(project)) {
+ return permissionBackend.user(user).project(project)
+ .filter(
+ repo.getRefDatabase().getRefs(), repo, PermissionBackend.RefFilterOptions.defaults())
+ .stream()
+ .map(Ref::getName)
+ .collect(toImmutableList());
+ }
+ }
+
+ ExternalUser createUserInGroup(String userId, String groupId) {
+ return externalUserFactory.create(
+ ImmutableSet.of(ExternalId.Key.parse("company-auth:" + groupId + "-" + userId)));
+ }
+}
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index e6b744c..8a6c282 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -23,6 +23,9 @@
flogger-log4j-backend
flogger-system-backend
guava
+guice-assistedinject
+guice-library-no-aop
+guice-servlet
httpasyncclient
httpcore-nio
j2objc
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 58ee52a..a0c53c6 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 58ee52a8670e38f30785bfbb648ba27c61c3a202
+Subproject commit a0c53c6c5ad1ba8f8967ed6d2bcb18995f734cad
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index a5b1626..af5a900 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -1,4 +1,5 @@
load("//tools/bzl:maven_jar.bzl", "maven_jar")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
GUAVA_VERSION = "29.0-jre"
GUAVA_BIN_SHA1 = "801142b4c3d0f0770dd29abea50906cacfddd447"
@@ -145,6 +146,36 @@
sha1 = GUAVA_BIN_SHA1,
)
+ GUICE_VERS = "4.2.3"
+
+ GUICE_LIBRARY_SHA256 = "5168f5e7383f978c1b4154ac777b78edd8ac214bb9f9afdb92921c8d156483d3"
+
+ http_file(
+ name = "guice-library-no-aop",
+ canonical_id = "guice-library-no-aop-" + GUICE_VERS + ".jar-" + GUICE_LIBRARY_SHA256,
+ downloaded_file_path = "guice-library-no-aop.jar",
+ sha256 = GUICE_LIBRARY_SHA256,
+ urls = [
+ "https://repo1.maven.org/maven2/com/google/inject/guice/" +
+ GUICE_VERS +
+ "/guice-" +
+ GUICE_VERS +
+ "-no_aop.jar",
+ ],
+ )
+
+ maven_jar(
+ name = "guice-assistedinject",
+ artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
+ sha1 = "acbfddc556ee9496293ed1df250cc378f331d854",
+ )
+
+ maven_jar(
+ name = "guice-servlet",
+ artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
+ sha1 = "8d6e7e35eac4fb5e7df19c55b3bc23fa51b10a11",
+ )
+
# Test-only dependencies below.
maven_jar(