diff --git a/application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java b/application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java index 0a9b173247..3872fc4c7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java @@ -23,18 +23,18 @@ import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.lang.Nullable; +import org.springframework.mail.MailException; +import org.springframework.mail.MailSendException; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mail.MailOauth2Provider; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.time.Duration; import java.time.Instant; @@ -50,6 +50,7 @@ public class TbMailSender extends JavaMailSenderImpl { private static final String MAIL_PROP = "mail."; private final TbMailContextComponent ctx; private final Lock lock; + private final Boolean oauth2Enabled; private volatile String accessToken; private volatile long tokenExpires; @@ -70,14 +71,39 @@ public class TbMailSender extends JavaMailSenderImpl { setJavaMailProperties(createJavaMailProperties(jsonConfig)); } - @SneakyThrows + public Boolean getOauth2Enabled() { + return oauth2Enabled; + } + + public long getTokenExpires() { + return tokenExpires; + } + + @Override + protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException { + updateOauth2PasswordIfExpired(); + doSendSuper(mimeMessages, originalMessages); + } + + public void doSendSuper(MimeMessage[] mimeMessages, Object[] originalMessages) { + super.doSend(mimeMessages, originalMessages); + } + @Override - public void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) { - if (oauth2Enabled && (System.currentTimeMillis() > tokenExpires)){ + public void testConnection() throws MessagingException { + updateOauth2PasswordIfExpired(); + testConnectionSuper(); + } + + public void testConnectionSuper() throws MessagingException { + super.testConnection(); + } + + public void updateOauth2PasswordIfExpired() { + if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())){ refreshAccessToken(); setPassword(accessToken); } - super.doSend(mimeMessages, originalMessages); } private Properties createJavaMailProperties(JsonNode jsonConfig) { @@ -125,10 +151,10 @@ public class TbMailSender extends JavaMailSenderImpl { return javaMailProperties; } - public void refreshAccessToken() throws ThingsboardException { + public void refreshAccessToken() { lock.lock(); try { - if (System.currentTimeMillis() > tokenExpires) { + if (System.currentTimeMillis() > getTokenExpires()) { AdminSettings settings = ctx.getAdminSettingsService().findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); JsonNode jsonValue = settings.getJsonValue(); @@ -151,8 +177,8 @@ public class TbMailSender extends JavaMailSenderImpl { tokenExpires = System.currentTimeMillis() + (tokenResponse.getExpiresInSeconds().intValue() * 1000); } } catch (Exception e) { - log.warn("Unable to retrieve access token: {}", e.getMessage()); - throw new ThingsboardException("Error while retrieving access token: " + e.getMessage(), ThingsboardErrorCode.GENERAL); + log.error("Unable to retrieve access token: {}", e.getMessage()); + throw new RuntimeException("Error while retrieving access token: " + e.getMessage()); } finally { lock.unlock(); } diff --git a/application/src/test/java/org/thingsboard/server/service/mail/TbMailSenderTest.java b/application/src/test/java/org/thingsboard/server/service/mail/TbMailSenderTest.java new file mode 100644 index 0000000000..5b95adf526 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/mail/TbMailSenderTest.java @@ -0,0 +1,95 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * 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 org.thingsboard.server.service.mail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willCallRealMethod; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +public class TbMailSenderTest { + + private TbMailSender tbMailSender; + + @BeforeEach + void setUp() { + tbMailSender = mock(TbMailSender.class); + } + + @Test + public void testDoSendSendMail() { + MimeMessage mimeMsg = new MimeMessage(Session.getInstance(new Properties())); + List mimeMessages = new ArrayList<>(1); + mimeMessages.add(mimeMsg); + + willCallRealMethod().given(tbMailSender).doSend(any(), any()); + tbMailSender.doSend(mimeMessages.toArray(new MimeMessage[0]), null); + + Mockito.verify(tbMailSender, times(1)).updateOauth2PasswordIfExpired(); + Mockito.verify(tbMailSender, times(1)).doSendSuper(any(), any()); + } + + @Test + public void testTestConnection() throws MessagingException { + willCallRealMethod().given(tbMailSender).testConnection(); + tbMailSender.testConnection(); + + Mockito.verify(tbMailSender, times(1)).updateOauth2PasswordIfExpired(); + Mockito.verify(tbMailSender, times(1)).testConnectionSuper(); + } + + @ParameterizedTest + @MethodSource("provideSenderConfiguration") + public void testUpdateOauth2PasswordIfExpiredIfOauth2Enabled(boolean oauth2, long expiresIn, boolean passwordUpdateNeeded) { + willReturn(oauth2).given(tbMailSender).getOauth2Enabled(); + willReturn(expiresIn).given(tbMailSender).getTokenExpires(); + + willCallRealMethod().given(tbMailSender).updateOauth2PasswordIfExpired(); + tbMailSender.updateOauth2PasswordIfExpired(); + + if (passwordUpdateNeeded) { + Mockito.verify(tbMailSender, times(1)).refreshAccessToken(); + Mockito.verify(tbMailSender, times(1)).setPassword(any()); + } else { + Mockito.verify(tbMailSender, Mockito.never()).refreshAccessToken(); + Mockito.verify(tbMailSender, Mockito.never()).setPassword(any()); + } + } + + private static Stream provideSenderConfiguration() { + return Stream.of( + Arguments.of(true, 0L, true), + Arguments.of(true, System.currentTimeMillis() + 5000, false), + Arguments.of(false, 0L, false), + Arguments.of(false, System.currentTimeMillis() + 5000, false) + ); + } +}