Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [CO 621] Allow LE certificates to be generated from Mailbox endpoint #175

Merged
merged 39 commits into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d6f841c
feat: [CO-621] add async execution
aheeva-yuliya Mar 13, 2023
ab153e7
feat: [CO-621] comment tests
aheeva-yuliya Mar 13, 2023
ff5ccbb
feat: [CO-621] add send message
aheeva-yuliya Mar 15, 2023
78100ce
feat: [CO-621] able to notify
aheeva-yuliya Mar 16, 2023
58f2a2c
feat: [CO-621] notify domain recipients using MailSender
aheeva-yuliya Mar 17, 2023
b94bd8e
feat: [CO-621] add convert method
aheeva-yuliya Mar 17, 2023
30dd98d
feat: [CO-621] setEnvelopeFrom
aheeva-yuliya Mar 18, 2023
9db3966
feat: [CO-621] added failure messages
aheeva-yuliya Mar 18, 2023
d619095
feat: [CO-621] added / to certbot working dir
aheeva-yuliya Mar 18, 2023
0fd4c90
feat: [CO-621] fix javadoc
aheeva-yuliya Mar 18, 2023
e7933e3
feat: [CO-621] add CertificateNotificationManager and tests
aheeva-yuliya Mar 27, 2023
01cee06
feat: [CO-621] modify CertificateNotificationManager tests
aheeva-yuliya Mar 28, 2023
e752803
feat: [CO-621] tests
aheeva-yuliya Mar 28, 2023
3af10e7
feat: [CO-621] tests
aheeva-yuliya Mar 28, 2023
bbae7b7
feat: [CO-621] tests
aheeva-yuliya Mar 28, 2023
5e6c4fb
feat: [CO-621] uncomment tests
aheeva-yuliya Mar 28, 2023
8bdad8f
feat: [CO-621] comment tests
aheeva-yuliya Mar 28, 2023
aa2bc46
feat: [CO-621] contact and FileIntoCopy tests keep failing
aheeva-yuliya Mar 28, 2023
9d4473f
feat: [CO-621] apply small changes, modify IssueCertTest
aheeva-yuliya Mar 29, 2023
7e044e2
feat: [CO-621] modify java docs
aheeva-yuliya Mar 29, 2023
bbd9fba
feat: [CO-621] add checkValidity method
aheeva-yuliya Mar 30, 2023
6825292
feat: [CO-621] make provisioning to be a field
aheeva-yuliya Mar 30, 2023
1e9f15e
feat: [CO-621] uncomment Contact and FileIntoCopy tests
aheeva-yuliya Mar 30, 2023
e7c8229
feat: [CO-621] slightly modify Contact and FileIntoCopy tests
aheeva-yuliya Mar 30, 2023
1ada9cb
feat: [CO-621] slightly modify ImapHandlerTest
aheeva-yuliya Mar 30, 2023
6c30810
feat: [CO-621] remove checkValidity
aheeva-yuliya Mar 30, 2023
6ea1e95
feat: [CO-621] modify templates
aheeva-yuliya Mar 30, 2023
d2183f3
feat: [CO-621] add domain name to success template
aheeva-yuliya Mar 31, 2023
8474927
feat: [CO-621] refactor to avoid casting
aheeva-yuliya Apr 3, 2023
632322c
feat: [CO-621] delete domainName from fields
aheeva-yuliya Apr 3, 2023
51c7375
feat: [CO-621] apply requested changes
aheeva-yuliya Apr 3, 2023
0c5033b
feat: [CO-621] apply requested changes
aheeva-yuliya Apr 3, 2023
7d3a12e
feat: [CO-621] apply requested changes
aheeva-yuliya Apr 3, 2023
c9558a0
feat: [CO-621] apply requested changes
aheeva-yuliya Apr 3, 2023
c642e08
feat: [CO-621] add small changes to tests
aheeva-yuliya Apr 3, 2023
3c7313f
feat: [CO-621] refactor CertificateNotificationManager, modify tests
aheeva-yuliya Apr 3, 2023
160d086
feat: [CO-621] refactor CertificateNotificationManager, modify tests
aheeva-yuliya Apr 4, 2023
17adeb3
feat: [CO-621] change javadoc
aheeva-yuliya Apr 4, 2023
dd98b69
feat: [CO-621] add logger to tests
aheeva-yuliya Apr 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 116 additions & 25 deletions store/src/main/java/com/zimbra/cs/mailbox/MailSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.zimbra.cs.mailbox;

import com.zimbra.cs.mailclient.smtp.SmtpTransport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -25,9 +26,11 @@

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

Expand Down Expand Up @@ -246,6 +249,29 @@ public MailSender setSession(Account account) throws ServiceException {
return this;
}

