Merge stable-3.6 into master

Change-Id: I2b322fc6d3ab08b4c937bed9ebbc0eb7d969c49f
diff --git a/BUILD b/BUILD
index 81b7851..d4074e0 100644
--- a/BUILD
+++ b/BUILD
@@ -1,9 +1,6 @@
-load("@npm//@bazel/rollup:index.bzl", "rollup_bundle")
-load("//tools/bzl:js.bzl", "polygerrit_plugin")
-load("//tools/bzl:genrule2.bzl", "genrule2")
-load("//tools/js:eslint.bzl", "eslint")
 load("//tools/bzl:junit.bzl", "junit_tests")
 load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS", "gerrit_plugin")
+load("//tools/js:eslint.bzl", "eslint")
 
 gerrit_plugin(
     name = "serviceuser",
@@ -14,7 +11,7 @@
         "Gerrit-HttpModule: com.googlesource.gerrit.plugins.serviceuser.HttpModule",
         "Gerrit-SshModule: com.googlesource.gerrit.plugins.serviceuser.SshModule",
     ],
-    resource_jars = [":gr-serviceuser-static"],
+    resource_jars = ["//plugins/serviceuser/gr-serviceuser:serviceuser"],
     resources = glob(["src/main/resources/**/*"]),
 )
 
@@ -30,36 +27,6 @@
     ],
 )
 
