Add REST endpoint to list service users

The REST endpoint only returns service users which were created by the
caller. Administrators are able to see all service users.

Change-Id: I666295b772cf4fd475c68632c63a9f11485b71a9
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
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 06ce09b..80b3e2a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
@@ -57,9 +57,9 @@
 
 @RequiresCapability(CreateServiceUserCapability.ID)
 public class CreateServiceUser implements RestModifyView<ConfigResource, Input> {
-  private static final String USER = "user";
-  private static final String KEY_CREATED_BY = "createdBy";
-  private static final String KEY_CREATED_AT = "createdAt";
+  public static final String USER = "user";
+  public static final String KEY_CREATED_BY = "createdBy";
+  public static final String KEY_CREATED_AT = "createdAt";
 
   static class Input {
     String username;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ListServiceUsers.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ListServiceUsers.java
new file mode 100644
index 0000000..d1d0cf0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ListServiceUsers.java
@@ -0,0 +1,97 @@
+// 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 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.USER;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.RestReadView;
+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.AccountResource;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.GetAccount;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.git.ProjectLevelConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Map;
+
+public class ListServiceUsers implements RestReadView<ConfigResource> {
+  private final Provider<CurrentUser> userProvider;
+  private final IdentifiedUser.GenericFactory userFactory;
+  private final ProjectLevelConfig storage;
+  private final AccountCache accountCache;
+  private final Provider<GetAccount> getAccount;
+
+  @Inject
+  ListServiceUsers(Provider<CurrentUser> userProvider,
+      IdentifiedUser.GenericFactory userFactory, @PluginName String pluginName,
+      ProjectCache projectCache, AccountCache accountCache,
+      Provider<GetAccount> getAccount) {
+    this.userProvider = userProvider;
+    this.userFactory = userFactory;
+    this.storage = projectCache.getAllProjects().getConfig(pluginName + ".db");
+    this.accountCache = accountCache;
+    this.getAccount = getAccount;
+  }
+
+  @Override
+  public Map<String, ServiceUserInfo> apply(ConfigResource rscr) throws OrmException {
+    Map<String, ServiceUserInfo> accounts = Maps.newTreeMap();
+    Config db = storage.get();
+    boolean isAdmin = userProvider.get().getCapabilities().canAdministrateServer();
+    String currentUser = userProvider.get().getUserName();
+    for (String username : db.getSubsections(USER)) {
+      String createdBy = db.getString(USER, username, KEY_CREATED_BY);
+      if (isAdmin || currentUser.equals(createdBy)) {
+        AccountState account = accountCache.getByUsername(username);
+        if (account != null) {
+          ServiceUserInfo info = new ServiceUserInfo(getAccount.get().apply(
+              new AccountResource(userFactory.create(account.getAccount().getId()))));
+          info.username = null;
+          info.createdBy = createdBy;
+          info.createdAt = db.getString(USER, username, KEY_CREATED_AT);
+          accounts.put(username, info);
+        }
+      }
+    }
+    return accounts;
+  }
+
+  public static class ServiceUserInfo extends AccountInfo {
+    public String createdBy;
+    public String createdAt;
+
+    public ServiceUserInfo(AccountInfo info) {
+      super(info._id);
+      _accountId = info._accountId;
+      name = info.name;
+      email = info.email;
+      username = info.username;
+      avatars = info.avatars;
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserCollection.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserCollection.java
index 4a95ed0..f04c1d4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserCollection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserCollection.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 public class ServiceUserCollection implements
     ChildCollection<ConfigResource, AccountResource>,
@@ -30,12 +31,15 @@
 
   private final DynamicMap<RestView<AccountResource>> views;
   private final CreateServiceUser.Factory createServiceUserFactory;
+  private final Provider<ListServiceUsers> list;
 
   @Inject
   ServiceUserCollection(DynamicMap<RestView<AccountResource>> views,
-      CreateServiceUser.Factory createServiceUserFactory) {
+      CreateServiceUser.Factory createServiceUserFactory,
+      Provider<ListServiceUsers> list) {
     this.views = views;
     this.createServiceUserFactory = createServiceUserFactory;
+    this.list = list;
   }
 
   @Override
@@ -45,8 +49,8 @@
   }
 
   @Override
-  public RestView<ConfigResource> list() throws ResourceNotFoundException {
-    throw new ResourceNotFoundException();
+  public RestView<ConfigResource> list() {
+    return list.get();
   }
 
   @Override
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index 56fae99..4273cad 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -51,6 +51,51 @@
   }
 ```
 
+### <a id="list-service-users"> List Service Users
+GET /config/server/@PLUGIN@~serviceusers/_
+
+Lists service users.
+
+In order to see a service user the caller must have created that service
+user or be a member of a group that is granted the 'Administrate Server'
+capability.
+
+#### Request
+
+```
+  GET /config/server/@PLUGIN@~serviceusers/ HTTP/1.0
+```
+
+As response a map is returned that maps the username to a
+[ServiceUserInfo](#service-user-info) entity. The username in
+ServiceUserInfo is not set since it is already available as map key.
+
+#### Response
+
+```
+  HTTP/1.1 201 Created
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "GlobalVerifier": {
+      "created_by": "jdoe",
+      "created_at": "Mon, 27 Jan 2014 21:00:12 +0100",
+      "_account_id": 1000107,
+      "name": "GlobalVerifier",
+      "avatars": []
+    },
+    "JenkinsVoter": {
+      "created_by": "jdoe",
+      "created_at": "Thu, 21 Nov 2013 15:00:55 +0100",
+      "_account_id": 1000195,
+      "name": "JenkinsVoter",
+      "avatars": []
+    }
+  }
+```
+
 ### <a id="get-config"> Get Config
 _GET /config/server/@PLUGIN@~config_
 
@@ -113,6 +158,18 @@
 * _allow\_email_: Whether it is allowed to provide an email address for
   a service user (not set if `false`).
 
+### <a id="service-user-info"></a>ServiceUserInfo
+
+The `ServiceUserInfo` entity contains information about a service user.
+It has the same fields as a detailed
+[AccountInfo](../../../Documentation/rest-api-accounts.html#account-info)
+and in addition the following fields:
+
+* _created\_by_: The username of the user that created this service
+  user.
+* _created\_at_: The date when the service user was created in the
+  format 'EEE, dd MMM yyyy HH:mm:ss Z'.
+
 ### <a id="service-user-input"></a>ServiceUserInput
 
 The `ServiceUserInput` entity contains options for creating a service