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
This commit is contained in:

committed by
David Pursehouse

parent
04a1704113
commit
d189897aaa
@@ -88,6 +88,11 @@ a user removing a reviewer (with a vote) from a change. It is a
|
||||
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
|
||||
|
@@ -1904,15 +1904,21 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
|
||||
@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 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
@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 class AccountIT extends AbstractDaemonTest {
|
||||
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
|
||||
|
@@ -124,6 +124,8 @@ public class SitePathInitializer {
|
||||
extractMailExample("Footer.soy");
|
||||
extractMailExample("FooterHtml.soy");
|
||||
extractMailExample("HeaderHtml.soy");
|
||||
extractMailExample("HttpPasswordUpdate.soy");
|
||||
extractMailExample("HttpPasswordUpdateHtml.soy");
|
||||
extractMailExample("Merged.soy");
|
||||
extractMailExample("MergedHtml.soy");
|
||||
extractMailExample("NewChange.soy");
|
||||
|
@@ -17,6 +17,7 @@ package com.google.gerrit.server.account;
|
||||
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.PutHttpPassword.Input;
|
||||
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.NoSuchAlgorithmException;
|
||||
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 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
|
||||
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 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ 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.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 @@ public class EmailModule extends FactoryModule {
|
||||
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);
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -56,6 +56,8 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
|
||||
"Footer.soy",
|
||||
"FooterHtml.soy",
|
||||
"HeaderHtml.soy",
|
||||
"HttpPasswordUpdate.soy",
|
||||
"HttpPasswordUpdateHtml.soy",
|
||||
"Merged.soy",
|
||||
"MergedHtml.soy",
|
||||
"NewChange.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}
|
@@ -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}
|
Reference in New Issue
Block a user