Merge "Add REST endpoints to get/set/delete HTTP passwords for service users" into stable-2.11
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
index 639c025..6ee3d6f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
@@ -62,6 +62,7 @@
     info.info = Strings.emptyToNull(cfg.getString("infoMessage"));
     info.onSuccess = Strings.emptyToNull(cfg.getString("onSuccessMessage"));
     info.allowEmail = toBoolean(cfg.getBoolean("allowEmail", false));
+    info.allowHttpPassword = toBoolean(cfg.getBoolean("allowHttpPassword", false));
     info.allowOwner = toBoolean(cfg.getBoolean("allowOwner", false));
     info.createNotes = toBoolean(cfg.getBoolean("createNotes", true));
     info.createNotesAsync = toBoolean(cfg.getBoolean("createNotesAsync", false));
@@ -94,6 +95,7 @@
     public String info;
     public String onSuccess;
     public Boolean allowEmail;
+    public Boolean allowHttpPassword;
     public Boolean allowOwner;
     public Boolean createNotes;
     public Boolean createNotesAsync;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetHttpPassword.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetHttpPassword.java
new file mode 100644
index 0000000..6cf4754
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetHttpPassword.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2015 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.base.Strings;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.AccountState;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetHttpPassword implements RestReadView<ServiceUserResource> {
+
+  @Override
+  public String apply(ServiceUserResource rsrc)
+      throws ResourceNotFoundException {
+    AccountState s = rsrc.getUser().state();
+    if (s.getUserName() == null) {
+      throw new ResourceNotFoundException();
+    }
+    return Strings.nullToEmpty(s.getPassword(s.getUserName()));
+  }
+}
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 66dd394..aeaff7d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
@@ -63,6 +63,9 @@
         get(SERVICE_USER_KIND, "email").to(GetEmail.class);
         put(SERVICE_USER_KIND, "email").to(PutEmail.class);
         delete(SERVICE_USER_KIND, "email").to(PutEmail.class);
