Introduce defaultImage when the account does not have emails

When the Gerrit account did not have any emails, the avatar
was null which was creating strange situations where the icon
was taken from other accounts on the GUI.

Return always the Gravatar default image, so that it would be
clear that the user does not have a visual identity and would
not be assigned to other people's faces.

Change-Id: Ia5bde853ebbb4a48bc30db30a6f9f47dfe68d935
diff --git a/src/main/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProvider.java b/src/main/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProvider.java
index b481a99..56a4b92 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProvider.java
@@ -46,6 +46,7 @@
   private final String avatarRating;
   private final String gravatarUrl;
   private final String changeAvatarUrl;
+  private final boolean defaultImage;
 
   @Inject
   GravatarAvatarProvider(
@@ -69,25 +70,34 @@
       this.gravatarUrl =
           (canonicalUrl.startsWith("https://") ? "https://" : "http://") + gravatarUrlCfg;
     }
+
+    this.defaultImage = cfgFactory.getFromGerritConfig(pluginName).getBoolean("defaultImage", true);
   }
 
   @Override
   public String getUrl(IdentifiedUser forUser, int imageSize) {
-    if (forUser.getAccount().preferredEmail() == null) {
+    String preferredEmail = forUser.getAccount().preferredEmail();
+    if (preferredEmail == null && !defaultImage) {
       return null;
     }
-    final String email = forUser.getAccount().preferredEmail().trim().toLowerCase();
-    final byte[] emailMd5;
-    try {
-      MessageDigest digest = MessageDigest.getInstance("MD5");
-      emailMd5 = digest.digest(email.getBytes("UTF-8"));
-    } catch (UnsupportedEncodingException e) {
-      throw new RuntimeException("JVM lacks UTF-8 encoding", e);
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("MD5 digest not supported - required for Gravatar");
+
+    String emailMd5;
+    if (preferredEmail != null) {
+      final String email = preferredEmail.trim().toLowerCase();
+      try {
+        MessageDigest digest = MessageDigest.getInstance("MD5");
+        emailMd5 = hex(digest.digest(email.getBytes("UTF-8")));
+      } catch (UnsupportedEncodingException e) {
+        throw new RuntimeException("JVM lacks UTF-8 encoding", e);
+      } catch (NoSuchAlgorithmException e) {
+        throw new RuntimeException("MD5 digest not supported - required for Gravatar");
+      }
+    } else {
+      emailMd5 = "00000000000000000000000000000000";
     }
+
     StringBuilder url = new StringBuilder(gravatarUrl);
-    url.append(hex(emailMd5));
+    url.append(emailMd5);
     url.append(".jpg");
     url.append("?d=" + avatarType + "&r=" + avatarRating);
     if (imageSize > 0) {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index d9a0f67..2112afb 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -40,3 +40,10 @@
 :	Link to where the avatar can be changed as displayed in the profile settings.
 
 	Default: http://www.gravatar.com
+
+<a id="defaultImage">
+`plugin.@PLUGIN@.defaultImage`
+:	Whether to have a default image placeholder when the account does not have
+	a default email.
+
+	Default: true
diff --git a/src/test/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProviderIT.java b/src/test/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProviderIT.java
new file mode 100644
index 0000000..c5634cb
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/avatars/gravatar/GravatarAvatarProviderIT.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 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.avatars.gravatar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PropertyMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@UseLocalDisk
+@TestPlugin(
+    name = "avatars-gravatar",
+    sysModule =
+        "com.googlesource.gerrit.plugins.avatars.gravatar.GravatarAvatarProviderIT$TestModule")
+public class GravatarAvatarProviderIT extends LightweightPluginDaemonTest {
+  private static final int IMAGE_SIZE = 100;
+
+  @Inject IdentifiedUser.GenericFactory userFactory;
+
+  public static class TestModule extends AbstractModule {}
+
+  @Test
+  public void accountWithoutEmailShouldHaveImageByDefault() {
+    assertThat(gravatarAvatar().getUrl(testUserWithoutEmail(), IMAGE_SIZE)).isNotNull();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.avatars-gravatar.defaultImage", value = "false")
+  public void accountWithoutEmailShouldDisableDefaultImage() {
+    assertThat(gravatarAvatar().getUrl(testUserWithoutEmail(), IMAGE_SIZE)).isNull();
+  }
+
+  private GravatarAvatarProvider gravatarAvatar() {
+    return plugin.getSysInjector().getInstance(GravatarAvatarProvider.class);
+  }
+
+  private IdentifiedUser testUserWithoutEmail() {
+    return userFactory.forTest(Account.id(1), PropertyMap.EMPTY);
+  }
+}