diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt index 62d0219116..da213a8376 100644 --- a/Documentation/config-mail.txt +++ b/Documentation/config-mail.txt @@ -28,6 +28,12 @@ The `Abandoned.vm` template will determine the contents of the email related 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 670adbaea2..80e35002cf 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.Lists; 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.client.AccountExternalId; 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.bouncycastle.openpgp.bc.BcPGPObjectFactory; 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 class PostGpgKeys implements RestModifyView { public List delete; } + private final Logger log = LoggerFactory.getLogger(getClass()); private final Provider serverIdent; private final Provider db; private final Provider storeProvider; private final PublicKeyChecker checker; + private final AddKeySender.Factory addKeyFactory; @Inject PostGpgKeys(@GerritPersonIdent Provider serverIdent, Provider db, Provider 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 @@ public class PostGpgKeys implements RestModifyView { Set toRemove) throws BadRequestException, ResourceConflictException, PGPException, IOException { try (PublicKeyStore store = storeProvider.get()) { + List addedKeys = new ArrayList<>(); for (PGPPublicKeyRing keyRing : keyRings) { PGPPublicKey key = keyRing.getPublicKey(); CheckResult result = checker.check(key); @@ -188,6 +197,7 @@ public class PostGpgKeys implements RestModifyView { "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 @@ public class PostGpgKeys implements RestModifyView { 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 8a227acbf8..6270a15a1f 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 @@ public class SitePathInitializer { 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 3c21d17558..7ec659ee2f 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 static java.nio.charset.StandardCharsets.UTF_8; 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.CurrentUser; 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.Inject; 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 { + private static final Logger log = LoggerFactory.getLogger(AddSshKey.class); + public static class Input { public RawInput raw; } @@ -50,13 +57,15 @@ public class AddSshKey implements RestModifyView { private final Provider self; private final Provider dbProvider; private final SshKeyCache sshKeyCache; + private final AddKeySender.Factory addKeyFactory; @Inject AddSshKey(Provider self, Provider 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 @@ public class AddSshKey implements RestModifyView { 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.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 cf1053d5ad..3964115384 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.git.validators.UploadValidators; 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 @@ public class GerritGlobalModule extends FactoryModule { 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 0000000000..0f1e86ebda --- /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 gpgKey); + } + + private final IdentifiedUser callingUser; + private final IdentifiedUser user; + private final AccountSshKey sshKey; + private final List 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 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 1e4fec7f46..a2f369bf4e 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 @@ public abstract class OutgoingEmail { 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 @@ public abstract class OutgoingEmail { } } + /** + * 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 eb32700c50..beada69795 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 class RegisterNewEmailSender extends OutgoingEmail { } 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 0000000000..c60ce8b14a --- /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.