Allow enabling of email address for service users

Per configuration it is now possible to define whether service users
are allowed to have an email address or not. Whether an email address
can be assigned for a service user is important because having an
email address e.g. allows the service user to push commits and tags
(if the corresponding access rights are assigned).

This renames the REST endpoints for getting and setting messages since
the REST endpoints are now handling also other configuration
parameters.

Change-Id: I2dcb4acbd05aea50087cd0b9ab31fc59592d8235
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 72af687..06ce09b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
@@ -64,6 +64,7 @@
   static class Input {
     String username;
     String sshKey;
+    String email;
   }
 
   public static interface Factory {
@@ -79,6 +80,7 @@
   private final Project.NameKey allProjects;
   private final ProjectLevelConfig storage;
   private final DateFormat rfc2822DateFormatter;
+  private final Provider<GetConfig> getConfig;
 
   @Inject
   CreateServiceUser(PluginConfigFactory cfgFactory,
@@ -88,7 +90,8 @@
       @GerritPersonIdent PersonIdent gerritIdent,
       MetaDataUpdate.User metaDataUpdateFactory,
       ProjectCache projectCache,
-      @Assisted String username) {
+      @Assisted String username,
+      Provider<GetConfig> getConfig) {
     this.cfg = cfgFactory.getFromGerritConfig(pluginName);
     this.createAccountFactory = createAccountFactory;
     this.username = username;
@@ -108,6 +111,7 @@
         new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
     this.rfc2822DateFormatter.setCalendar(Calendar.getInstance(
         gerritIdent.getTimeZone(), Locale.US));
+    this.getConfig = getConfig;
   }
 
   @Override
@@ -129,8 +133,16 @@
           + "' is not allowed as name for service users.");
     }
 
+    input.email = Strings.emptyToNull(input.email);
+    if (input.email != null) {
+      Boolean emailAllowed = getConfig.get().apply(new ConfigResource()).allowEmail;
+      if (emailAllowed == null || !emailAllowed) {
+        throw new ResourceConflictException("email not allowed");
+      }
+    }
+
     CreateAccount.Input in =
-        new ServiceUserInput(username, input.sshKey, cfg);
+        new ServiceUserInput(username, input.email, input.sshKey, cfg);
     Response<AccountInfo> response =
         createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, in);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