/**
* Sets an alternate JavaMail {@link javax.mail.Session} and SMTP hosts
* that will be used to send the message based on the domain values.
* The default behavior is to use SMTP settings from
* the {@link javax.mail.Session} on the {@link MimeMessage}.
*
* @param domain {@link com.zimbra.cs.account.Domain}
* @throws ServiceException if not able to get SMTP session for the domain
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public MailSender setSession(Domain domain) throws ServiceException {
try {
mSession = JMSession.getSmtpSession(domain);
} catch (MessagingException e) {
throw ServiceException.FAILURE("Unable to get SMTP session for " + domain, e);
}
mSmtpHosts.clear();
mSmtpHosts.addAll(JMSession.getSmtpHosts(domain));
return this;
}
frisonisland marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sets JavaMail <tt>Session</tt> using the SMTP settings associated with the data source.
* @throws ServiceException
Expand Down Expand Up @@ -282,6 +308,17 @@ public MailSender setEnvelopeFrom(String address) {
return this;
}

/**
* Returns the current session {@link javax.mail.Session}.
*
* @return {@link #mSession} value
* @author Yuliya Aheeva
* @since 23.5.0
*/
public Session getCurrentSession() {
return this.mSession;
}

public static int getSentFolderId(Mailbox mbox, Identity identity) throws ServiceException {
int folderId = Mailbox.ID_FOLDER_SENT;
String sentFolder = identity.getAttr(Provisioning.A_zimbraPrefSentMailFolder, null);
Expand Down Expand Up @@ -394,30 +431,6 @@ public static enum ReplyForwardType {
FORWARD // Forwarding another message
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender) throws ServiceException {
return sendMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, identityId, replyToSender, null);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender, MimeProcessor mimeProc) throws ServiceException {
Account authuser = octxt == null ? null : octxt.getAuthenticatedUser();
if (authuser == null) {
authuser = mbox.getAccount();
}
Identity identity = null;
if (identityId != null) {
identity = Provisioning.getInstance().get(authuser, Key.IdentityBy.id, identityId);
}
return sendMimeMessage(octxt, mbox, null, mm, uploads, origMsgId, replyType, identity, replyToSender, mimeProc);
}

public ItemId sendDataSourceMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType) throws ServiceException {
return sendDataSourceMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, null);
Expand Down Expand Up @@ -460,6 +473,62 @@ public void rollback() {
}
}