-genrule2(
-    name = "gr-serviceuser-static",
-    srcs = [":gr-serviceuser"],
-    outs = ["gr-serviceuser-static.jar"],
-    cmd = " && ".join([
-        "mkdir $$TMP/static",
-        "cp -r $(locations :gr-serviceuser) $$TMP/static",
-        "cd $$TMP",
-        "zip -Drq $$ROOT/$@ -g .",
-    ]),
-)
-
-rollup_bundle(
-    name = "serviceuser-bundle",
-    srcs = glob(["gr-serviceuser/*.js"]),
-    entry_point = "gr-serviceuser/gr-serviceuser.js",
-    format = "iife",
-    rollup_bin = "//tools/node_tools:rollup-bin",
-    sourcemap = "hidden",
-    deps = [
-        "@tools_npm//rollup-plugin-node-resolve",
-    ],
-)
-
-polygerrit_plugin(
-    name = "gr-serviceuser",
-    app = "serviceuser-bundle.js",
-    plugin_name = "serviceuser",
-)
-
 # Define the eslinter for the plugin
 # The eslint macro creates 2 rules: lint_test and lint_bin
 eslint(
diff --git a/gr-serviceuser/BUILD b/gr-serviceuser/BUILD
new file mode 100644
index 0000000..484aafa
--- /dev/null
+++ b/gr-serviceuser/BUILD
@@ -0,0 +1,14 @@
+load("//tools/bzl:js.bzl", "gerrit_js_bundle")
+
+package(default_visibility = [":visibility"])
+
+package_group(
+    name = "visibility",
+    packages = ["//plugins/serviceuser/..."],
+)
+
+gerrit_js_bundle(
+    name = "serviceuser",
+    srcs = glob(["*.js"]),
+    entry_point = "gr-serviceuser.js",
+)
diff --git a/gr-serviceuser/gr-serviceuser-create.js b/gr-serviceuser/gr-serviceuser-create.js
index 4ca98d9..fa5144b 100644
--- a/gr-serviceuser/gr-serviceuser-create.js
+++ b/gr-serviceuser/gr-serviceuser-create.js
@@ -71,10 +71,7 @@
   }
 
   _forwardToDetails() {
-    page.show(
-        this.plugin.screenUrl()
-        + '/user/'
-        + this._accountId);
+    window.location.href = `${this.plugin.screenUrl()}/user/${this._accountId}`;
   }
 
   _getConfig() {
diff --git a/gr-serviceuser/gr-serviceuser-create_html.js b/gr-serviceuser/gr-serviceuser-create_html.js
index 1e408ab..2a9823b 100644
--- a/gr-serviceuser/gr-serviceuser-create_html.js
+++ b/gr-serviceuser/gr-serviceuser-create_html.js
@@ -18,6 +18,7 @@
 export const htmlTemplate = Polymer.html`
     <style include="shared-styles"></style>
     <style include="gr-subpage-styles"></style>
+    <style include="gr-font-styles"></style>
     <style include="gr-form-styles"></style>
     <style>
       main {
diff --git a/gr-serviceuser/gr-serviceuser-detail_html.js b/gr-serviceuser/gr-serviceuser-detail_html.js
index 7b2211c..e69cf9b 100644
--- a/gr-serviceuser/gr-serviceuser-detail_html.js
+++ b/gr-serviceuser/gr-serviceuser-detail_html.js
@@ -18,6 +18,7 @@
 export const htmlTemplate = Polymer.html`
     <style include="shared-styles"></style>
     <style include="gr-subpage-styles"></style>
+    <style include="gr-font-styles"></style>
     <style include="gr-form-styles"></style>
     <style>
       .heading {
diff --git a/gr-serviceuser/gr-serviceuser-list.js b/gr-serviceuser/gr-serviceuser-list.js
index a3c40c0..b579164 100644
--- a/gr-serviceuser/gr-serviceuser-list.js
+++ b/gr-serviceuser/gr-serviceuser-list.js
@@ -34,6 +34,10 @@
    */
   static get properties() {
     return {
+      _canCreate: {
+        type: Boolean,
+        value: false,
+      },
       _serviceUsers: Array,
       _loading: {
         type: Boolean,
@@ -54,9 +58,19 @@
         new CustomEvent(
             'title-change',
             {detail: {title: 'Service Users'}, bubbles: true, composed: true}));
+    this._getPermissions();
     this._getServiceUsers();
   }
 
+  _getPermissions() {
+    return this.plugin.restApi('/accounts/self/capabilities/').get('')
+        .then(capabilities => {
+          this._canCreate = capabilities
+              && (capabilities.administrateServer
+                  || capabilities['serviceuser-createServiceUser']);
+        });
+  }
+
   _getServiceUsers() {
     return this.plugin.restApi('/a/config/server/serviceuser~serviceusers/')
         .get('')
@@ -112,7 +126,7 @@
   }
 
   _createNewServiceUser() {
-    page.show(this.plugin.screenUrl() + '/create');
+    window.location.href = `${this.plugin.screenUrl()}/create`;
   }
 }
 
diff --git a/gr-serviceuser/gr-serviceuser-list_html.js b/gr-serviceuser/gr-serviceuser-list_html.js
index cc57ade..c69b4ff 100644
--- a/gr-serviceuser/gr-serviceuser-list_html.js
+++ b/gr-serviceuser/gr-serviceuser-list_html.js
@@ -17,6 +17,7 @@
 
 export const htmlTemplate = Polymer.html`
     <style include="shared-styles"></style>
+    <style include="gr-font-styles"></style>
     <style include="gr-table-styles"></style>
     <style>
       .topHeader {
@@ -41,7 +42,8 @@
         <h1 class="heading">Service Users</h1>
       </div>
       <div id="createNewContainer"
-           class$="[[_computeCreateClass(createNew)]]">
+           class$="[[_computeCreateClass(createNew)]]"
+           hidden$="[[!_canCreate]]">
         <gr-button primary
                    link
                    id="createNew"
diff --git a/gr-serviceuser/gr-serviceuser-ssh-panel.js b/gr-serviceuser/gr-serviceuser-ssh-panel.js
index a2db9c0..006bdd3 100644
--- a/gr-serviceuser/gr-serviceuser-ssh-panel.js
+++ b/gr-serviceuser/gr-serviceuser-ssh-panel.js
@@ -99,7 +99,8 @@
         this._newKey.trim(), null, 'plain/text')
         .then(key => {
           this.push('_keys', key);
-        }).catch(() => {
+          this._newKey = '';
+        }).finally(() => {
           this.$.addButton.disabled = false;
           this.$.newKey.disabled = false;
         });
diff --git a/gr-serviceuser/gr-serviceuser.js b/gr-serviceuser/gr-serviceuser.js
index 81b7329..6c95d9b 100644
--- a/gr-serviceuser/gr-serviceuser.js
+++ b/gr-serviceuser/gr-serviceuser.js
@@ -25,14 +25,13 @@
         if (capabilities
             && (capabilities.administrateServer
                 || capabilities['serviceuser-createServiceUser'])) {
-          plugin.screen('list', GrServiceUserList.is);
-          plugin.screen('user', GrServiceUserDetail.is);
           plugin.screen('create', GrServiceUserCreate.is);
         }
+        plugin.screen('list', GrServiceUserList.is);
+        plugin.screen('user', GrServiceUserDetail.is);
         plugin.admin()
             .addMenuLink(
                 'Service Users',
-                '/x/serviceuser/list',
-                'serviceuser-createServiceUser');
+                '/x/serviceuser/list');
       });
 });
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..f49b968
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,12 @@
+{
+  "name": "serviceuser",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "serviceuser",
+      "license": "Apache-2.0",
+      "devDependencies": {}
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/DeleteSshKey.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/DeleteSshKey.java
index 9ce83d3..3257c02 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/DeleteSshKey.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/DeleteSshKey.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -40,8 +39,6 @@
   public Response<?> apply(ServiceUserResource.SshKey rsrc, Input input)
       throws AuthException, RepositoryNotFoundException, IOException, ConfigInvalidException,
           PermissionBackendException {
-    return deleteSshKey
-        .get()
-        .apply(new AccountResource.SshKey(rsrc.getUser(), rsrc.getSshKey()), input);
+    return deleteSshKey.get().apply(rsrc.getUser(), rsrc.getSshKey());
   }
 }
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 16e200a..c4aa9e1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
@@ -67,6 +67,7 @@
     info.onSuccess = Strings.emptyToNull(cfg.getString("onSuccessMessage"));
     info.allowEmail = toBoolean(cfg.getBoolean("allowEmail", false));
     info.allowHttpPassword = toBoolean(cfg.getBoolean("allowHttpPassword", false));
+    info.allowCustomHttpPassword = toBoolean(cfg.getBoolean("allowCustomHttpPassword", false));
     info.allowOwner = toBoolean(cfg.getBoolean("allowOwner", false));
     info.createNotes = toBoolean(cfg.getBoolean("createNotes", true));
     info.createNotesAsync = toBoolean(cfg.getBoolean("createNotesAsync", false));
@@ -100,6 +101,7 @@
     public String onSuccess;
     public Boolean allowEmail;
     public Boolean allowHttpPassword;
+    public Boolean allowCustomHttpPassword;
     public Boolean allowOwner;
     public Boolean createNotes;
     public Boolean createNotesAsync;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetServiceUser.java
index 784c6b0..f0b92dd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetServiceUser.java
@@ -58,7 +58,7 @@
   @Override
   public Response<ServiceUserInfo> apply(ServiceUserResource rsrc)
       throws IOException, RestApiException, PermissionBackendException {
-    String username = rsrc.getUser().getUserName().get();
+    String username = rsrc.getUser().getUserName().orElseThrow(ResourceNotFoundException::new);
     Config db = storageCache.get();
     if (!db.getSubsections(USER).contains(username)) {
       throw new ResourceNotFoundException(username);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java
index 4ff0525..16cfab8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutHttpPassword.java
@@ -28,6 +28,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.serviceuser.GetConfig.ConfigInfo;
 import com.googlesource.gerrit.plugins.serviceuser.PutHttpPassword.Input;
 import java.io.IOException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -65,19 +66,19 @@
     }
     input.httpPassword = Strings.emptyToNull(input.httpPassword);
 
-    Boolean httpPasswordAllowed;
+    ConfigInfo config;
     try {
-      httpPasswordAllowed = getConfig.get().apply(new ConfigResource()).value().allowHttpPassword;
+      config = getConfig.get().apply(new ConfigResource()).value();
     } catch (Exception e) {
       throw asRestApiException("Cannot get configuration", e);
     }
 
-    if (input.generate || input.httpPassword == null) {
-      if ((httpPasswordAllowed == null || !httpPasswordAllowed)) {
+    if ((config.allowHttpPassword == null || !config.allowHttpPassword)) {
+      permissionBackend.user(self.get()).check(ADMINISTRATE_SERVER);
+    } else if (!input.generate && input.httpPassword != null) {
+      if ((config.allowCustomHttpPassword == null || !config.allowCustomHttpPassword)) {
         permissionBackend.user(self.get()).check(ADMINISTRATE_SERVER);
       }
-    } else {
-      permissionBackend.user(self.get()).check(ADMINISTRATE_SERVER);
     }
 
     String newPassword = input.generate ? generate() : input.httpPassword;
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index aa2e4b5..b66c181 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -59,6 +59,15 @@
     passwords for any service user.
     By default false.
 
+<a id="allowCustomHttpPassword"></a>
+`plugin.@PLUGIN@.allowCustomHttpPassword`
+:	Whether it is allowed for service user owners to set custom HTTP
+	passwords for their service users. This option requires
+	`plugin.@PLUGIN@.allowHttpPassword` to be true. Independent of this
+	setting Gerrit administrators are always able to set custom HTTP
+	passwords for any service user.
+	By default false.
+
 <a id="allowOwner"></a>
 `plugin.@PLUGIN@.allowOwner`
 :	Whether it is allowed to set an owner group for a service user.