Send email notification when SSH key or GPG key is removed

We already send a notification when a key is added. Also sending a
notification when a key is removed will help to alert the user if
their account was compromised and the key removed by an attacker.

Also-by: David Pursehouse <dpursehouse@collab.net>
Change-Id: I84f4bc5df5b6a609c1a17bad7c4a327cd780aa10
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 9eb31bf..db5228d 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -65,6 +65,12 @@
 will be appended to emails related to a user submitting comments on changes.
 See `ChangeSubject.soy`, Comment and ChangeFooter.
 
+=== DeleteKey.soy and DeleteKeyHtml.soy
+
+DeleteKey templates will determine the contents of the email related to SSH or GPG keys
+being deleted from a user account. This notification is not sent when the key is
+administratively deleted from another user account.
+
 === DeleteVote.soy and DeleteVoteHtml.soy
 
 The DeleteVote templates will determine the contents of the email related to
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 80e43de..ec96c4b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -1602,9 +1602,12 @@
     addGpgKey(key.getPublicKeyArmored());
     assertKeys(key);
 
+    sender.clear();
     gApi.accounts().self().gpgKey(id).delete();
     accountIndexedCounter.assertReindexOf(admin);
     assertKeys();
+    assertThat(sender.getMessages()).hasSize(1);
+    assertThat(sender.getMessages().get(0).body()).contains("GPG keys have been deleted");
 
     exception.expect(ResourceNotFoundException.class);
     exception.expectMessage(id);
@@ -1707,12 +1710,15 @@
     assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
 
     // Delete second key
+    sender.clear();
     gApi.accounts().self().deleteSshKey(2);
     info = gApi.accounts().self().listSshKeys();
     assertThat(info).hasSize(2);
     assertThat(info.get(0).seq).isEqualTo(1);
     assertThat(info.get(1).seq).isEqualTo(3);
     accountIndexedCounter.assertReindexOf(admin);
+    assertThat(sender.getMessages()).hasSize(1);
+    assertThat(sender.getMessages().get(0).body()).contains("SSH keys have been deleted");
   }
 
   // reindex is tested by {@link AbstractQueryAccountsTest#reindex}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index baf5a58..212b419 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -17,7 +17,9 @@
 import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -26,6 +28,7 @@
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -36,22 +39,29 @@
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RefUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
+  private static final Logger log = LoggerFactory.getLogger(DeleteGpgKey.class);
+
   public static class Input {}
 
   private final Provider<PersonIdent> serverIdent;
   private final Provider<PublicKeyStore> storeProvider;
   private final ExternalIdsUpdate.User externalIdsUpdateFactory;
+  private final DeleteKeySender.Factory deleteKeySenderFactory;
 
   @Inject
   DeleteGpgKey(
       @GerritPersonIdent Provider<PersonIdent> serverIdent,
       Provider<PublicKeyStore> storeProvider,
-      ExternalIdsUpdate.User externalIdsUpdateFactory) {
+      ExternalIdsUpdate.User externalIdsUpdateFactory,
+      DeleteKeySender.Factory deleteKeySenderFactory) {
     this.serverIdent = serverIdent;
     this.storeProvider = storeProvider;
     this.externalIdsUpdateFactory = externalIdsUpdateFactory;
+    this.deleteKeySenderFactory = deleteKeySenderFactory;
   }
 
   @Override
