Merge branch 'stable-3.2' into stable-3.3
* stable-3.2:
Fix formatting of documentation
Remove unsused variable
Allow blocking of usernames using wildcards and regex
Add command to register existing user as service user
Change-Id: Ib9dfe4cac48acefd85a103a7241c2abbc9c2f675
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilter.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilter.java
new file mode 100644
index 0000000..c365fb7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilter.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 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.annotations.PluginName;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+@Singleton
+public class BlockedNameFilter {
+ private final PluginConfig cfg;
+ private final Set<String> blockedExactNames = new HashSet<>();
+ private final List<String> blockedNamePrefixes = new ArrayList<>();
+ private final List<Pattern> blockedRegexNames = new ArrayList<>();
+
+ @Inject
+ public BlockedNameFilter(PluginConfigFactory cfgFactory, @PluginName String pluginName) {
+ this.cfg = cfgFactory.getFromGerritConfig(pluginName);
+ parseConfig();
+ }
+
+ public boolean isBlocked(String username) {
+ username = username.toLowerCase();
+ return isBlockedByExactName(username)
+ || isBlockedByWildcard(username)
+ || isBlockedByRegex(username);
+ }
+
+ private void parseConfig() {
+ for (String s : cfg.getStringList("block")) {
+ if (s.startsWith("^")) {
+ blockedRegexNames.add(Pattern.compile(s, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE));
+ } else if (s.endsWith("*")) {
+ blockedNamePrefixes.add(s.substring(0, s.length() - 1).toLowerCase());
+ } else {
+ blockedExactNames.add(s.toLowerCase());
+ }
+ }
+ }
+
+ private boolean isBlockedByExactName(String username) {
+ return blockedExactNames.contains(username);
+ }
+
+ private boolean isBlockedByWildcard(String username) {
+ for (String prefix : blockedNamePrefixes) {
+ if (username.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isBlockedByRegex(String username) {
+ for (Pattern p : blockedRegexNames) {
+ if (p.matcher(username).find()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
index 0bb2cbf..b63cf8b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
@@ -16,9 +16,7 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
-import com.google.common.base.Function;
import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.PluginName;
@@ -55,7 +53,6 @@
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
@@ -80,7 +77,6 @@
private final PluginConfig cfg;
private final Provider<ProjectLevelConfig.Bare> configProvider;
private final CreateAccount createAccount;
- private final List<String> blockedNames;
private final Provider<CurrentUser> userProvider;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final Project.NameKey allProjects;
@@ -88,6 +84,7 @@
private final Provider<GetConfig> getConfig;
private final AccountLoader.Factory accountLoader;
private final StorageCache storageCache;
+ private final BlockedNameFilter blockedNameFilter;
@Inject
CreateServiceUser(
@@ -101,19 +98,11 @@
ProjectCache projectCache,
Provider<GetConfig> getConfig,
AccountLoader.Factory accountLoader,
- StorageCache storageCache) {
+ StorageCache storageCache,
+ BlockedNameFilter blockedNameFilter) {
this.cfg = cfgFactory.getFromGerritConfig(pluginName);
this.configProvider = configProvider;
this.createAccount = createAccount;
- this.blockedNames =
- Lists.transform(
- Arrays.asList(cfg.getStringList("block")),
- new Function<String, String>() {
- @Override
- public String apply(String blockedName) {
- return blockedName.toLowerCase();
- }
- });
this.userProvider = userProvider;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allProjects = projectCache.getAllProjects().getProject().getNameKey();
@@ -123,6 +112,7 @@
this.getConfig = getConfig;
this.accountLoader = accountLoader;
this.storageCache = storageCache;
+ this.blockedNameFilter = blockedNameFilter;
}
@Override
@@ -150,7 +140,7 @@
throw new BadRequestException("sshKey invalid.");
}
- if (blockedNames.contains(username.toLowerCase())) {
+ if (blockedNameFilter.isBlocked(username)) {
throw new BadRequestException(
"The username '" + username + "' is not allowed as name for service users.");
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RefUpdateListener.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RefUpdateListener.java
index fef0bc7..421b326 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RefUpdateListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RefUpdateListener.java
@@ -27,7 +27,6 @@
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.concurrent.Future;
-
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java
new file mode 100644
index 0000000..f0319f1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUser.java
@@ -0,0 +1,193 @@
+// Copyright (C) 2021 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 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;
+import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_CREATOR_ID;
+import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.KEY_OWNER;
+import static com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.USER;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+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.ConfigResource;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.group.GroupResolver;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectLevelConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.serviceuser.GetServiceUser.ServiceUserInfo;
+import com.googlesource.gerrit.plugins.serviceuser.RegisterServiceUser.Input;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+
+@RequiresCapability(CreateServiceUserCapability.ID)
+@Singleton
+class RegisterServiceUser
+ implements RestCollectionCreateView<ConfigResource, ServiceUserResource, Input> {
+
+ static class Input {
+ String username;
+ String creator;
+ String owner;
+ }
+
+ private final Provider<ProjectLevelConfig.Bare> configProvider;
+ private final AccountResolver accountResolver;
+ private final GroupResolver groupResolver;
+ private final Provider<CurrentUser> userProvider;
+ private final MetaDataUpdate.User metaDataUpdateFactory;
+ private final Project.NameKey allProjects;
+ private final DateFormat rfc2822DateFormatter;
+ private final AccountLoader.Factory accountLoader;
+ private final StorageCache storageCache;
+ private final PermissionBackend permissionBackend;
+ private final BlockedNameFilter blockedNameFilter;
+
+ @Inject
+ RegisterServiceUser(
+ Provider<ProjectLevelConfig.Bare> configProvider,
+ AccountResolver accountResolver,
+ GroupResolver groupResolver,
+ Provider<CurrentUser> userProvider,
+ @GerritPersonIdent PersonIdent gerritIdent,
+ MetaDataUpdate.User metaDataUpdateFactory,
+ ProjectCache projectCache,
+ AccountLoader.Factory accountLoader,
+ StorageCache storageCache,
+ PermissionBackend permissionBackend,
+ BlockedNameFilter blockedNameFilter) {
+ this.configProvider = configProvider;
+ this.accountResolver = accountResolver;
+ this.groupResolver = groupResolver;
+ this.userProvider = userProvider;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.allProjects = projectCache.getAllProjects().getProject().getNameKey();
+ this.rfc2822DateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
+ this.rfc2822DateFormatter.setCalendar(
+ Calendar.getInstance(gerritIdent.getTimeZone(), Locale.US));
+ this.accountLoader = accountLoader;
+ this.storageCache = storageCache;
+ this.permissionBackend = permissionBackend;
+ this.blockedNameFilter = blockedNameFilter;
+ }
+
+ @Override
+ public Response<ServiceUserInfo> apply(ConfigResource parentResource, IdString id, Input input)
+ throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
+ CurrentUser requestingUser = userProvider.get();
+ if (requestingUser == null || !requestingUser.isIdentifiedUser()) {
+ throw new AuthException("authentication required");
+ }
+
+ if (input == null) {
+ input = new Input();
+ }
+
+ IdentifiedUser user;
+ try {
+ user = accountResolver.resolve(input.username).asUniqueUser();
+ } catch (UnresolvableAccountException e) {
+ throw new BadRequestException("Username does not exist");
+ }
+
+ if (!requestingUser.getAccountId().equals(user.getAccountId())
+ && !permissionBackend.user(requestingUser).testOrFalse(ADMINISTRATE_SERVER)) {
+ throw new MethodNotAllowedException("Forbidden");
+ }
+
+ if (blockedNameFilter.isBlocked(input.username)) {
+ throw new BadRequestException(
+ "The username '" + input.username + "' is not allowed as name for service users.");
+ }
+
+ String creator;
+ Account.Id creatorId;
+ if (Strings.isNullOrEmpty(input.creator)) {
+ creator = requestingUser.getUserName().orElse(null);
+ creatorId = requestingUser.asIdentifiedUser().getAccountId();
+ } else {
+ creator = input.creator;
+ creatorId = accountResolver.resolve(input.creator).asUniqueUser().getAccountId();
+ }
+ String creationDate = rfc2822DateFormatter.format(new Date());
+
+ String owner = null;
+ if (!Strings.isNullOrEmpty(input.owner)) {
+ try {
+ owner = groupResolver.parse(input.owner).getGroupUUID().toString();
+ } catch (UnresolvableAccountException e) {
+ throw new BadRequestException("The group '" + input.owner + "' does not exist");
+ }
+ }
+
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(allProjects)) {
+ ProjectLevelConfig.Bare update = configProvider.get();
+ update.load(md);
+
+ Config db = update.getConfig();
+ if (db.getSubsections(USER).contains(input.username)) {
+ return Response.none();
+ }
+ db.setInt(USER, input.username, KEY_CREATOR_ID, creatorId.get());
+ if (creator != null) {
+ db.setString(USER, input.username, KEY_CREATED_BY, creator);
+ }
+ if (owner != null) {
+ db.setString(USER, input.username, KEY_OWNER, owner);
+ }
+ db.setString(USER, input.username, KEY_CREATED_AT, creationDate);
+
+ md.setMessage("Create service user '" + input.username + "'\n");
+ update.commit(md);
+ storageCache.invalidate();
+ }
+
+ ServiceUserInfo info = new ServiceUserInfo(new AccountInfo(user.getAccountId().get()));
+ AccountLoader al = accountLoader.create(true);
+ info.createdBy = al.get(creatorId);
+ al.fill();
+ info.createdAt = creationDate;
+ return Response.created(info);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUserCommand.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUserCommand.java
new file mode 100644
index 0000000..fb8503c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/RegisterServiceUserCommand.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 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.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@RequiresCapability(CreateServiceUserCapability.ID)
+@CommandMetaData(name = "register", description = "Register Service User")
+class RegisterServiceUserCommand extends SshCommand {
+
+ @Argument(index = 0, required = true, metaVar = "USERNAME", usage = "name of the service user")
+ private String username;
+
+ @Option(
+ name = "--creator",
+ required = false,
+ metaVar = "CREATOR",
+ usage = "name of the creator of the service user")
+ private String creator;
+
+ @Option(
+ name = "--owner",
+ required = false,
+ metaVar = "OWNER",
+ usage = "group that owns the service user")
+ private String owner;
+
+ @Inject private RegisterServiceUser registerServiceUser;
+
+ @Override
+ protected void run()
+ throws IOException, UnloggedFailure, ConfigInvalidException, PermissionBackendException {
+ RegisterServiceUser.Input input = new RegisterServiceUser.Input();
+ input.username = username;
+ input.creator = creator;
+ input.owner = owner;
+
+ try {
+ registerServiceUser.apply(new ConfigResource(), IdString.fromDecoded(username), input);
+ } catch (RestApiException e) {
+ throw die(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshModule.java
index f775e4c..40a1509 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshModule.java
@@ -21,5 +21,6 @@
@Override
protected void configureCommands() {
command(CreateServiceUserCommand.class);
+ command(RegisterServiceUserCommand.class);
}
}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 17a4e29..e19d856 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -36,11 +36,11 @@
createdAt = Wed, 13 Nov 2013 14:45:00 +0100
```
-<a id="createdBy">
+<a id="createdBy"></a>
`user.<service-user-name>.createdBy`
: The username of the user who created the service user.
-<a id="createdAt">
+<a id="createdAt"></a>
`user.<service-user-name>.createdAt`
: The date when the service user was created.
diff --git a/src/main/resources/Documentation/cmd-register.md b/src/main/resources/Documentation/cmd-register.md
new file mode 100644
index 0000000..3378a2c
--- /dev/null
+++ b/src/main/resources/Documentation/cmd-register.md
@@ -0,0 +1,50 @@
+@PLUGIN@ register
+=================
+
+NAME
+----
+@PLUGIN@ register - Registers an existing user as a service user
+
+SYNOPSIS
+--------
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ register
+ --creator <CREATOR>
+ --owner <OWNER>
+ <USERNAME>
+```
+
+DESCRIPTION
+-----------
+Registers an existing user as a service user.
+
+ACCESS
+------
+Caller must be a member of a group that is granted the
+'Create Service User' capability (provided by this plugin) or the
+'Administrate Server' capability. If not possessing the 'Administrate
+Server' capability, the user to be registered as a service user must
+also be the caller.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+
+`--creator`
+: Username of the user that will be set as the creator of the
+ serviceuser. Defaults to the caller.
+
+`--owner`
+: ID or name of the group that will own the service user. Defaults
+ to no owner group being set.
+
+EXAMPLES
+--------
+Register a service user:
+
+```
+ $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ register --creator admin --owner Administrators username
+```
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index f52421e..aa2e4b5 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -9,30 +9,41 @@
group = Service Users
```
-<a id="block">
+<a id="block"></a>
`plugin.@PLUGIN@.block`
: A username which is forbidden to be used as name for a service
- user. The blocked username is case insensitive. Multiple
- usernames can be blocked by specifying multiple
+ user. The blocked username is case insensitive. The match can
+ either be exact, have a wildcard ('*') at the end or use regular
+ expressions, which have to start with '^'. If the regex pattern is not
+ ending with '$', every username starting with a matching prefix will be
+ blocked. Multiple usernames can be blocked by specifying multiple
`plugin.@PLUGIN@.block` entries.
+ Examples:
-<a id="group">
+```
+ [plugin "serviceuser"]
+ block = johndoe
+ block = jane*
+ block = ^gerrit[0-9]*
+```
+
+<a id="group"></a>
`plugin.@PLUGIN@.group`
: The name of an internal group to which newly created service users
should be automatically added. Multiple groups can be specified by
having multiple `plugin.@PLUGIN@.group` entries.
-<a id="infoMessage">
+<a id="infoMessage"></a>
`plugin.@PLUGIN@.infoMessage`
: HTML formatted message that should be displayed on the service user
creation screen.
-<a id="onSuccessMessage">
+<a id="onSuccessMessage"></a>
`plugin.@PLUGIN@.onSuccessMessage`
: Message that should be displayed after a service user was
successfully created.
-<a id="allowEmail">
+<a id="allowEmail"></a>
`plugin.@PLUGIN@.allowEmail`
: Whether it is allowed for service user owners to set email
addresses for their service users. Independent of this setting
@@ -40,7 +51,7 @@
any service user.
By default false.
-<a id="allowHttpPassword">
+<a id="allowHttpPassword"></a>
`plugin.@PLUGIN@.allowHttpPassword`
: Whether it is allowed for service user owners to generate HTTP
passwords for their service users. Independent of this setting
@@ -48,12 +59,12 @@
passwords for any service user.
By default false.
-<a id="allowOwner">
+<a id="allowOwner"></a>
`plugin.@PLUGIN@.allowOwner`
: Whether it is allowed to set an owner group for a service user.
By default false.
-<a id="createNotes">
+<a id="createNotes"></a>
`plugin.@PLUGIN@.createNotes`
: Whether commits of a service user should be annotated by a Git note
that contains information about the current owners of the service
@@ -62,7 +73,7 @@
user the 'Forge Committer' access right must be blocked for service
users. By default true.
-<a id="createNotes">
+<a id="createNotes"></a>
`plugin.@PLUGIN@.createNotesAsync`
: Whether the Git notes on commits that are pushed by a service user
should be created asynchronously. By default false.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilterTest.java
new file mode 100644
index 0000000..997c275
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/serviceuser/BlockedNameFilterTest.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BlockedNameFilterTest {
+
+ private static String[] BLOCKED_NAMES =
+ new String[] {
+ "exact", "ex*act", "wild*", "^regex[0-9]+", "^ABC", "^[0-9]+$", "regex[0-9]+", "^⁋+"
+ };
+
+ private BlockedNameFilter blockedNameFilter;
+
+ @Mock private PluginConfigFactory configFactory;
+
+ @Mock private PluginConfig config;
+
+ @Before
+ public void setup() {
+ when(configFactory.getFromGerritConfig("serviceuser")).thenReturn(config);
+ when(config.getStringList("block")).thenReturn(BLOCKED_NAMES);
+ blockedNameFilter = new BlockedNameFilter(configFactory, "serviceuser");
+ }
+
+ @Test
+ public void exactMatchIsBlocked() {
+ assertThat(blockedNameFilter.isBlocked("exact")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("ExAct")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("ex*act")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("regex[0-9]+")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("notexact")).isFalse();
+ assertThat(blockedNameFilter.isBlocked("exxact")).isFalse();
+ }
+
+ @Test
+ public void wildcardMatchIsBlocked() {
+ assertThat(blockedNameFilter.isBlocked("wild")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("wildcard")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("Wilde")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("wil")).isFalse();
+ }
+
+ @Test
+ public void regexMatchIsBlocked() {
+ assertThat(blockedNameFilter.isBlocked("regex1")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("Regex1")).isTrue();
+
+ // Pattern matching is done at the beginning of the username
+ assertThat(blockedNameFilter.isBlocked("foo-regex1")).isFalse();
+
+ // Names with unicode characters can be blocked
+ assertThat(blockedNameFilter.isBlocked("⁋")).isTrue();
+
+ // Regex matches only complete name, when ending with '$'.
+ assertThat(blockedNameFilter.isBlocked("01234")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("01234abcd")).isFalse();
+
+ // Regex matches prefix without trailing '$'
+ assertThat(blockedNameFilter.isBlocked("regex1suffix")).isTrue();
+
+ // Uppercase regex matches case-insenstive
+ assertThat(blockedNameFilter.isBlocked("abc")).isTrue();
+ assertThat(blockedNameFilter.isBlocked("ABC")).isTrue();
+ }
+}