/**
* Sends a list of messages {@link javax.mail.internet.MimeMessage}.
*
* Tries to find the account by name (if unable to find will set account from the mailbox later
* with the {@link #sendMimeMessage(OperationContext, Mailbox, MimeMessage)} method)
* and provide the proper operational context.
*
* @param mbox object of {@link com.zimbra.cs.mailbox.Mailbox}
* @param mimeMessageList a list of {@link javax.mail.internet.MimeMessage} to be sent
* @throws ServiceException if sender is not set for the specific
* {@link javax.mail.internet.MimeMessage}
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public void sendMimeMessageList(Mailbox mbox, List<MimeMessage> mimeMessageList)
throws ServiceException {

Provisioning prov = Provisioning.getInstance();

try {
for (MimeMessage mm: mimeMessageList) {
Account account = prov.get(AccountBy.name, mm.getSender().toString());
OperationContext operationContext = new OperationContext(account);

sendMimeMessage(operationContext, mbox, mm);
}
} catch (MessagingException e) {
throw ServiceException.FAILURE("Unable to get sender account: " + e.getMessage());
}
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender) throws ServiceException {
return sendMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, identityId, replyToSender, null);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender, MimeProcessor mimeProc) throws ServiceException {
Account authuser = octxt == null ? null : octxt.getAuthenticatedUser();
if (authuser == null) {
authuser = mbox.getAccount();
}
Identity identity = null;
if (identityId != null) {
identity = Provisioning.getInstance().get(authuser, Key.IdentityBy.id, identityId);
}
return sendMimeMessage(octxt, mbox, null, mm, uploads, origMsgId, replyType, identity, replyToSender, mimeProc);
}

/**
* Sets member variables and sends the message.
*/
Expand All @@ -476,6 +545,9 @@ public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, Boolean save
return sendMimeMessage(octxt, mbox, mm);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, Boolean saveToSent, MimeMessage mm,
Collection<Upload> uploads, ItemId origMsgId, String replyType, Identity identity, boolean replyToSender)
throws ServiceException {
Expand Down Expand Up @@ -1231,7 +1303,9 @@ private void sendMessageToHost(String hostname, MimeMessage mm, Address[] rcptAd
}
ZimbraLog.smtp.debug("Sending message %s to SMTP host %s with properties: %s",
mm.getMessageID(), hostname, mSession.getProperties());
Transport transport = mSession.getTransport("smtp");

Transport transport = getTransport();

try {
transport.connect();
transport.sendMessage(mm, rcptAddresses);
Expand All @@ -1240,6 +1314,23 @@ private void sendMessageToHost(String hostname, MimeMessage mm, Address[] rcptAd
}
}

/**
* Initializes a new SMTP transport if not able to get one from the {@link #mSession}.
*
* @return SMTP transport
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
private Transport getTransport() {
try {
return mSession.getTransport("smtp");
} catch (NoSuchProviderException e) {
URLName urlName = new URLName("smtp", null, -1, null, null, null);
return new SmtpTransport(mSession, urlName);
}
}

private void checkMTAConnectionToHost(String hostname) throws MessagingException {
mSession.getProperties().setProperty("mail.smtp.host", hostname);
if (mEnvelopeFrom != null) {
Expand Down
19 changes: 19 additions & 0 deletions store/src/main/java/com/zimbra/cs/mailbox/Mailbox.java
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,25 @@ public MailSender getMailSender() throws ServiceException {
return sender;
}

/**
* Returns a {@link MailSender} object based on specific domain properties
* that can be used to send mails.
*
* @param domain {@link com.zimbra.cs.account.Domain} to get needed properties
* @return {@link MailSender} object
* @throws ServiceException if unable to get SMTP session for the current domain
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public MailSender getMailSender(Domain domain) throws ServiceException {
MailSender sender = new MailSender();
sender.setTrackBadHosts(true);
sender.setSession(domain);

return sender;
}

/**
* Returns the list of all <code>Mailbox</code> listeners of a given type. Returns all listeners
* when the passed-in type is <tt>null</tt>.
Expand Down
52 changes: 43 additions & 9 deletions store/src/main/java/com/zimbra/cs/rmgmt/RemoteCertbot.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.zimbra.cs.rmgmt;

import com.zimbra.common.service.ServiceException;
import com.zimbra.cs.service.admin.CertificateNotificationManager;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
* RemoteCertbot class interacts with "Certbot" - an acme client for managing Let’s Encrypt
Expand Down Expand Up @@ -30,27 +32,42 @@ public class RemoteCertbot {
private final RemoteManager remoteManager;
private StringBuilder stringBuilder;

public RemoteCertbot(RemoteManager remoteManager) {
private RemoteCertbot(RemoteManager remoteManager) {
this.remoteManager = remoteManager;
}

/**
* Instantiates a RemoteCertbot object.
*
* @param remoteManager {@link com.zimbra.cs.rmgmt.RemoteManager} which will be used to execute
* remote commands with.
* @return an instantiated object
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public static RemoteCertbot getRemoteCertbot(RemoteManager remoteManager) {
return new RemoteCertbot(remoteManager);
}

/**
* Creates a command to be executed by the Certbot acme client.
* E.g. certbot certonly --agree-tos --email admin@test.com -n --webroot -w /opt/zextras
*
* <p>E.g. certbot certonly --agree-tos --email admin@test.com -n --webroot -w /opt/zextras
* --cert-name demo.zextras.io -d acme.demo.zextras.io -d webmail-acme.demo.zextras.io
*
* @param remoteCommand {@link com.zimbra.cs.rmgmt.RemoteCommands}
* @param email domain admin email who tries to execute a command (should be agreed to the
* ACME server's Subscriber Agreement)
* @param email domain admin email who tries to execute a command (should be agreed to the ACME
* server's Subscriber Agreement)
* @param chain long (default) or short (should be specified by domain admin in {@link
* com.zimbra.soap.admin.message.IssueCertRequest} request with the key word "short")
* com.zimbra.soap.admin.message.IssueCertRequest} request with the key word "short")
* @param domainName a value of domain attribute zimbraDomainName
* @param publicServiceHostName a value of domain attribute zimbraPublicServiceHostname
* @param virtualHosts a value/ values of domain attribute zimbraVirtualHostname
* @return created command
*/
public String createCommand(String remoteCommand, String email, String chain,
String domainName, String publicServiceHostName, String[] virtualHosts) {
public String createCommand(String remoteCommand, String email, String chain, String domainName,
String publicServiceHostName, String[] virtualHosts) {

this.stringBuilder = new StringBuilder();

Expand All @@ -69,11 +86,28 @@ public String createCommand(String remoteCommand, String email, String chain,
return stringBuilder.toString();
}

/**
* Executes a command asynchronously and notifies global and domain recipients about the command
* execution using {@link com.zimbra.cs.service.admin.CertificateNotificationManager}.
*
* @param notificationManager an object of {@link com.zimbra.cs.service.admin.CertificateNotificationManager}
* @param command a Certbot command to be executed remotely
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public void supplyAsync(CertificateNotificationManager notificationManager, String command) {
CompletableFuture.supplyAsync(() -> execute(command))
.thenApply(notificationManager::createIssueCertNotificationMap)
.thenAccept(notificationManager::notify);
aheeva-yuliya marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Executes a command using {@link com.zimbra.cs.rmgmt.RemoteManager}.
*
* @param command a command to be executed
* @return a sting message of successful remote execution or detailed exception message
* in case of failure.
* @return a sting message of successful remote execution or detailed exception message in case of
* failure.
*/
public String execute(String command) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public synchronized RemoteResult execute(String command) throws ServiceException
}
} catch (Exception e) {
throw ServiceException.FAILURE(
"exception executing command: " + command + " with " + this + " " + e);
"exception executing command " + command + " with " + this + " " + e);
}
return result;
}
Expand Down
Loading