Add REST endpoints to get/set/delete HTTP passwords for service users
Whether it is allowed to generate HTTP passwords for service users can
be controlled by a new config option.
Change-Id: Icde6b506f66c8866ba986fca7b5218a6d5c51ebd
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
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 f6232b2..a134a89 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.