@@ -79,6 +89,16 @@
       switch (saveResult) {
         case NO_CHANGE:
         case FAST_FORWARD:
+          try {
+            deleteKeySenderFactory
+                .create(rsrc.getUser(), ImmutableList.of(PublicKeyStore.keyToString(key)))
+                .send();
+          } catch (EmailException e) {
+            log.error(
+                "Cannot send GPG key deletion message to {}",
+                rsrc.getUser().getAccount().getPreferredEmail(),
+                e);
+          }
           break;
         case FORCED:
         case IO_FAILURE:
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 77852ad..979691e 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
 import com.google.gerrit.server.mail.send.AddKeySender;
+import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -87,7 +88,8 @@
   private final Provider<CurrentUser> self;
   private final Provider<PublicKeyStore> storeProvider;
   private final GerritPublicKeyChecker.Factory checkerFactory;
-  private final AddKeySender.Factory addKeyFactory;
+  private final AddKeySender.Factory addKeySenderFactory;
+  private final DeleteKeySender.Factory deleteKeySenderFactory;
   private final Provider<InternalAccountQuery> accountQueryProvider;
   private final ExternalIds externalIds;
   private final ExternalIdsUpdate.User externalIdsUpdateFactory;
@@ -98,7 +100,8 @@
       Provider<CurrentUser> self,
       Provider<PublicKeyStore> storeProvider,
       GerritPublicKeyChecker.Factory checkerFactory,
-      AddKeySender.Factory addKeyFactory,
+      AddKeySender.Factory addKeySenderFactory,
+      DeleteKeySender.Factory deleteKeySenderFactory,
       Provider<InternalAccountQuery> accountQueryProvider,
       ExternalIds externalIds,
       ExternalIdsUpdate.User externalIdsUpdateFactory) {
@@ -106,7 +109,8 @@
     this.self = self;
     this.storeProvider = storeProvider;
     this.checkerFactory = checkerFactory;
-    this.addKeyFactory = addKeyFactory;
+    this.addKeySenderFactory = addKeySenderFactory;
+    this.deleteKeySenderFactory = deleteKeySenderFactory;
     this.accountQueryProvider = accountQueryProvider;
     this.externalIds = externalIds;
     this.externalIdsUpdateFactory = externalIdsUpdateFactory;
@@ -225,13 +229,24 @@
         case FORCED:
           if (!addedKeys.isEmpty()) {
             try {
-              addKeyFactory.create(user, addedKeys).send();
+              addKeySenderFactory.create(user, addedKeys).send();
             } catch (EmailException e) {
               log.error(
                   "Cannot send GPG key added message to " + user.getAccount().getPreferredEmail(),
                   e);
             }
           }
+          if (!toRemove.isEmpty()) {
+            try {
+              deleteKeySenderFactory
+                  .create(user, toRemove.stream().map(Fingerprint::toString).collect(toList()))
+                  .send();
+            } catch (EmailException e) {
+              log.error(
+                  "Cannot send GPG key deleted message to " + user.getAccount().getPreferredEmail(),
+                  e);
+            }
+          }
           break;
         case NO_CHANGE:
           break;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 647bb6b..a633335 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -115,6 +115,8 @@
     extractMailExample("CommentHtml.soy");
     extractMailExample("CommentFooter.soy");
     extractMailExample("CommentFooterHtml.soy");
+    extractMailExample("DeleteKey.soy");
+    extractMailExample("DeleteKeyHtml.soy");
     extractMailExample("DeleteReviewer.soy");
     extractMailExample("DeleteReviewerHtml.soy");
     extractMailExample("DeleteVote.soy");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
index 74616bf..c22e345 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.server.account;
 
+import com.google.gerrit.common.errors.EmailException;
 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.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.DeleteSshKey.Input;
+import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -30,26 +33,33 @@
 import java.io.IOException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Singleton
 public class DeleteSshKey implements RestModifyView<AccountResource.SshKey, Input> {
+  private static final Logger log = LoggerFactory.getLogger(DeleteSshKey.class);
+
   public static class Input {}
 
   private final Provider<CurrentUser> self;
   private final PermissionBackend permissionBackend;
   private final VersionedAuthorizedKeys.Accessor authorizedKeys;
   private final SshKeyCache sshKeyCache;
+  private final DeleteKeySender.Factory deleteKeySenderFactory;
 
   @Inject
   DeleteSshKey(
       Provider<CurrentUser> self,
       PermissionBackend permissionBackend,
       VersionedAuthorizedKeys.Accessor authorizedKeys,
-      SshKeyCache sshKeyCache) {
+      SshKeyCache sshKeyCache,
+      DeleteKeySender.Factory deleteKeySenderFactory) {
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.authorizedKeys = authorizedKeys;
     this.sshKeyCache = sshKeyCache;
+    this.deleteKeySenderFactory = deleteKeySenderFactory;
   }
 
   @Override
@@ -60,8 +70,17 @@
       permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
     }
 
-    authorizedKeys.deleteKey(rsrc.getUser().getAccountId(), rsrc.getSshKey().getKey().get());
-    sshKeyCache.evict(rsrc.getUser().getUserName());
+    IdentifiedUser user = rsrc.getUser();
+    authorizedKeys.deleteKey(user.getAccountId(), rsrc.getSshKey().getKey().get());
+
+    try {
+      deleteKeySenderFactory.create(user, rsrc.getSshKey()).send();
+    } catch (EmailException e) {
+      log.error(
+          "Cannot send SSH key deletion message to {}", user.getAccount().getPreferredEmail(), e);
+    }
+
+    sshKeyCache.evict(user.getUserName());
 
     return Response.none();
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
index 862293e..6b363c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.server.mail.send.AddReviewerSender;
 import com.google.gerrit.server.mail.send.CommentSender;
 import com.google.gerrit.server.mail.send.CreateChangeSender;
+import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.gerrit.server.mail.send.DeleteReviewerSender;
 import com.google.gerrit.server.mail.send.DeleteVoteSender;
 import com.google.gerrit.server.mail.send.MergedSender;
@@ -37,6 +38,7 @@
     factory(AddReviewerSender.Factory.class);
     factory(CommentSender.Factory.class);
     factory(CreateChangeSender.Factory.class);
+    factory(DeleteKeySender.Factory.class);
     factory(DeleteReviewerSender.Factory.class);
     factory(DeleteVoteSender.Factory.class);
     factory(MergedSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
new file mode 100644
index 0000000..c6a3532
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2019 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.google.gerrit.server.mail.send;
+
+import com.google.common.base.Joiner;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import java.util.Collections;
+import java.util.List;
+
+public class DeleteKeySender extends OutgoingEmail {
+  public interface Factory {
+    DeleteKeySender create(IdentifiedUser user, AccountSshKey sshKey);
+
+    DeleteKeySender create(IdentifiedUser user, List<String> gpgKeyFingerprints);
+  }
+
+  private final PermissionBackend permissionBackend;
+  private final IdentifiedUser callingUser;
+  private final IdentifiedUser user;
+  private final AccountSshKey sshKey;
+  private final List<String> gpgKeyFingerprints;
+
+  @AssistedInject
+  public DeleteKeySender(
+      EmailArguments ea,
+      PermissionBackend permissionBackend,
+      IdentifiedUser callingUser,
+      @Assisted IdentifiedUser user,
+      @Assisted AccountSshKey sshKey) {
+    super(ea, "deletekey");
+    this.permissionBackend = permissionBackend;
+    this.callingUser = callingUser;
+    this.user = user;
+    this.gpgKeyFingerprints = Collections.emptyList();
+    this.sshKey = sshKey;
+  }
+
+  @AssistedInject
+  public DeleteKeySender(
+      EmailArguments ea,
+      PermissionBackend permissionBackend,
+      IdentifiedUser callingUser,
+      @Assisted IdentifiedUser user,
+      @Assisted List<String> gpgKeyFingerprints) {
+    super(ea, "deletekey");
+    this.permissionBackend = permissionBackend;
+    this.callingUser = callingUser;
+    this.user = user;
+    this.gpgKeyFingerprints = gpgKeyFingerprints;
+    this.sshKey = null;
+  }
+
+  @Override
+  protected void init() throws EmailException {
+    super.init();
+    setHeader("Subject", String.format("[Gerrit Code Review] %s Keys Deleted", getKeyType()));
+    add(RecipientType.TO, new Address(getEmail()));
+  }
+
+  @Override
+  protected boolean shouldSendMessage() {
+    if (user.equals(callingUser)) {
+      // Send email if the user self-removed a key; this notification is necessary to alert
+      // the user if their account was compromised and a key was unexpectedly deleted.
+      return true;
+    }
+
+    try {
+      // Don't email if an administrator removed a key on behalf of the user.
+      permissionBackend.user(callingUser).check(GlobalPermission.ADMINISTRATE_SERVER);
+      return false;
+    } catch (AuthException | PermissionBackendException e) {
+      // Send email if a non-administrator modified the keys, e.g. by MODIFY_ACCOUNT.
+      return true;
+    }
+  }
+
+  @Override
+  protected void format() throws EmailException {
+    appendText(textTemplate("DeleteKey"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("DeleteKeyHtml"));
+    }
+  }
+
+  public String getEmail() {
+    return user.getAccount().getPreferredEmail();
+  }
+
+  public String getUserNameEmail() {
+    return getUserNameEmailFor(user.getAccountId());
+  }
+
+  public String getKeyType() {
+    if (sshKey != null) {
+      return "SSH";
+    } else if (gpgKeyFingerprints != null) {
+      return "GPG";
+    }
+    throw new IllegalStateException("key type is not SSH or GPG");
+  }
+
+  public String getSshKey() {
+    return (sshKey != null) ? sshKey.getSshPublicKey() + "\n" : null;
+  }
+
+  public String getGpgKeyFingerprints() {
+    if (!gpgKeyFingerprints.isEmpty()) {
+      return Joiner.on("\n").join(gpgKeyFingerprints);
+    }
+    return null;
+  }
+
+  @Override
+  protected void setupSoyContext() {
+    super.setupSoyContext();
+    soyContextEmailData.put("email", getEmail());
+    soyContextEmailData.put("gpgKeyFingerprints", getGpgKeyFingerprints());
+    soyContextEmailData.put("keyType", getKeyType());
+    soyContextEmailData.put("sshKey", getSshKey());
+    soyContextEmailData.put("userNameEmail", getUserNameEmail());
+  }
+
+  @Override
+  protected boolean supportsHtml() {
+    return true;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
index b267275..81074fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
@@ -47,6 +47,8 @@
     "CommentHtml.soy",
     "CommentFooter.soy",
     "CommentFooterHtml.soy",
+    "DeleteKey.soy",
+    "DeleteKeyHtml.soy",
     "DeleteReviewer.soy",
     "DeleteReviewerHtml.soy",
     "DeleteVote.soy",
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKey.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKey.soy
new file mode 100644
index 0000000..0fc4bf8
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKey.soy
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2019 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.
+ */
+
+{namespace com.google.gerrit.server.mail.template}
+
+/**
+ * The .DeleteKey template will determine the contents of the email related to
+ * deleting a SSH or GPG key.
+ * @param email
+ */
+{template .DeleteKey autoescape="strict" kind="text"}
+  One or more {$email.keyType} keys have been deleted on Gerrit Code Review at
+  {sp}{$email.gerritHost}:
+
+  {\n}
+  {\n}
+
+  {if $email.sshKey}
+    {$email.sshKey}
+  {elseif $email.gpgKeyFingerprints}
+    {$email.gpgKeyFingerprints}
+  {/if}
+
+  {\n}
+  {\n}
+
+
+  If this is not expected, please contact your Gerrit Administrators
+  immediately.
+
+  {\n}
+  {\n}
+
+  You can also manage your {$email.keyType} keys by visiting
+  {\n}
+  {if $email.sshKey}
+    {$email.gerritUrl}#/settings/ssh-keys
+  {elseif $email.gpgKey}
+    {$email.gerritUrl}#/settings/gpg-keys
+  {/if}
+  {\n}
+  {if $email.userNameEmail}
+    (while signed in as {$email.userNameEmail})
+  {else}
+    (while signed in as {$email.email})
+  {/if}
+
+  {\n}
+  {\n}
+
+  If clicking the link above does not work, copy and paste the URL in a new
+  browser window instead.
+
+  {\n}
+  {\n}
+
+  This is a send-only email address.  Replies to this message will not be read
+  or answered.
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
new file mode 100644
index 0000000..acdadad
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2019 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.
+ */
+
+{namespace com.google.gerrit.server.mail.template}
+
+/**
+ * @param email
+ */
+{template .DeleteKeyHtml autoescape="strict" kind="html"}
+  <p>
+    One or more {$email.keyType} keys have been deleted on Gerrit Code Review
+    at {$email.gerritHost}:
+  </p>
+
+  {let $keyStyle kind="css"}
+    background: #f0f0f0;
+    border: 1px solid #ccc;
+    color: #555;
+    padding: 12px;
+    width: 400px;
+  {/let}
+
+  {if $email.sshKey}
+    <pre style="{$keyStyle}">{$email.sshKey}</pre>
+  {elseif $email.gpgKeyFingerprints}
+    <pre style="{$keyStyle}">{$email.gpgKeyFingerprints}</pre>
+  {/if}
+
+  <p>
+    If this is not expected, please contact your Gerrit Administrators
+    immediately.
+  </p>
+
+  <p>
+    You can also manage your {$email.keyType} keys by following{sp}
+    {if $email.sshKey}
+      <a href="{$email.gerritUrl}#/settings/ssh-keys">this link</a>
+    {elseif $email.gpgKeyFingerprints}
+      <a href="{$email.gerritUrl}#/settings/gpg-keys">this link</a>
+    {/if}
+    {sp}
+    {if $email.userNameEmail}
+      (while signed in as {$email.userNameEmail})
+    {else}
+      (while signed in as {$email.email})
+    {/if}.
+  </p>
+
+  <p>
+    This is a send-only email address.  Replies to this message will not be read
+    or answered.
+  </p>
+{/template}