Send an email notification when the HTTP password is deleted or changed
We already send a notification when an account's SSH/GPG keys are added
or removed. Also send a notification when the HTTP password is changed
or deleted. This will alert the user if their account is compromised
and the HTTP password is altered by an attacker.
Also-by: David Pursehouse <dpursehouse@collab.net>
Change-Id: Iaf0e7900c98f6e29b5d609fc7d43797e7e76d1ec
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index db5228d..91c7abb 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -88,6 +88,11 @@
The Footer templates will determine the contents of the footer text appended to
the end of all outgoing emails after the ChangeFooter and CommentFooter.
+=== HttpPasswordUpdate.soy and HttpPasswordUpdateHtml.soy
+
+HttpPasswordUpdate templates will determine the contents of the email related to adding,
+changing or deleting the HTTP password on a user account.
+
=== Merged.soy and MergedHtml.soy
The Merged templates will determine the contents of the email related to a
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 ec96c4b..1e1fc11 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
@@ -1904,15 +1904,21 @@
@Test
public void userCanGenerateNewHttpPassword() throws Exception {
+ sender.clear();
String newPassword = gApi.accounts().self().generateHttpPassword();
assertThat(newPassword).isNotNull();
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
}
@Test
public void adminCanGenerateNewHttpPasswordForUser() throws Exception {
setApiUser(admin);
+ sender.clear();
String newPassword = gApi.accounts().id(user.username).generateHttpPassword();
assertThat(newPassword).isNotNull();
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
}
@Test
@@ -1939,7 +1945,10 @@
@Test
public void userCanRemoveHttpPassword() throws Exception {
setApiUser(user);
+ sender.clear();
assertThat(gApi.accounts().self().setHttpPassword(null)).isNull();
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("HTTP password was deleted");
}
@Test
@@ -1953,14 +1962,20 @@
public void adminCanExplicitlySetHttpPasswordForUser() throws Exception {
setApiUser(admin);
String httpPassword = "new-password-for-user";
+ sender.clear();
assertThat(gApi.accounts().id(user.username).setHttpPassword(httpPassword))
.isEqualTo(httpPassword);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
}
@Test
public void adminCanRemoveHttpPasswordForUser() throws Exception {
setApiUser(admin);
+ sender.clear();
assertThat(gApi.accounts().id(user.username).setHttpPassword(null)).isNull();
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("HTTP password was deleted");
}
@Test
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 a633335..86daae2 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
@@ -124,6 +124,8 @@
extractMailExample("Footer.soy");
extractMailExample("FooterHtml.soy");
extractMailExample("HeaderHtml.soy");
+ extractMailExample("HttpPasswordUpdate.soy");
+ extractMailExample("HttpPasswordUpdateHtml.soy");
extractMailExample("Merged.soy");
extractMailExample("MergedHtml.soy");
extractMailExample("NewChange.soy");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index ef953e6..19b5a66 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.base.Strings;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -28,6 +29,7 @@
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -39,8 +41,12 @@
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
+ private static final Logger log = LoggerFactory.getLogger(PutHttpPassword.class);
+
public static class Input {
public String httpPassword;
public boolean generate;
@@ -61,17 +67,20 @@
private final PermissionBackend permissionBackend;
private final ExternalIds externalIds;
private final ExternalIdsUpdate.User externalIdsUpdate;
+ private final HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory;
@Inject
PutHttpPassword(
Provider<CurrentUser> self,
PermissionBackend permissionBackend,
ExternalIds externalIds,
- ExternalIdsUpdate.User externalIdsUpdate) {
+ ExternalIdsUpdate.User externalIdsUpdate,
+ HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory) {
this.self = self;
this.permissionBackend = permissionBackend;
this.externalIds = externalIds;
this.externalIdsUpdate = externalIdsUpdate;
+ this.httpPasswordUpdateSenderFactory = httpPasswordUpdateSenderFactory;
}
@Override
@@ -111,6 +120,17 @@
ExternalId.createWithPassword(extId.key(), extId.accountId(), extId.email(), newPassword);
externalIdsUpdate.create().upsert(newExtId);
+ try {
+ httpPasswordUpdateSenderFactory
+ .create(user, newPassword == null ? "deleted" : "added or updated")
+ .send();
+ } catch (EmailException e) {
+ log.error(
+ "Cannot send HttpPassword update message to {}",
+ user.getAccount().getPreferredEmail(),
+ e);
+ }
+
return Strings.isNullOrEmpty(newPassword) ? Response.<String>none() : Response.ok(newPassword);
}
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 6b363c0..cc3db75 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
@@ -23,6 +23,7 @@
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.HttpPasswordUpdateSender;
import com.google.gerrit.server.mail.send.MergedSender;
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
@@ -41,6 +42,7 @@
factory(DeleteKeySender.Factory.class);
factory(DeleteReviewerSender.Factory.class);
factory(DeleteVoteSender.Factory.class);
+ factory(HttpPasswordUpdateSender.Factory.class);
factory(MergedSender.Factory.class);
factory(RegisterNewEmailSender.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
new file mode 100644
index 0000000..eb2ca25
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
@@ -0,0 +1,81 @@
+// 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.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.Address;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+public class HttpPasswordUpdateSender extends OutgoingEmail {
+ public interface Factory {
+ HttpPasswordUpdateSender create(IdentifiedUser user, String operation);
+ }
+
+ private final IdentifiedUser user;
+ private final String operation;
+
+ @AssistedInject
+ public HttpPasswordUpdateSender(
+ EmailArguments ea, @Assisted IdentifiedUser user, @Assisted String operation) {
+ super(ea, "HttpPasswordUpdate");
+ this.user = user;
+ this.operation = operation;
+ }
+
+ @Override
+ protected void init() throws EmailException {
+ super.init();
+ setHeader("Subject", "[Gerrit Code Review] HTTP password was " + operation);
+ add(RecipientType.TO, new Address(getEmail()));
+ }
+
+ @Override
+ protected boolean shouldSendMessage() {
+ // Always send an email if the HTTP password is updated.
+ return true;
+ }
+
+ @Override
+ protected void format() throws EmailException {
+ appendText(textTemplate("HttpPasswordUpdate"));
+ if (useHtml()) {
+ appendHtml(soyHtmlTemplate("HttpPasswordUpdateHtml"));
+ }
+ }
+
+ public String getEmail() {
+ return user.getAccount().getPreferredEmail();
+ }
+
+ public String getUserNameEmail() {
+ return getUserNameEmailFor(user.getAccountId());
+ }
+
+ @Override
+ protected void setupSoyContext() {
+ super.setupSoyContext();
+ soyContextEmailData.put("email", getEmail());
+ soyContextEmailData.put("userNameEmail", getUserNameEmail());
+ soyContextEmailData.put("operation", operation);
+ }
+
+ @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 81074fd..34a7085 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
@@ -56,6 +56,8 @@
"Footer.soy",
"FooterHtml.soy",
"HeaderHtml.soy",
+ "HttpPasswordUpdate.soy",
+ "HttpPasswordUpdateHtml.soy",
"Merged.soy",
"MergedHtml.soy",
"NewChange.soy",
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy
new file mode 100644
index 0000000..49fce7b
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy
@@ -0,0 +1,55 @@
+/**
+ * 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 .HttpPasswordUpdate template will determine the contents of the email related to
+ * adding, changing or deleting the HTTP password.
+ * @param email
+ */
+{template .HttpPasswordUpdate autoescape="strict" kind="text"}
+ The HTTP password was {$email.operation} on Gerrit Code Review at
+ {sp}{$email.gerritHost}.
+
+ If this is not expected, please contact your Gerrit Administrators
+ immediately.
+
+ {\n}
+ {\n}
+
+ You can also manage your HTTP password by visiting
+ {\n}
+ {$email.gerritUrl}#/settings/http-password
+ {\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/HttpPasswordUpdateHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy
new file mode 100644
index 0000000..0aac668
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy
@@ -0,0 +1,48 @@
+/**
+ * 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 .HttpPasswordUpdateHtml autoescape="strict" kind="html"}
+ <p>
+ The HTTP password was {$email.operation} on Gerrit Code Review
+ at {$email.gerritHost}.
+ </p>
+
+ <p>
+ If this is not expected, please contact your Gerrit Administrators
+ immediately.
+ </p>
+
+ <p>
+ You can also manage your HTTP password by following{sp}
+ <a href="{$email.gerritUrl}#/settings/http-password">this link</a>
+ {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}