+        get(SERVICE_USER_KIND, "password.http").to(GetHttpPassword.class);
+        put(SERVICE_USER_KIND, "password.http").to(PutHttpPassword.class);
+        delete(SERVICE_USER_KIND, "password.http").to(PutHttpPassword.class);
         get(SERVICE_USER_KIND, "active").to(GetActive.class);
         put(SERVICE_USER_KIND, "active").to(PutActive.class);
         delete(SERVICE_USER_KIND, "active").to(DeleteActive.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
index 4986cec..3f6fbb0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
@@ -46,6 +46,7 @@
     public String info;
     public String onSuccess;
     public Boolean allowEmail;
+    public Boolean allowHttpPassword;
     public Boolean allowOwner;
     public Boolean createNotes;
     public Boolean createNotesAsync;
@@ -84,6 +85,9 @@
     if (input.allowEmail != null) {
       setBoolean(cfg, "allowEmail", input.allowEmail);
     }
+    if (input.allowHttpPassword != null) {
+      setBoolean(cfg, "allowHttpPassword", input.allowHttpPassword);
+    }
     if (input.allowOwner != null) {
       setBoolean(cfg, "allowOwner", input.allowOwner);
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java
new file mode 100644
index 0000000..6294780
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2015 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.base.Strings;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import com.googlesource.gerrit.plugins.serviceuser.PutHttpPassword.Input;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+@Singleton
+public class PutHttpPassword implements RestModifyView<ServiceUserResource, Input> {
+  public static class Input {
+    public String httpPassword;
+    public boolean generate;
+  }
+
+  private static final int LEN = 31;
+  private static final SecureRandom rng;
+
+  static {
+    try {
+      rng = SecureRandom.getInstance("SHA1PRNG");
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("Cannot create RNG for password generator", e);
+    }
+  }
+
+  private final Provider<GetConfig> getConfig;
+  private final com.google.gerrit.server.account.PutHttpPassword putHttpPassword;
+  private final Provider<CurrentUser> self;
+
+  @Inject
+  PutHttpPassword(Provider<GetConfig> getConfig,
+      com.google.gerrit.server.account.PutHttpPassword putHttpPassword,
+      Provider<CurrentUser> self) {
+    this.getConfig = getConfig;
+    this.putHttpPassword = putHttpPassword;
+    this.self = self;
+  }
+
+  @Override
+  public Response<String> apply(ServiceUserResource rsrc, Input input)
+      throws AuthException, ResourceConflictException,
+      ResourceNotFoundException, OrmException {
+    if (input == null) {
+      input = new Input();
+    }
+    input.httpPassword = Strings.emptyToNull(input.httpPassword);
+
+    Boolean httpPasswordAllowed = getConfig.get().apply(new ConfigResource()).allowHttpPassword;
+    if (input.generate || input.httpPassword == null) {
+      if ((httpPasswordAllowed == null || !httpPasswordAllowed)
+          && !self.get().getCapabilities().canAdministrateServer()) {
+        throw new ResourceConflictException("not allowed to generate HTTP password");
+      }
+    } else {
+      if (!self.get().getCapabilities().canAdministrateServer()) {
+        throw new AuthException("not allowed to set HTTP password directly, "
+            + "requires the Administrate Server permission");
+      }
+    }
+
+    String newPassword = input.generate ? generate() : input.httpPassword;
+    return putHttpPassword.apply(rsrc.getUser(), newPassword);
+  }
+
+  private static String generate() {
+    byte[] rand = new byte[LEN];
+    rng.nextBytes(rand);
+
+    byte[] enc = Base64.encodeBase64(rand, false);
+    StringBuilder r = new StringBuilder(enc.length);
+    for (int i = 0; i < enc.length; i++) {
+      if (enc[i] == '=') {
+        break;
+      }
+      r.append((char) enc[i]);
+    }
+    return r.toString();
+  }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 04b9501..6b358ac 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -37,6 +37,14 @@
 :	Whether it is allowed to provide an email address for
 	a service user. By default false.
 
+<a id="allowHttpPassword">
+`plugin.@PLUGIN@.allowHttpPassword`
+:	Whether it is allowed for service user owners to generate HTTP
+    passwords for their service users. Independent of this setting
+    Gerrit administrators are always able to set/generate HTTP
+    passwords for any service user.
+    By default false.
+
 <a id="allowOwner">
 `plugin.@PLUGIN@.allowOwner`
 :	Whether it is allowed to set an owner group for a service user.
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index f8643a1..c5958c7 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -425,6 +425,89 @@
 Some realms may not allow to modify the email. In this case the
 request is rejected with "`405 Method Not Allowed`".
 
+### <a id="get-http-password"> Get HTTP password
+GET
+/config/server/@PLUGIN@~serviceusers/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)/password.http_
+
+Retrieves the HTTP password of a service user.
+
+#### Request
+
+```
+  GET /config/server/@PLUGIN@~serviceusers/JenkinsVoter/password.http HTTP/1.0
+```
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "6A5zoF7Bf2fMggf4R6pO3Bzgchnwl6oBI8+yA3YJUA"
+```
+
+If the service user does not have an HTTP password an empty string is returned.
+
+### <a id="set-http-password"> Set/Generate HTTP password
+PUT
+/config/server/@PLUGIN@~serviceusers/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)/password.http_
+
+Sets/Generates an HTTP password for a service user.
+
+The options for setting/generating the HTTP password must be provided
+in the request body inside a [HttpPasswordInput](#http-password-input)
+entity.
+
+#### Request
+
+```
+  PUT /config/server/@PLUGIN@~serviceusers/JenkinsVoter/password.http HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "generate": true
+  }
+```
+
+As response the new HTTP password is returned.
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "6A5zoF7Bf2fMggf4R6pO3Bzgchnwl6oBI8+yA3YJUA"
+```
+
+If the HTTP password was deleted the response is "`204 No Content`".
+
+
+### <a id="delete-http-password"> Delete HTTP password
+DELETE
+/config/server/@PLUGIN@~serviceusers/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)/password.http
+
+Clears the HTTP password of a service user.
+
+#### Request
+
+```
+  DELETE
+  /config/server/@PLUGIN@~serviceusers/JenkinsVoter/password.http HTTP/1.0
+```
+
+As response "`204 No Content`" is returned.
+
+#### Response
+
+```
+  HTTP/1.1 204 No Content
+```
+
 ### <a id="get-active"> Get Active
 GET /config/server/@PLUGIN@~serviceusers/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)/active_
 
@@ -652,6 +735,8 @@
   a service user was successfully created.
 * _allow\_email_: Whether it is allowed to provide an email address for
   a service user (not set if `false`).
+* _allow\_http\_password_: Whether it is allowed to generate an HTTP
+  password for a service user (not set if `false`).
 * _create\_notes_: Whether commits of a service user should be
   annotated by a Git note that contains information about the current
   owners of the service user (not set if `false`).
@@ -677,6 +762,8 @@
   a service user was successfully created.
 * _allow\_email_: Whether it is allowed to provide an email address for
   a service user (not set if `false`).
+* _allow\_http\_password_: Whether it is allowed to generate an HTTP
+  password for a service user (not set if `false`).
 * _allow\_owner_: Whether it is allowed to set an owner group for a
   service user (not set if `false`).
 * _create\_notes_: Whether commits of a service user should be
@@ -696,6 +783,14 @@
 
 * _email_: The new email address.
 
+### <a id="http-password-input"></a>HttpPasswordInput
+
+The `HttpPasswordInput` entity contains information for
+setting/generating an HTTP password.
+
+* _generate_: Whether a new HTTP password should be generated.
+* _http\_password_: The new HTTP password.
+
 ### <a id="owner-input"></a>OwnerInput
 
 The `OwnerInput` entity contains a group that should own a service user.