mirror of
https://github.com/google/nomulus.git
synced 2025-07-23 19:20:44 +02:00
Add placeholder configs for Gmail (#2089)
Add placeholder configs for sending emails using Gmail in GSuite. The names of the new configs are temporary. After migration they will revert to the names currently in use by the AppEngine email API.
This commit is contained in:
parent
1e0a0cf29e
commit
10d28efa1c
8 changed files with 117 additions and 8 deletions
|
@ -71,7 +71,7 @@ public class CannedScriptExecutionAction implements Runnable {
|
||||||
GmailClient gmailClient,
|
GmailClient gmailClient,
|
||||||
@Config("projectId") String projectId,
|
@Config("projectId") String projectId,
|
||||||
@Config("gSuiteDomainName") String gSuiteDomainName,
|
@Config("gSuiteDomainName") String gSuiteDomainName,
|
||||||
@Config("alertRecipientEmailAddress") InternetAddress recipientAddress) {
|
@Config("newAlertRecipientEmailAddress") InternetAddress recipientAddress) {
|
||||||
this.groupsConnection = groupsConnection;
|
this.groupsConnection = groupsConnection;
|
||||||
this.gmailClient = gmailClient;
|
this.gmailClient = gmailClient;
|
||||||
this.gSuiteDomainName = gSuiteDomainName;
|
this.gSuiteDomainName = gSuiteDomainName;
|
||||||
|
@ -116,6 +116,8 @@ public class CannedScriptExecutionAction implements Runnable {
|
||||||
try {
|
try {
|
||||||
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
|
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
|
||||||
logger.atInfo().log("%s has %s members.", groupKey, currentMembers.size());
|
logger.atInfo().log("%s has %s members.", groupKey, currentMembers.size());
|
||||||
|
// One success is enough for validation.
|
||||||
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.atWarning().withCause(e).log("Failed to check %s", groupKey);
|
logger.atWarning().withCause(e).log("Failed to check %s", groupKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,13 +93,56 @@ public abstract class CredentialModule {
|
||||||
@AdcDelegatedCredential
|
@AdcDelegatedCredential
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public static GoogleCredentialsBundle provideSelfSignedDelegatedCredential(
|
public static GoogleCredentialsBundle provideSelfSignedAdminDelegatedCredential(
|
||||||
@Config("defaultCredentialOauthScopes") ImmutableList<String> defaultScopes,
|
@Config("defaultCredentialOauthScopes") ImmutableList<String> defaultScopes,
|
||||||
@Config("delegatedCredentialOauthScopes") ImmutableList<String> delegationScopes,
|
@Config("delegatedCredentialOauthScopes") ImmutableList<String> delegationScopes,
|
||||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||||
@Config("gSuiteAdminAccountEmailAddress") String gSuiteAdminAccountEmailAddress,
|
@Config("gSuiteAdminAccountEmailAddress") String gSuiteAdminAccountEmailAddress,
|
||||||
@Config("tokenRefreshDelay") Duration tokenRefreshDelay,
|
@Config("tokenRefreshDelay") Duration tokenRefreshDelay,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
|
return createSelfSignedDelegatedCredential(
|
||||||
|
defaultScopes,
|
||||||
|
delegationScopes,
|
||||||
|
credentialsBundle,
|
||||||
|
gSuiteAdminAccountEmailAddress,
|
||||||
|
tokenRefreshDelay,
|
||||||
|
clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link GoogleCredentialsBundle} for sending emails through Google Workspace.
|
||||||
|
*
|
||||||
|
* <p>The Workspace domain must grant delegated admin access to the default service account user
|
||||||
|
* (project-id@appspot.gserviceaccount.com on AppEngine) with all scopes in {@code defaultScopes}
|
||||||
|
* and {@code delegationScopes}. In addition, the user {@code gSuiteOutgoingEmailAddress} must
|
||||||
|
* have the permission to send emails.
|
||||||
|
*/
|
||||||
|
@GmailDelegatedCredential
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public static GoogleCredentialsBundle provideSelfSignedGmailDelegatedCredential(
|
||||||
|
@Config("defaultCredentialOauthScopes") ImmutableList<String> defaultScopes,
|
||||||
|
@Config("delegatedCredentialOauthScopes") ImmutableList<String> delegationScopes,
|
||||||
|
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||||
|
@Config("gSuiteNewOutgoingEmailAddress") String gSuiteOutgoingEmailAddress,
|
||||||
|
@Config("tokenRefreshDelay") Duration tokenRefreshDelay,
|
||||||
|
Clock clock) {
|
||||||
|
return createSelfSignedDelegatedCredential(
|
||||||
|
defaultScopes,
|
||||||
|
delegationScopes,
|
||||||
|
credentialsBundle,
|
||||||
|
gSuiteOutgoingEmailAddress,
|
||||||
|
tokenRefreshDelay,
|
||||||
|
clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GoogleCredentialsBundle createSelfSignedDelegatedCredential(
|
||||||
|
ImmutableList<String> defaultScopes,
|
||||||
|
ImmutableList<String> delegationScopes,
|
||||||
|
GoogleCredentialsBundle credentialsBundle,
|
||||||
|
String gSuiteUserEmailAddress,
|
||||||
|
Duration tokenRefreshDelay,
|
||||||
|
Clock clock) {
|
||||||
GoogleCredentials signer = credentialsBundle.getGoogleCredentials();
|
GoogleCredentials signer = credentialsBundle.getGoogleCredentials();
|
||||||
|
|
||||||
checkArgument(
|
checkArgument(
|
||||||
|
@ -118,7 +161,7 @@ public abstract class CredentialModule {
|
||||||
DelegatedCredentials.createSelfSignedDelegatedCredential(
|
DelegatedCredentials.createSelfSignedDelegatedCredential(
|
||||||
(ServiceAccountSigner) signer,
|
(ServiceAccountSigner) signer,
|
||||||
ImmutableList.<String>builder().addAll(defaultScopes).addAll(delegationScopes).build(),
|
ImmutableList.<String>builder().addAll(defaultScopes).addAll(delegationScopes).build(),
|
||||||
gSuiteAdminAccountEmailAddress,
|
gSuiteUserEmailAddress,
|
||||||
clock,
|
clock,
|
||||||
tokenRefreshDelay);
|
tokenRefreshDelay);
|
||||||
return GoogleCredentialsBundle.create(credential);
|
return GoogleCredentialsBundle.create(credential);
|
||||||
|
@ -136,6 +179,15 @@ public abstract class CredentialModule {
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface GoogleWorkspaceCredential {}
|
public @interface GoogleWorkspaceCredential {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger qualifier for a credential with delegated Email-sending permission for a dasher domain
|
||||||
|
* (for Google Workspace) backed by the application default credential (ADC).
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface GmailDelegatedCredential {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dagger qualifier for a credential with delegated admin access for a dasher domain (for Google
|
* Dagger qualifier for a credential with delegated admin access for a dasher domain (for Google
|
||||||
* Workspace) backed by the application default credential (ADC).
|
* Workspace) backed by the application default credential (ADC).
|
||||||
|
|
|
@ -538,6 +538,13 @@ public final class RegistryConfig {
|
||||||
return parseEmailAddress(config.gSuite.outgoingEmailAddress);
|
return parseEmailAddress(config.gSuite.outgoingEmailAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/279671974): reuse the 'gSuiteOutgoingEmailAddress' annotation after migration
|
||||||
|
@Provides
|
||||||
|
@Config("gSuiteNewOutgoingEmailAddress")
|
||||||
|
public static String provideGSuiteNewOutgoingEmailAddress(RegistryConfigSettings config) {
|
||||||
|
return config.gSuite.newOutgoingEmailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The display name that is used on outgoing emails sent by Nomulus.
|
* The display name that is used on outgoing emails sent by Nomulus.
|
||||||
*
|
*
|
||||||
|
@ -549,6 +556,16 @@ public final class RegistryConfig {
|
||||||
return config.gSuite.outgoingEmailDisplayName;
|
return config.gSuite.outgoingEmailDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the `reply-to` address for outgoing email messages. This address may be outside the
|
||||||
|
* GSuite domain.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Config("replyToEmailAddress")
|
||||||
|
public static InternetAddress provideReplyToEmailAddress(RegistryConfigSettings config) {
|
||||||
|
return parseEmailAddress(config.gSuite.replyToEmailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether an SSL certificate hash is required to log in via EPP and run flows.
|
* Returns whether an SSL certificate hash is required to log in via EPP and run flows.
|
||||||
*
|
*
|
||||||
|
@ -859,6 +876,14 @@ public final class RegistryConfig {
|
||||||
return parseEmailAddress(config.misc.alertRecipientEmailAddress);
|
return parseEmailAddress(config.misc.alertRecipientEmailAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/279671974): remove below method after migration
|
||||||
|
@Provides
|
||||||
|
@Config("newAlertRecipientEmailAddress")
|
||||||
|
public static InternetAddress provideNewAlertRecipientEmailAddress(
|
||||||
|
RegistryConfigSettings config) {
|
||||||
|
return parseEmailAddress(config.misc.newAlertRecipientEmailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the email address to which spec 11 email should be replied.
|
* Returns the email address to which spec 11 email should be replied.
|
||||||
*
|
*
|
||||||
|
|
|
@ -77,6 +77,9 @@ public class RegistryConfigSettings {
|
||||||
public static class GSuite {
|
public static class GSuite {
|
||||||
public String domainName;
|
public String domainName;
|
||||||
public String outgoingEmailAddress;
|
public String outgoingEmailAddress;
|
||||||
|
// TODO(b/279671974): remove below field after migration
|
||||||
|
public String newOutgoingEmailAddress;
|
||||||
|
public String replyToEmailAddress;
|
||||||
public String outgoingEmailDisplayName;
|
public String outgoingEmailDisplayName;
|
||||||
public String adminAccountEmailAddress;
|
public String adminAccountEmailAddress;
|
||||||
public String supportGroupEmailAddress;
|
public String supportGroupEmailAddress;
|
||||||
|
@ -203,6 +206,8 @@ public class RegistryConfigSettings {
|
||||||
public static class Misc {
|
public static class Misc {
|
||||||
public String sheetExportId;
|
public String sheetExportId;
|
||||||
public String alertRecipientEmailAddress;
|
public String alertRecipientEmailAddress;
|
||||||
|
// TODO(b/279671974): remove below field after migration
|
||||||
|
public String newAlertRecipientEmailAddress;
|
||||||
public String spec11OutgoingEmailAddress;
|
public String spec11OutgoingEmailAddress;
|
||||||
public List<String> spec11BccEmailAddresses;
|
public List<String> spec11BccEmailAddresses;
|
||||||
public int transientFailureRetries;
|
public int transientFailureRetries;
|
||||||
|
|
|
@ -33,6 +33,9 @@ gSuite:
|
||||||
# https://cloud.google.com/appengine/docs/standard/java/mail/#who_can_send_mail
|
# https://cloud.google.com/appengine/docs/standard/java/mail/#who_can_send_mail
|
||||||
outgoingEmailDisplayName: Example Registry
|
outgoingEmailDisplayName: Example Registry
|
||||||
outgoingEmailAddress: noreply@project-id.appspotmail.com
|
outgoingEmailAddress: noreply@project-id.appspotmail.com
|
||||||
|
# TODO(b/279671974): reuse `outgoingEmailAddress` after migration
|
||||||
|
newOutgoingEmailAddress: noreply@example.com
|
||||||
|
replyToEmailAddress: reply-to@example.com
|
||||||
|
|
||||||
# Email address of the admin account on the G Suite app. This is used for
|
# Email address of the admin account on the G Suite app. This is used for
|
||||||
# logging in to perform administrative actions, not sending emails.
|
# logging in to perform administrative actions, not sending emails.
|
||||||
|
@ -432,6 +435,9 @@ misc:
|
||||||
# Address we send alert summary emails to.
|
# Address we send alert summary emails to.
|
||||||
alertRecipientEmailAddress: email@example.com
|
alertRecipientEmailAddress: email@example.com
|
||||||
|
|
||||||
|
# TODO(b/279671974): reuse `alertRecipientEmailAddress` after migration
|
||||||
|
newAlertRecipientEmailAddress: email@example.com
|
||||||
|
|
||||||
# Address from which Spec 11 emails to registrars are sent. This needs
|
# Address from which Spec 11 emails to registrars are sent. This needs
|
||||||
# to be a deliverable email address to handle replies from registrars as well.
|
# to be a deliverable email address to handle replies from registrars as well.
|
||||||
spec11OutgoingEmailAddress: abuse@example.com
|
spec11OutgoingEmailAddress: abuse@example.com
|
||||||
|
|
|
@ -20,10 +20,12 @@ import com.google.api.services.gmail.Gmail;
|
||||||
import com.google.api.services.gmail.model.Message;
|
import com.google.api.services.gmail.model.Message;
|
||||||
import com.google.common.net.MediaType;
|
import com.google.common.net.MediaType;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.util.EmailMessage;
|
import google.registry.util.EmailMessage;
|
||||||
import google.registry.util.EmailMessage.Attachment;
|
import google.registry.util.EmailMessage.Attachment;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.mail.Address;
|
import javax.mail.Address;
|
||||||
|
@ -42,10 +44,24 @@ import org.apache.commons.codec.binary.Base64;
|
||||||
public final class GmailClient {
|
public final class GmailClient {
|
||||||
|
|
||||||
private final Gmail gmail;
|
private final Gmail gmail;
|
||||||
|
private final InternetAddress outgoingEmailAddressWithUsername;
|
||||||
|
private final InternetAddress replyToEmailAddress;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GmailClient(Gmail gmail) {
|
GmailClient(
|
||||||
|
Gmail gmail,
|
||||||
|
@Config("gSuiteNewOutgoingEmailAddress") String gSuiteOutgoingEmailAddress,
|
||||||
|
@Config("gSuiteOutgoingEmailDisplayName") String gSuiteOutgoingEmailDisplayName,
|
||||||
|
@Config("replyToEmailAddress") InternetAddress replyToEmailAddress) {
|
||||||
|
|
||||||
this.gmail = gmail;
|
this.gmail = gmail;
|
||||||
|
this.replyToEmailAddress = replyToEmailAddress;
|
||||||
|
try {
|
||||||
|
this.outgoingEmailAddressWithUsername =
|
||||||
|
new InternetAddress(gSuiteOutgoingEmailAddress, gSuiteOutgoingEmailDisplayName);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,6 +74,7 @@ public final class GmailClient {
|
||||||
public Message sendEmail(EmailMessage emailMessage) {
|
public Message sendEmail(EmailMessage emailMessage) {
|
||||||
Message message = toGmailMessage(toMimeMessage(emailMessage));
|
Message message = toGmailMessage(toMimeMessage(emailMessage));
|
||||||
try {
|
try {
|
||||||
|
// "me" is reserved word for the authorized user of the Gmail API.
|
||||||
return gmail.users().messages().send("me", message).execute();
|
return gmail.users().messages().send("me", message).execute();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new EmailException(e);
|
throw new EmailException(e);
|
||||||
|
@ -78,11 +95,12 @@ public final class GmailClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static MimeMessage toMimeMessage(EmailMessage emailMessage) {
|
MimeMessage toMimeMessage(EmailMessage emailMessage) {
|
||||||
try {
|
try {
|
||||||
MimeMessage msg =
|
MimeMessage msg =
|
||||||
new MimeMessage(Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
|
new MimeMessage(Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
|
||||||
msg.setFrom(emailMessage.from());
|
msg.setFrom(this.outgoingEmailAddressWithUsername);
|
||||||
|
msg.setReplyTo(new InternetAddress[] {replyToEmailAddress});
|
||||||
msg.addRecipients(
|
msg.addRecipients(
|
||||||
RecipientType.TO, toArray(emailMessage.recipients(), InternetAddress.class));
|
RecipientType.TO, toArray(emailMessage.recipients(), InternetAddress.class));
|
||||||
msg.setSubject(emailMessage.subject());
|
msg.setSubject(emailMessage.subject());
|
||||||
|
|
|
@ -17,7 +17,7 @@ package google.registry.groups;
|
||||||
import com.google.api.services.gmail.Gmail;
|
import com.google.api.services.gmail.Gmail;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import google.registry.config.CredentialModule.AdcDelegatedCredential;
|
import google.registry.config.CredentialModule.GmailDelegatedCredential;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.util.GoogleCredentialsBundle;
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -29,7 +29,7 @@ public class GmailModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Gmail provideGmail(
|
Gmail provideGmail(
|
||||||
@AdcDelegatedCredential GoogleCredentialsBundle credentialsBundle,
|
@GmailDelegatedCredential GoogleCredentialsBundle credentialsBundle,
|
||||||
@Config("projectId") String projectId) {
|
@Config("projectId") String projectId) {
|
||||||
return new Gmail.Builder(
|
return new Gmail.Builder(
|
||||||
credentialsBundle.getHttpTransport(),
|
credentialsBundle.getHttpTransport(),
|
||||||
|
|
|
@ -46,6 +46,7 @@ public abstract class EmailMessage {
|
||||||
|
|
||||||
public abstract ImmutableSet<InternetAddress> recipients();
|
public abstract ImmutableSet<InternetAddress> recipients();
|
||||||
|
|
||||||
|
// TODO(b/279671974): remove `from` after migration.
|
||||||
public abstract InternetAddress from();
|
public abstract InternetAddress from();
|
||||||
|
|
||||||
public abstract ImmutableSet<InternetAddress> ccs();
|
public abstract ImmutableSet<InternetAddress> ccs();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue