Add notification when adding keys
When adding SSH/GPG keys, add a notification to the user to inform
them as an additional protection for their account should any
credentials be compromised.
Change-Id: Ia448af8da33dc0be3ba1acbb354ff3628630fe09
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 62d0219..da213a8 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -28,6 +28,12 @@
to a change being abandoned. It is a `ChangeEmail`: see `ChangeSubject.vm` and
`ChangeFooter.vm`.
+=== AddKey.vm
+
+The `AddKey.vm` template will determine the contents of the email related to
+SSH and GPG keys being added to a user account. This notification is not sent
+when the key is administratively added to another user account.
+
=== ChangeFooter.vm
The `ChangeFooter.vm` template will determine the contents of the footer
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 670adba..80e3500 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
@@ -27,6 +27,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -41,6 +42,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -54,6 +56,8 @@
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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -71,20 +75,24 @@
public List<String> delete;
}
+ private final Logger log = LoggerFactory.getLogger(getClass());
private final Provider<PersonIdent> serverIdent;
private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
private final PublicKeyChecker checker;
+ private final AddKeySender.Factory addKeyFactory;
@Inject
PostGpgKeys(@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<ReviewDb> db,
Provider<PublicKeyStore> storeProvider,
- PublicKeyChecker checker) {
+ PublicKeyChecker checker,
+ AddKeySender.Factory addKeyFactory) {
this.serverIdent = serverIdent;
this.db = db;
this.storeProvider = storeProvider;
this.checker = checker;
+ this.addKeyFactory = addKeyFactory;
}
@Override
@@ -180,6 +188,7 @@
Set<Fingerprint> toRemove) throws BadRequestException,
ResourceConflictException, PGPException, IOException {
try (PublicKeyStore store = storeProvider.get()) {
+ List<String> addedKeys = new ArrayList<>();
for (PGPPublicKeyRing keyRing : keyRings) {
PGPPublicKey key = keyRing.getPublicKey();
CheckResult result = checker.check(key);
@@ -188,6 +197,7 @@
"Problems with public key %s:\n%s",
keyToString(key), Joiner.on('\n').join(result.getProblems())));
}
+ addedKeys.add(PublicKeyStore.keyToString(key));
store.add(keyRing);
}
for (Fingerprint fp : toRemove) {
@@ -204,6 +214,13 @@
case NEW:
case FAST_FORWARD:
case FORCED:
+ try {
+ addKeyFactory.create(rsrc.getUser(), addedKeys).send();
+ } catch (EmailException e) {
+ log.error("Cannot send GPG key added message to "
+ + rsrc.getUser().getAccount().getPreferredEmail(), e);
+ }
+ break;
case NO_CHANGE:
break;
default:
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 8a227ac..6270a15 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
@@ -100,6 +100,7 @@
chmod(0700, site.tmp_dir);
extractMailExample("Abandoned.vm");
+ extractMailExample("AddKey.vm");
extractMailExample("ChangeFooter.vm");
extractMailExample("ChangeSubject.vm");
extractMailExample("Comment.vm");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 3c21d17..7ec659e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -18,6 +18,7 @@
import com.google.common.collect.Iterables;
import com.google.common.io.ByteSource;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -30,6 +31,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AddSshKey.Input;
import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -37,12 +39,17 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@Singleton
public class AddSshKey implements RestModifyView<AccountResource, Input> {
+ private static final Logger log = LoggerFactory.getLogger(AddSshKey.class);
+
public static class Input {
public RawInput raw;
}
@@ -50,13 +57,15 @@
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final SshKeyCache sshKeyCache;
+ private final AddKeySender.Factory addKeyFactory;
@Inject
AddSshKey(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
- SshKeyCache sshKeyCache) {
+ SshKeyCache sshKeyCache, AddKeySender.Factory addKeyFactory) {
this.self = self;
this.dbProvider = dbProvider;
this.sshKeyCache = sshKeyCache;
+ this.addKeyFactory = addKeyFactory;
}
@Override
@@ -96,6 +105,12 @@
sshKeyCache.create(new AccountSshKey.Id(
user.getAccountId(), max + 1), sshPublicKey);
dbProvider.get().accountSshKeys().insert(Collections.singleton(sshKey));
+ try {
+ addKeyFactory.create(user, sshKey).send();
+ } catch (EmailException e) {
+ log.error("Cannot send SSH key added message to "
+ + user.getAccount().getPreferredEmail(), e);
+ }
sshKeyCache.evict(user.getUserName());
return Response.<SshKeyInfo>created(new SshKeyInfo(sshKey));
} catch (InvalidSshKeyException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index cf1053d..3964115 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -95,6 +95,7 @@
import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.ReindexAfterUpdate;
import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -186,6 +187,7 @@
factory(AccountInfoCacheFactory.Factory.class);
factory(AddReviewerSender.Factory.class);
+ factory(AddKeySender.Factory.class);
factory(CapabilityControl.Factory.class);
factory(ChangeData.Factory.class);
factory(ChangeJson.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
new file mode 100644
index 0000000..0f1e86e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
@@ -0,0 +1,113 @@
+// 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.google.gerrit.server.mail;
+
+import com.google.common.base.Joiner;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import java.util.List;
+
+public class AddKeySender extends OutgoingEmail {
+ public interface Factory {
+ public AddKeySender create(IdentifiedUser user, AccountSshKey sshKey);
+
+ public AddKeySender create(IdentifiedUser user, List<String> gpgKey);
+ }
+
+ private final IdentifiedUser callingUser;
+ private final IdentifiedUser user;
+ private final AccountSshKey sshKey;
+ private final List<String> gpgKeys;
+
+ @AssistedInject
+ public AddKeySender(EmailArguments ea,
+ IdentifiedUser callingUser,
+ @Assisted IdentifiedUser user,
+ @Assisted AccountSshKey sshKey) {
+ super(ea, "addkey");
+ this.callingUser = callingUser;
+ this.user = user;
+ this.sshKey = sshKey;
+ this.gpgKeys = null;
+ }
+
+ @AssistedInject
+ public AddKeySender(EmailArguments ea,
+ IdentifiedUser callingUser,
+ @Assisted IdentifiedUser user,
+ @Assisted List<String> gpgKeys) {
+ super(ea, "addkey");
+ this.callingUser = callingUser;
+ this.user = user;
+ this.sshKey = null;
+ this.gpgKeys = gpgKeys;
+ }
+
+ @Override
+ protected void init() throws EmailException {
+ super.init();
+ setHeader("Subject",
+ String.format("[Gerrit Code Review] New %s Keys Added", getKeyType()));
+ add(RecipientType.TO, new Address(getEmail()));
+ }
+
+ @Override
+ protected boolean shouldSendMessage() {
+ /*
+ * Don't send an email if no keys are added, or an admin is adding a key to
+ * a user.
+ */
+ return (sshKey != null || gpgKeys.size() > 0) &&
+ (user.equals(callingUser) ||
+ !callingUser.getCapabilities().canAdministrateServer());
+ }
+
+ @Override
+ protected void format() throws EmailException {
+ appendText(velocifyFile("AddKey.vm"));
+ }
+
+ public String getEmail() {
+ return user.getAccount().getPreferredEmail();
+ }
+
+ public String getUserNameEmail() {
+ return getUserNameEmailFor(user.getAccountId());
+ }
+
+ public String getKeyType() {
+ if (sshKey != null) {
+ return "SSH";
+ } else if (gpgKeys != null) {
+ return "GPG";
+ }
+ return "Unknown";
+ }
+
+ public String getSshKey() {
+ return (sshKey != null) ? sshKey.getSshPublicKey() + "\n" : null;
+ }
+
+ public String getGpgKeys() {
+ if (gpgKeys != null) {
+ return Joiner.on("\n").join(gpgKeys);
+ }
+ return null;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index 1e4fec7..a2f369b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -268,6 +268,13 @@
return name;
}
+ /**
+ * Gets the human readable name and email for an account;
+ * if neither are available, returns the Anonymous Coward name.
+ *
+ * @param accountId user to fetch.
+ * @return name/email of account, or Anonymous Coward if unset.
+ */
public String getNameEmailFor(Account.Id accountId) {
AccountState who = args.accountCache.get(accountId);
String name = who.getAccount().getFullName();
@@ -286,6 +293,33 @@
}
}
+ /**
+ * Gets the human readable name and email for an account;
+ * if both are unavailable, returns the username. If no
+ * username is set, this function returns null.
+ *
+ * @param accountId user to fetch.
+ * @return name/email of account, username, or null if unset.
+ */
+ public String getUserNameEmailFor(Account.Id accountId) {
+ AccountState who = args.accountCache.get(accountId);
+ String name = who.getAccount().getFullName();
+ String email = who.getAccount().getPreferredEmail();
+
+ if (name != null && email != null) {
+ return name + " <" + email + ">";
+ } else if (email != null) {
+ return email;
+ } else if (name != null) {
+ return name;
+ }
+ String username = who.getUserName();
+ if (username != null) {
+ return username;
+ }
+ return null;
+ }
+
protected boolean shouldSendMessage() {
if (body.length() == 0) {
// If we have no message body, don't send.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index eb32700..beada69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -58,22 +58,7 @@
}
public String getUserNameEmail() {
- String name = user.getAccount().getFullName();
- String email = user.getAccount().getPreferredEmail();
-
- if (name != null && email != null) {
- return name + " <" + email + ">";
- } else if (email != null) {
- return email;
- } else if (name != null) {
- return name;
- } else {
- String username = user.getUserName();
- if (username != null) {
- return username;
- }
- }
- return null;
+ return getUserNameEmailFor(user.getAccountId());
}
public String getEmailRegistrationToken() {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm
new file mode 100644
index 0000000..c60ce8b
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm
@@ -0,0 +1,61 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The AddKey.vm template will determine the contents of the email
+## related to adding a new SSH or GPG key to an account.
+##
+One or more new ${email.keyType} keys have been added to Gerrit Code Review at ${email.gerritHost}:
+
+#if($email.sshKey)
+$email.sshKey
+#elseif($email.gpgKeys)
+$email.gpgKeys
+#end
+
+If this is not expected, please contact your Gerrit Administrators
+immediately.
+
+You can also manage your ${email.keyType} keys by visiting
+#if($email.sshKey)
+$email.gerritUrl#/settings/ssh-keys
+#elseif($email.gpgKeys)
+$email.gerritUrl#/settings/gpg-keys
+#end
+#if($email.userNameEmail)
+(while signed in as $email.userNameEmail)
+#else
+(while signed in as $email.email)
+#end
+
+If clicking the link above does not work, copy and paste the URL in a
+new browser window instead.
+
+This is a send-only email address. Replies to this message will not
+be read or answered.