Skip to content

Commit

Permalink
Added OAuth2.0 to mail client and agent (openremote#1010)
Browse files Browse the repository at this point in the history
  • Loading branch information
richturner authored Mar 22, 2023
1 parent d640ab6 commit 91185f2
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
import org.openremote.model.attribute.AttributeEvent;
import org.openremote.model.attribute.AttributeRef;
import org.openremote.model.attribute.AttributeState;
import org.openremote.model.auth.OAuthGrant;
import org.openremote.model.auth.UsernamePassword;
import org.openremote.model.mail.MailMessage;
import org.openremote.model.syslog.SyslogCategory;

import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
Expand All @@ -46,7 +48,7 @@ public abstract class AbstractMailProtocol<T extends AbstractMailAgent<T, U, V>,
protected MailClient mailClient;
protected ConcurrentMap<AttributeRef, Function<MailMessage, String>> attributeMessageProcessorMap = new ConcurrentHashMap<>();
protected static final Logger LOG = SyslogCategory.getLogger(PROTOCOL, AbstractMailProtocol.class);
protected static int INITIAL_CHECK_DELAY_MILLIS = 10000;
protected static int INITIAL_CHECK_DELAY_SECONDS = 10;

public AbstractMailProtocol(T agent) {
super(agent);
Expand All @@ -55,33 +57,35 @@ public AbstractMailProtocol(T agent) {
@Override
protected void doStart(Container container) throws Exception {

UsernamePassword usernamePassword = getAgent().getUsernamePassword().orElseThrow();
Path storageDir = container.getService(PersistenceService.class).getStorageDir();

Path persistenceDir = storageDir.resolve("protocol").resolve("mail");
Optional<OAuthGrant> oAuthGrant = getAgent().getOAuthGrant();
UsernamePassword userPassword = getAgent().getUsernamePassword().orElseThrow();

mailClient = new MailClientBuilder(
MailClientBuilder clientBuilder = new MailClientBuilder(
container.getExecutorService(),
getAgent().getProtocol().orElseThrow(),
getAgent().getHost().orElseThrow(),
getAgent().getPort().orElseThrow(),
usernamePassword.getUsername(),
usernamePassword.getPassword()
getAgent().getPort().orElseThrow()
)
.setCheckIntervalMillis(
getAgent().getCheckIntervalSeconds().orElse(MailClientBuilder.DEFAULT_CHECK_INTERVAL_MILLIS)
.setCheckIntervalSeconds(
getAgent().getCheckIntervalSeconds().orElse(MailClientBuilder.DEFAULT_CHECK_INTERVAL_SECONDS)
)
.setDeleteMessageOnceProcessed(
getAgent().getDeleteProcessedMail().orElse(false)
)
.setFolder(getAgent().getMailFolderName().orElse(null))
.setPersistenceDir(persistenceDir)
// Set an initial delay to allow attributes to be linked before we read messages - not perfect but it should do
.setCheckInitialDelayMillis(INITIAL_CHECK_DELAY_MILLIS)
.setCheckInitialDelaySeconds(INITIAL_CHECK_DELAY_SECONDS)
.setPreferHTML(getAgent().getPreferHTML().orElse(false))
.setEarliestMessageDate(agent.getCreatedOn())
.build();
.setEarliestMessageDate(agent.getCreatedOn());

oAuthGrant.map(oAuth -> clientBuilder.setOAuth(userPassword.getUsername(), oAuth)).orElseGet(() ->
clientBuilder.setBasicAuth(userPassword.getUsername(), userPassword.getPassword()));

mailClient = clientBuilder.build();
mailClient.addConnectionListener(this::onConnectionEvent);
mailClient.addMessageListener(this::onMailMessage);
mailClient.connect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.undertow.util.Headers;
import org.openremote.container.util.MailUtil;
import org.openremote.model.asset.agent.ConnectionStatus;
import org.openremote.model.auth.UsernamePassword;
import org.openremote.model.mail.MailMessage;
import org.openremote.model.syslog.SyslogCategory;

Expand All @@ -38,7 +39,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.logging.Level;

public class MailClient {

Expand Down Expand Up @@ -93,7 +93,7 @@ protected boolean doConnect() {
try {
withFolder((folder) -> {
connected.set(true);
mailChecker = config.getScheduledExecutorService().scheduleWithFixedDelay(this::checkForMessages, config.getCheckInitialDelayMillis(), config.getCheckIntervalMillis(), TimeUnit.MILLISECONDS);
mailChecker = config.getScheduledExecutorService().scheduleWithFixedDelay(this::checkForMessages, config.getCheckInitialDelaySeconds(), config.getCheckIntervalSeconds(), TimeUnit.SECONDS);
});

updateConnectionStatus(ConnectionStatus.CONNECTED);
Expand Down Expand Up @@ -140,7 +140,8 @@ protected void withFolder(Consumer<Folder> folderConsumer) throws Exception {
LOG.log(System.Logger.Level.INFO, "Connecting to mail server: " + config.getHost());

try (Store mailStore = session.getStore()) {
mailStore.connect(config.getUser(), config.getPassword());
UsernamePassword usernamePassword = config.getAuth();
mailStore.connect(usernamePassword.getUsername(), usernamePassword.getPassword());
Folder mailFolder = mailStore.getFolder(config.getFolder());

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,76 @@
*/
package org.openremote.agent.protocol.mail;

import org.openremote.container.web.OAuthFilter;
import org.openremote.container.web.WebTargetBuilder;
import org.openremote.model.auth.OAuthGrant;
import org.openremote.model.auth.UsernamePassword;

import javax.ws.rs.client.Client;
import java.net.SocketException;
import java.nio.file.Path;
import java.util.Date;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;

public class MailClientBuilder {
public static final String DEFAULT_FOLDER_NAME = "INBOX";
public static final int DEFAULT_CHECK_INTERVAL_MILLIS = 5 * 60000;
protected static int MIN_CHECK_INTERVAL_MILLIS = 10000;
public static final int DEFAULT_CHECK_INTERVAL_SECONDS = 5 * 60;
protected static int MIN_CHECK_INTERVAL_SECONDS = 10;
protected static final AtomicReference<Client> jaxrsClient = new AtomicReference<>();
protected ScheduledExecutorService scheduledExecutorService;
protected int checkInitialDelayMillis = 0;
protected int checkIntervalMillis = DEFAULT_CHECK_INTERVAL_MILLIS;
protected int checkInitialDelaySeconds = 0;
protected int checkIntervalSeconds = DEFAULT_CHECK_INTERVAL_SECONDS;
protected String folder;
protected boolean preferHTML;
protected Date earliestMessageDate;
protected Path persistenceDir;
protected String protocol;
protected String host;
protected OAuthGrant oAuthGrant;
protected OAuthFilter oAuthFilter;
protected int port;
protected String user;
protected String password;
protected boolean deleteMessageOnceProcessed;
protected Properties properties = new Properties();

public MailClientBuilder(ScheduledExecutorService scheduledExecutorService, String protocol, String host, int port, String user, String password) {
public MailClientBuilder(ScheduledExecutorService scheduledExecutorService, String protocol, String host, int port) {
this.scheduledExecutorService = scheduledExecutorService;
this.protocol = protocol;
this.host = host;
this.port = port;
this.user = user;
this.password = password;

properties.put("mail.store.protocol", protocol);
properties.put("mail." + protocol + ".host", host);
properties.put("mail." + protocol + ".port", port);
}

public MailClientBuilder setCheckIntervalMillis(int checkIntervalMillis) {
this.checkIntervalMillis = Math.max(checkIntervalMillis, MIN_CHECK_INTERVAL_MILLIS);
public MailClientBuilder setBasicAuth(String user, String password) {
this.user = user;
this.password = password;
return this;
}

public MailClientBuilder setOAuth(String user, OAuthGrant oAuthGrant) {
this.user = user;
this.oAuthGrant = oAuthGrant;
setProperty("auth.mechanisms", "XOAUTH2");
setProperty("mail.imap.sasl.enable", "true");
setProperty("auth.login.disable", "true");
setProperty("auth.plain.disable", "true");
return this;
}

public MailClientBuilder setCheckInitialDelayMillis(int checkInitialDelayMillis) {
this.checkInitialDelayMillis = Math.max(checkInitialDelayMillis, 0);
public MailClientBuilder setCheckIntervalSeconds(int checkIntervalSeconds) {
this.checkIntervalSeconds = Math.max(checkIntervalSeconds, MIN_CHECK_INTERVAL_SECONDS);
return this;
}

public MailClientBuilder setCheckInitialDelaySeconds(int checkInitialDelaySeconds) {
this.checkInitialDelaySeconds = Math.max(checkInitialDelaySeconds, 0);
return this;
}

Expand Down Expand Up @@ -90,6 +116,9 @@ public MailClientBuilder setDeleteMessageOnceProcessed(boolean deleteMessageOnce
}

public MailClientBuilder setProperty(String property, Object value) {
if (!property.startsWith("mail." + protocol + ".")) {
property = "mail." + protocol + "." + property;
}
properties.put(property, value);
return this;
}
Expand All @@ -107,12 +136,12 @@ public ScheduledExecutorService getScheduledExecutorService() {
return scheduledExecutorService;
}

public int getCheckIntervalMillis() {
return checkIntervalMillis;
public int getCheckIntervalSeconds() {
return checkIntervalSeconds;
}

public int getCheckInitialDelayMillis() {
return checkInitialDelayMillis;
public int getCheckInitialDelaySeconds() {
return checkInitialDelaySeconds;
}

public Date getEarliestMessageDate() {
Expand Down Expand Up @@ -143,6 +172,10 @@ public String getPassword() {
return password;
}

public OAuthGrant getOAuthGrant() {
return oAuthGrant;
}

public boolean isDeleteMessageOnceProcessed() {
return deleteMessageOnceProcessed;
}
Expand All @@ -158,4 +191,26 @@ public Properties getProperties() {
public MailClient build() {
return new MailClient(this);
}

UsernamePassword getAuth() throws SocketException, NullPointerException {
Objects.requireNonNull(user, "User must be supplied");

if (oAuthGrant != null) {
synchronized (jaxrsClient) {
if (jaxrsClient.get() == null) {
jaxrsClient.set(WebTargetBuilder.createClient(scheduledExecutorService));
}
}
synchronized (this) {
if (oAuthFilter == null) {
oAuthFilter = new OAuthFilter(jaxrsClient.get(), oAuthGrant);
}
}
return new UsernamePassword(user, oAuthFilter.getAccessToken());
}

Objects.requireNonNull(password, "Password must be supplied");

return new UsernamePassword(user, password);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package org.openremote.agent.protocol.mail;

import org.openremote.container.timer.TimerService;
import org.openremote.model.attribute.Attribute;
import org.openremote.model.auth.UsernamePassword;
import org.openremote.model.mail.MailMessage;
Expand All @@ -35,7 +36,6 @@ public MailProtocol(MailAgent agent) {
super(agent);
}


@Override
public String getProtocolName() {
return PROTOCOL_DISPLAY_NAME;
Expand All @@ -54,7 +54,10 @@ protected String getMailMessageAttributeValue(String assetId, Attribute<?> attri

@Override
protected Predicate<MailMessage> getAttributeMailMessageFilter(String assetId, Attribute<?> attribute, MailAgentLink agentLink) {
return getAttributeMailMessageFilter(timerService, agentLink);
}

public static <T extends MailAgentLink> Predicate<MailMessage> getAttributeMailMessageFilter(TimerService timerService, T agentLink) {
StringPredicate fromPredicate = agentLink.getFromMatchPredicate();
StringPredicate subjectPredicate = agentLink.getSubjectMatchPredicate();

Expand Down
Loading

0 comments on commit 91185f2

Please sign in to comment.