use of com.google.gerrit.entities.Address in project gerrit by GerritCodeReview.
the class OutgoingEmail method sendImpl.
private void sendImpl() throws EmailException {
if (!args.emailSender.isEnabled()) {
// Server has explicitly disabled email sending.
//
logger.atFine().log("Not sending '%s': Email sending is disabled by server config", messageClass);
return;
}
if (!notify.shouldNotify()) {
logger.atFine().log("Not sending '%s': Notify handling is NONE", messageClass);
return;
}
init();
if (messageId == null) {
throw new IllegalStateException("All emails must have a messageId");
}
format();
appendText(textTemplate("Footer"));
if (useHtml()) {
appendHtml(soyHtmlTemplate("FooterHtml"));
}
Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
if (shouldSendMessage()) {
if (fromId != null) {
Optional<AccountState> fromUser = args.accountCache.get(fromId);
if (fromUser.isPresent()) {
GeneralPreferencesInfo senderPrefs = fromUser.get().generalPreferences();
CurrentUser user = args.currentUserProvider.get();
boolean isImpersonating = user.isIdentifiedUser() && user.isImpersonating();
if (isImpersonating && user.getAccountId() != fromId) {
// set up correctly.
throw new EmailException(String.format("User %s is sending email from %s, while acting on behalf of %s", user.asIdentifiedUser().getRealUser().getAccountId(), fromId, user.getAccountId()));
}
if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
// Include the sender in email if they enabled email notifications on their own
// comments.
//
logger.atFine().log("CC email sender %s because the email strategy of this user is %s", fromUser.get().account().id(), CC_ON_OWN_COMMENTS);
add(RecipientType.CC, fromId);
} else if (isImpersonating) {
// If we are impersonating a user, make sure they receive a CC of
// this message regardless of email strategy, unless email notifications are explicitly
// disabled for this user. This way they can always review and audit what we sent
// on their behalf to others.
logger.atFine().log("CC email sender %s because the email is sent on behalf of and email notifications" + " are enabled for this user.", fromUser.get().account().id());
add(RecipientType.CC, fromId);
} else if (!notify.accounts().containsValue(fromId) && rcptTo.remove(fromId)) {
// If they don't want a copy, but we queued one up anyway,
// drop them from the recipient lists, but only if the user is not being impersonated.
//
logger.atFine().log("Not CCing email sender %s because the email strategy of this user is not %s but" + " %s", fromUser.get().account().id(), CC_ON_OWN_COMMENTS, senderPrefs != null ? senderPrefs.getEmailStrategy() : null);
removeUser(fromUser.get().account());
}
}
}
// In addition, check if users only want to receive plaintext email.
for (Account.Id id : rcptTo) {
Optional<AccountState> thisUser = args.accountCache.get(id);
if (thisUser.isPresent()) {
Account thisUserAccount = thisUser.get().account();
GeneralPreferencesInfo prefs = thisUser.get().generalPreferences();
if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
logger.atFine().log("Not emailing account %s because user has set email strategy to %s", id, DISABLED);
removeUser(thisUserAccount);
} else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
logger.atFine().log("Removing account %s from HTML email because user prefers plain text emails", id);
removeUser(thisUserAccount);
smtpRcptToPlaintextOnly.add(Address.create(thisUserAccount.fullName(), thisUserAccount.preferredEmail()));
}
}
if (smtpRcptTo.isEmpty() && smtpRcptToPlaintextOnly.isEmpty()) {
logger.atFine().log("Not sending '%s': No SMTP recipients", messageClass);
return;
}
}
// inbound email replies.
if (!headers.containsKey(FieldName.REPLY_TO)) {
StringJoiner j = new StringJoiner(", ");
if (fromId != null) {
Address address = toAddress(fromId);
if (address != null) {
j.add(address.email());
}
}
// For users who prefer plaintext, this comes at the cost of not being
// listed in the multipart To and Cc headers. We work around this by adding
// all users to the Reply-To address in both the plaintext and multipart
// email. We should exclude any BCC addresses from reply-to, because they should be
// invisible to other recipients.
Sets.difference(Sets.union(smtpRcptTo, smtpRcptToPlaintextOnly), smtpBccRcptTo).stream().forEach(a -> j.add(a.email()));
setHeader(FieldName.REPLY_TO, j.toString());
}
String textPart = textBody.toString();
OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
va.messageClass = messageClass;
va.smtpFromAddress = smtpFromAddress;
va.smtpRcptTo = smtpRcptTo;
va.headers = headers;
va.body = textPart;
if (useHtml()) {
va.htmlBody = htmlBody.toString();
} else {
va.htmlBody = null;
}
Set<Address> intersection = Sets.intersection(va.smtpRcptTo, smtpRcptToPlaintextOnly);
if (!intersection.isEmpty()) {
logger.atSevere().log("Email '%s' will be sent twice to %s", messageClass, intersection);
}
if (!va.smtpRcptTo.isEmpty()) {
// Send multipart message
addMessageId(va, "-HTML");
if (!validateEmail(va))
return;
logger.atFine().log("Sending multipart '%s' from %s to %s", messageClass, va.smtpFromAddress, va.smtpRcptTo);
args.emailSender.send(va.smtpFromAddress, va.smtpRcptTo, va.headers, va.body, va.htmlBody);
}
if (!smtpRcptToPlaintextOnly.isEmpty()) {
addMessageId(va, "-PLAIN");
// Send plaintext message
Map<String, EmailHeader> shallowCopy = new HashMap<>();
shallowCopy.putAll(headers);
// Remove To and Cc
shallowCopy.remove(FieldName.TO);
shallowCopy.remove(FieldName.CC);
for (Address a : smtpRcptToPlaintextOnly) {
// Add new To
EmailHeader.AddressList to = new EmailHeader.AddressList();
to.add(a);
shallowCopy.put(FieldName.TO, to);
}
if (!validateEmail(va))
return;
logger.atFine().log("Sending plaintext '%s' from %s to %s", messageClass, va.smtpFromAddress, smtpRcptToPlaintextOnly);
args.emailSender.send(va.smtpFromAddress, smtpRcptToPlaintextOnly, shallowCopy, va.body);
}
}
}
use of com.google.gerrit.entities.Address in project gerrit by GerritCodeReview.
the class SmtpEmailSender method send.
@Override
public void send(final Address from, Collection<Address> rcpt, final Map<String, EmailHeader> callerHeaders, String textBody, @Nullable String htmlBody) throws EmailException {
if (!isEnabled()) {
throw new EmailException("Sending email is disabled");
}
StringBuilder rejected = new StringBuilder();
try {
final SMTPClient client = open();
try {
if (!client.setSender(from.email())) {
throw new EmailException("Server " + smtpHost + " rejected from address " + from.email());
}
/* Do not prevent the email from being sent to "good" users simply
* because some users get rejected. If not, a single rejected
* project watcher could prevent email for most actions on a project
* from being sent to any user! Instead, queue up the errors, and
* throw an exception after sending the email to get the rejected
* error(s) logged.
*/
for (Address addr : rcpt) {
if (!client.addRecipient(addr.email())) {
String error = client.getReplyString();
rejected.append("Server ").append(smtpHost).append(" rejected recipient ").append(addr).append(": ").append(error);
}
}
try (Writer messageDataWriter = client.sendMessageData()) {
if (messageDataWriter == null) {
/* Include rejected recipient error messages here to not lose that
* information. That piece of the puzzle is vital if zero recipients
* are accepted and the server consequently rejects the DATA command.
*/
throw new EmailException(rejected.append("Server ").append(smtpHost).append(" rejected DATA command: ").append(client.getReplyString()).toString());
}
render(messageDataWriter, callerHeaders, textBody, htmlBody);
if (!client.completePendingCommand()) {
throw new EmailException("Server " + smtpHost + " rejected message body: " + client.getReplyString());
}
client.logout();
if (rejected.length() > 0) {
throw new EmailException(rejected.toString());
}
}
} finally {
client.disconnect();
}
} catch (IOException e) {
throw new EmailException("Cannot send outgoing email", e);
}
}
use of com.google.gerrit.entities.Address in project gerrit by GerritCodeReview.
the class ChangeQueryBuilder method reviewerByState.
public Predicate<ChangeData> reviewerByState(String who, ReviewerStateInternal state, boolean forDefaultField) throws QueryParseException, IOException, ConfigInvalidException {
Predicate<ChangeData> reviewerByEmailPredicate = null;
Address address = Address.tryParse(who);
if (address != null) {
reviewerByEmailPredicate = ReviewerByEmailPredicate.forState(address, state);
}
Predicate<ChangeData> reviewerPredicate = null;
try {
Set<Account.Id> accounts = parseAccount(who);
if (!forDefaultField || accounts.size() <= MAX_ACCOUNTS_PER_DEFAULT_FIELD) {
reviewerPredicate = Predicate.or(accounts.stream().map(id -> ReviewerPredicate.forState(id, state)).collect(toList()));
} else {
logger.atFine().log("Skipping reviewer predicate for %s in default field query" + " because the number of matched accounts (%d) exceeds the limit of %d", who, accounts.size(), MAX_ACCOUNTS_PER_DEFAULT_FIELD);
}
} catch (QueryParseException e) {
logger.atFine().log("Parsing %s as account failed: %s", who, e.getMessage());
// Propagate this exception only if we can't use 'who' to query by email
if (reviewerByEmailPredicate == null) {
throw e;
}
}
if (reviewerPredicate != null && reviewerByEmailPredicate != null) {
return Predicate.or(reviewerPredicate, reviewerByEmailPredicate);
} else if (reviewerPredicate != null) {
return reviewerPredicate;
} else if (reviewerByEmailPredicate != null) {
return reviewerByEmailPredicate;
} else {
return Predicate.any();
}
}
use of com.google.gerrit.entities.Address in project gerrit by GerritCodeReview.
the class AbstractSubmitOnPush method assertThatEmailsForChangeCreationAndSubmitWereSent.
/**
* Makes sure that two emails are sent: one for the change creation, and one for the submit.
*
* @param expected The account expected to receive message.
* @param expectedRecipientType The notification's type: To/Cc/Bcc. if {@code null} then it is not
* needed to check the recipientType. It is meant for -notify without other flags like
* notify-cc, notify-to, and notify-bcc. With the -notify flag, the message can sometimes be
* sent as "To" and sometimes can be sent as "Cc".
*/
private void assertThatEmailsForChangeCreationAndSubmitWereSent(TestAccount expected, @Nullable RecipientType expectedRecipientType) {
String expectedEmail = expected.email();
String expectedFullName = expected.fullName();
Address expectedAddress = Address.create(expectedFullName, expectedEmail);
assertThat(sender.getMessages()).hasSize(2);
Message message = sender.getMessages().get(0);
assertThat(message.body().contains("review")).isTrue();
assertAddress(message, expectedAddress, expectedRecipientType);
message = sender.getMessages().get(1);
assertThat(message.rcpt()).containsExactly(expectedAddress);
assertAddress(message, expectedAddress, expectedRecipientType);
assertThat(message.body().contains("submitted")).isTrue();
}
use of com.google.gerrit.entities.Address in project gerrit by GerritCodeReview.
the class ProjectWatchIT method noNotificationForPrivateChangesForWatchersInNotifyConfig.
@Test
public void noNotificationForPrivateChangesForWatchersInNotifyConfig() throws Exception {
Address addr = Address.create("Watcher", "watcher@example.com");
NotifyConfig.Builder nc = NotifyConfig.builder();
nc.addAddress(addr);
nc.setName("team");
nc.setHeader(NotifyConfig.Header.TO);
nc.setNotify(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().putNotifyConfig("team", nc.build());
u.save();
}
sender.clear();
PushOneCommit.Result r = pushFactory.create(admin.newIdent(), testRepo, "private change", "a", "a1").to("refs/for/master%private");
r.assertOkStatus();
assertThat(sender.getMessages()).isEmpty();
requestScopeOperations.setApiUser(admin.id());
ReviewInput in = new ReviewInput();
in.message = "comment";
gApi.changes().id(r.getChangeId()).current().review(in);
assertThat(sender.getMessages()).isEmpty();
}
Aggregations