similarity index 78%
rename from src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java
rename to src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
index 1ce5ad7..40d6137 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetConfig.java
@@ -22,26 +22,31 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 
-public class GetMessages implements RestReadView<ConfigResource> {
+public class GetConfig implements RestReadView<ConfigResource> {
 
   private final PluginConfig cfg;
 
   @Inject
-  public GetMessages(PluginConfigFactory cfgFactory,
+  public GetConfig(PluginConfigFactory cfgFactory,
       @PluginName String pluginName) {
     this.cfg = cfgFactory.getFromGerritConfig(pluginName);
   }
 
   @Override
-  public MessagesInfo apply(ConfigResource rsrc) {
-    MessagesInfo info = new MessagesInfo();
+  public ConfigInfo apply(ConfigResource rsrc) {
+    ConfigInfo info = new ConfigInfo();
     info.info = Strings.emptyToNull(cfg.getString("infoMessage"));
     info.onSuccess = Strings.emptyToNull(cfg.getString("onSuccessMessage"));
+    info.allowEmail = cfg.getBoolean("allowEmail", false);
+    if (!info.allowEmail) {
+      info.allowEmail = null;
+    }
     return info;
   }
 
-  public class MessagesInfo {
+  public class ConfigInfo {
     String info;
     String onSuccess;
+    Boolean allowEmail;
   }
 }
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 c75c52f..a76a0ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
@@ -38,8 +38,8 @@
         bind(ServiceUserCollection.class);
         child(CONFIG_KIND, "serviceusers").to(ServiceUserCollection.class);
         install(new FactoryModuleBuilder().build(CreateServiceUser.Factory.class));
-        get(CONFIG_KIND, "messages").to(GetMessages.class);
-        put(CONFIG_KIND, "messages").to(PutMessages.class);
+        get(CONFIG_KIND, "config").to(GetConfig.class);
+        put(CONFIG_KIND, "config").to(PutConfig.class);
       }
     });
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutMessages.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
similarity index 84%
rename from src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutMessages.java
rename to src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
index e34852e..e966590 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutMessages.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/PutConfig.java
@@ -25,7 +25,7 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 
-import com.googlesource.gerrit.plugins.serviceuser.PutMessages.Input;
+import com.googlesource.gerrit.plugins.serviceuser.PutConfig.Input;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -34,10 +34,11 @@
 import java.io.IOException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-public class PutMessages implements RestModifyView<ConfigResource, Input>{
+public class PutConfig implements RestModifyView<ConfigResource, Input>{
   public static class Input {
     public String info;
     public String onSuccess;
+    public Boolean allowEmail;
   }
 
   private final PluginConfigFactory cfgFactory;
@@ -45,7 +46,7 @@
   private final String pluginName;
 
   @Inject
-  PutMessages(PluginConfigFactory cfgFactory, SitePaths sitePaths,
+  PutConfig(PluginConfigFactory cfgFactory, SitePaths sitePaths,
       @PluginName String pluginName) throws IOException, ConfigInvalidException {
     this.cfgFactory = cfgFactory;
     this.sitePaths = sitePaths;
@@ -66,6 +67,13 @@
       cfg.setString("plugin", pluginName, "onSuccessMessage",
           Strings.emptyToNull(input.onSuccess));
     }
+    if (input.allowEmail != null) {
+      if (input.allowEmail) {
+        cfg.setBoolean("plugin", pluginName, "allowEmail", true);
+      } else {
+        cfg.unset("plugin", pluginName, "allowEmail");
+      }
+    }
     cfg.save();
     cfgFactory.getFromGerritConfig(pluginName, true);
     return Response.<String> ok("OK");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserInput.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserInput.java
index 8fb6122..b0e006d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserInput.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/ServiceUserInput.java
@@ -21,9 +21,11 @@
 
 public class ServiceUserInput extends CreateAccount.Input {
 
-  public ServiceUserInput(String username, String sshKey, PluginConfig cfg) {
+  public ServiceUserInput(String username, String email, String sshKey,
+      PluginConfig cfg) {
     this.username = username;
     this.name = username;
+    this.email = email;
     this.sshKey = sshKey;
     this.groups = Arrays.asList(cfg.getStringList("group"));
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/MessagesInfo.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ConfigInfo.java
similarity index 76%
rename from src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/MessagesInfo.java
rename to src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ConfigInfo.java
index 767d83f..95305b6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/MessagesInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ConfigInfo.java
@@ -16,18 +16,20 @@
 
 import com.google.gwt.core.client.JavaScriptObject;
 
-public class MessagesInfo extends JavaScriptObject {
+public class ConfigInfo extends JavaScriptObject {
   final native String getInfoMessage() /*-{ return this.info }-*/;
   final native String getOnSuccessMessage() /*-{ return this.on_success }-*/;
+  final native boolean getAllowEmail() /*-{ return this.allow_email ? true : false; }-*/;
 
   final native void setInfoMessage(String s) /*-{ this.info = s; }-*/;
   final native void setOnSuccessMessage(String s) /*-{ this.on_success = s; }-*/;
+  final native void setAllowEmail(boolean s) /*-{ this.allow_email = s; }-*/;
 
-  static MessagesInfo create() {
-    MessagesInfo g = (MessagesInfo) createObject();
+  static ConfigInfo create() {
+    ConfigInfo g = (ConfigInfo) createObject();
     return g;
   }
 
-  protected MessagesInfo() {
+  protected ConfigInfo() {
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserScreen.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserScreen.java
index 3a9155f..5242c60 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserScreen.java
@@ -47,6 +47,7 @@
   }
 
   private TextBox usernameTxt;
+  private TextBox emailTxt;
   private TextArea sshKeyTxt;
   private String onSuccessMessage;
 
@@ -136,16 +137,47 @@
     usernameTxt.setFocus(true);
     createButton.setEnabled(false);
 
-    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "messages")
-        .get(new AsyncCallback<MessagesInfo>() {
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config")
+        .get(new AsyncCallback<ConfigInfo>() {
           @Override
-          public void onSuccess(MessagesInfo info) {
+          public void onSuccess(ConfigInfo info) {
             onSuccessMessage = info.getOnSuccessMessage();
 
             String infoMessage = info.getInfoMessage();
             if (infoMessage != null && !"".equals(infoMessage)) {
               insert(new HTML(infoMessage), 0);
             }
+
+            if (info.getAllowEmail()) {
+              Panel emailPanel = new VerticalPanel();
+              emailPanel.add(new Label("Email:"));
+              emailTxt = new TextBox() {
+                @Override
+                public void onBrowserEvent(Event event) {
+                  super.onBrowserEvent(event);
+                  if (event.getTypeInt() == Event.ONPASTE) {
+                    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+                      @Override
+                      public void execute() {
+                        if (getValue().trim().length() != 0) {
+                          setEnabled(true);
+                        }
+                      }
+                    });
+                  }
+                }
+              };
+              emailTxt.addKeyPressHandler(new KeyPressHandler() {
+                @Override
+                public void onKeyPress(final KeyPressEvent event) {
+                  event.stopPropagation();
+                }
+              });
+              emailTxt.sinkEvents(Event.ONPASTE);
+              emailTxt.setVisibleLength(40);
+              emailPanel.add(emailTxt);
+              insert(emailPanel, 2);
+            }
           }
 
           @Override
@@ -164,6 +196,9 @@
 
     ServiceUserInput in = ServiceUserInput.create();
     in.ssh_key(sshKey);
+    if (emailTxt != null) {
+      in.email(emailTxt.getValue().trim());
+    }
     new RestApi("config").id("server").view("serviceuser", "serviceusers")
         .id(username).post(in, new AsyncCallback<JavaScriptObject>() {
 
@@ -204,11 +239,15 @@
 
   private void clearForm() {
     usernameTxt.setValue("");
+    if (emailTxt != null) {
+      emailTxt.setValue("");
+    }
     sshKeyTxt.setValue("");
   }
 
   private static class ServiceUserInput extends JavaScriptObject {
     final native void ssh_key(String s) /*-{ this.ssh_key = s; }-*/;
+    final native void email(String e) /*-{ this.email = e; }-*/;
 
     static ServiceUserInput create() {
       ServiceUserInput g = (ServiceUserInput) createObject();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ServiceUserAdminScreen.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ServiceUserAdminScreen.java
index 7b87da2..7023bc5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ServiceUserAdminScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/ServiceUserAdminScreen.java
@@ -24,6 +24,7 @@
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.Label;
@@ -42,15 +43,16 @@
 
   private TextArea infoMsgTxt;
   private TextArea onSuccessMsgTxt;
+  private CheckBox allowEmailCheckBox;
   private Button saveButton;
 
   ServiceUserAdminScreen() {
     setStyleName("serviceuser-panel");
 
-    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "messages")
-        .get(new AsyncCallback<MessagesInfo>() {
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config")
+        .get(new AsyncCallback<ConfigInfo>() {
           @Override
-          public void onSuccess(MessagesInfo info) {
+          public void onSuccess(ConfigInfo info) {
             display(info);
           }
 
@@ -61,7 +63,7 @@
         });
   }
 
-  private void display(MessagesInfo info) {
+  private void display(ConfigInfo info) {
     Panel infoMsgPanel = new VerticalPanel();
     Panel infoMsgTitelPanel = new HorizontalPanel();
     infoMsgTitelPanel.add(new Label("Info Message"));
@@ -108,6 +110,17 @@
     onSuccessMsgPanel.add(onSuccessMsgTxt);
     add(onSuccessMsgPanel);
 
+    Panel allowEmailPanel = new HorizontalPanel();
+    allowEmailCheckBox = new CheckBox("Allow Email Address");
+    allowEmailCheckBox.setValue(info.getAllowEmail());
+    allowEmailPanel.add(allowEmailCheckBox);
+    Image allowEmailInfo = new Image(ServiceUserPlugin.RESOURCES.info());
+    allowEmailInfo.setTitle("Whether it is allowed to provide an email address "
+        + "for a service user. E.g. having an email address allows a service user "
+        + "to push commits and tags.");
+    allowEmailPanel.add(allowEmailInfo);
+    add(allowEmailPanel);
+
     HorizontalPanel buttons = new HorizontalPanel();
     add(buttons);
 
@@ -123,16 +136,18 @@
     saveButton.setEnabled(false);
     OnEditEnabler onEditEnabler = new OnEditEnabler(saveButton, infoMsgTxt);
     onEditEnabler.listenTo(onSuccessMsgTxt);
+    onEditEnabler.listenTo(allowEmailCheckBox);
 
     infoMsgTxt.setFocus(true);
     saveButton.setEnabled(false);
   }
 
   private void doSave() {
-    MessagesInfo in = MessagesInfo.create();
+    ConfigInfo in = ConfigInfo.create();
     in.setInfoMessage(infoMsgTxt.getValue());
     in.setOnSuccessMessage(onSuccessMsgTxt.getValue());
-    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "messages")
+    in.setAllowEmail(allowEmailCheckBox.getValue());
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config")
         .put(in, new AsyncCallback<JavaScriptObject>() {
 
           @Override
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index ff65add..04c8ba2 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -31,3 +31,8 @@
 `plugin.@PLUGIN@.onSuccessMessage`
 :	HTML formatted message that should be displayed after a service
 	user was successfully created.
+
+<a id="allowEmail">
+`plugin.@PLUGIN@.allowEmail`
+:	Whether it is allowed to provide an email address for
+	a service user. By default false.
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index 80932a8..56fae99 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -51,20 +51,19 @@
   }
 ```
 
-### <a id="get-messages"> Get Messages
-_GET /config/server/@PLUGIN@~messages_
+### <a id="get-config"> Get Config
+_GET /config/server/@PLUGIN@~config_
 
-Gets help messages to be displayed for the service user creation in the
-Web UI.
+Gets the configuration of the @PLUGIN@ plugin.
 
 #### Request
 
 ```
-  GET /config/server/@PLUGIN@~messages HTTP/1.0
+  GET /config/server/@PLUGIN@~config HTTP/1.0
 ```
 
-As response a [MessagesInfo](#messages-info) entity is returned that
-contains the messages.
+As response a [ConfigInfo](#config-info) entity is returned that
+contains the configuration.
 
 #### Response
 
@@ -79,20 +78,19 @@
   }
 ```
 
-### <a id="put-messages"> Put Messages
-_PUT /config/server/@PLUGIN@~messages_
+### <a id="put-config"> Put Config
+_PUT /config/server/@PLUGIN@~config_
 
-Sets the help messages that are displayed for the service user creation
-in the Web UI.
+Sets the configuration of the @PLUGIN@ plugin.
 
-The new messages must be specified as a [MessagesInfo](#messages-info)
-entity in the request body. Not setting a message leaves the message
+The new configuration must be specified as a [ConfigInfo](#config-info)
+entity in the request body. Not setting a parameter leaves the parameter
 unchanged.
 
 #### Request
 
 ```
-  PUT /config/server/@PLUGIN@~messages HTTP/1.0
+  PUT /config/server/@PLUGIN@~config HTTP/1.0
   Content-Type: application/json;charset=UTF-8
 
   {
@@ -104,15 +102,16 @@
 <a id="json-entities">JSON Entities
 -----------------------------------
 
-### <a id="messages-info"></a>MessagesInfo
+### <a id="config-info"></a>ConfigInfo
 
-The `MessagesInfo` entity contains help messages that should be
-displayed for the service user creation in the Web UI.
+The `ConfigInfo` entity contains configuration of the @PLUGIN@ plugin.
 
 * _info_: HTML formatted message that should be displayed in the
   service user creation dialog.
 * _on\_success_: HTML formatted message that should be displayed after
   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`).
 
 ### <a id="service-user-input"></a>ServiceUserInput