Add checks for SMS and mail service when setting up 2FA provider; 2Fa verification mail template
This commit is contained in:
parent
bd43ebc204
commit
41fede9b43
@ -345,18 +345,6 @@
|
||||
<groupId>org.jboss.aerogear</groupId>
|
||||
<artifactId>aerogear-otp-java</artifactId>
|
||||
</dependency>
|
||||
<!-- TMP -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>javase</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
<!-- TMP -->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -15,10 +15,6 @@
|
||||
*/
|
||||
package org.thingsboard.server.controller;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.Data;
|
||||
@ -32,11 +28,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
|
||||
@ -45,8 +39,6 @@ import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -113,21 +105,6 @@ public class TwoFaConfigController extends BaseController {
|
||||
return twoFactorAuthService.generateNewAccountConfig(user, providerType);
|
||||
}
|
||||
|
||||
/* TMP */
|
||||
@PostMapping("/account/config/tmp/generate/qr")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
public void generateTwoFaAccountConfigWithQr(@RequestParam TwoFaProviderType providerType, HttpServletResponse response) throws Exception {
|
||||
TwoFaAccountConfig config = generateTwoFaAccountConfig(providerType);
|
||||
if (providerType == TwoFaProviderType.TOTP) {
|
||||
BitMatrix qr = new QRCodeWriter().encode(((TotpTwoFaAccountConfig) config).getAuthUrl(), BarcodeFormat.QR_CODE, 200, 200);
|
||||
try (ServletOutputStream outputStream = response.getOutputStream()) {
|
||||
MatrixToImageWriter.writeToStream(qr, "PNG", outputStream);
|
||||
}
|
||||
}
|
||||
response.setHeader("config", JacksonUtil.toString(config));
|
||||
}
|
||||
/* TMP */
|
||||
|
||||
@ApiOperation(value = "Submit 2FA account config (submitTwoFaAccountConfig)",
|
||||
notes = "Submit 2FA account config to prepare for a future verification. " +
|
||||
"Basically, this method will send a verification code for a given account config, if this has " +
|
||||
|
||||
@ -27,9 +27,12 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.EmailTwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig;
|
||||
import org.thingsboard.server.dao.user.UserService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
@ -104,9 +107,9 @@ public class TwoFactorAuthController extends BaseController {
|
||||
@ApiOperation(value = "Get available 2FA providers (getAvailableTwoFaProviders)", notes =
|
||||
"Get the list of 2FA provider infos available for user to use. Example:\n" +
|
||||
"```\n[\n" +
|
||||
" {\n \"type\": \"EMAIL\",\n \"default\": true\n },\n" +
|
||||
" {\n \"type\": \"TOTP\",\n \"default\": false\n },\n" +
|
||||
" {\n \"type\": \"SMS\",\n \"default\": false\n }\n" +
|
||||
" {\n \"type\": \"EMAIL\",\n \"default\": true,\n \"contact\": \"ab*****ko@gmail.com\"\n },\n" +
|
||||
" {\n \"type\": \"TOTP\",\n \"default\": false,\n \"contact\": null\n },\n" +
|
||||
" {\n \"type\": \"SMS\",\n \"default\": false,\n \"contact\": \"+38********12\"\n }\n" +
|
||||
"]\n```")
|
||||
@GetMapping("/providers")
|
||||
@PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')")
|
||||
@ -114,10 +117,24 @@ public class TwoFactorAuthController extends BaseController {
|
||||
SecurityUser user = getCurrentUser();
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId())
|
||||
.map(settings -> settings.getConfigs().values()).orElse(Collections.emptyList())
|
||||
.stream().map(config -> TwoFaProviderInfo.builder()
|
||||
.stream().map(config -> {
|
||||
String contact = null;
|
||||
switch (config.getProviderType()) {
|
||||
case SMS:
|
||||
String phoneNumber = ((SmsTwoFaAccountConfig) config).getPhoneNumber();
|
||||
contact = StringUtils.obfuscate(phoneNumber, 2, '*', phoneNumber.indexOf('+') + 1, phoneNumber.length());
|
||||
break;
|
||||
case EMAIL:
|
||||
String email = ((EmailTwoFaAccountConfig) config).getEmail();
|
||||
contact = StringUtils.obfuscate(email, 2, '*', 0, email.indexOf('@'));
|
||||
break;
|
||||
}
|
||||
return TwoFaProviderInfo.builder()
|
||||
.type(config.getProviderType())
|
||||
.isDefault(config.isUseByDefault())
|
||||
.build())
|
||||
.contact(contact)
|
||||
.build();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ -127,6 +144,7 @@ public class TwoFactorAuthController extends BaseController {
|
||||
public static class TwoFaProviderInfo {
|
||||
private TwoFaProviderType type;
|
||||
private boolean isDefault;
|
||||
private String contact;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
|
||||
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.HashMap;
|
||||
@ -100,7 +101,17 @@ public class DefaultMailService implements MailService {
|
||||
mailSender = createMailSender(jsonConfig);
|
||||
mailFrom = jsonConfig.get("mailFrom").asText();
|
||||
} else {
|
||||
throw new IncorrectParameterException("Failed to date mail configuration. Settings not found!");
|
||||
throw new IncorrectParameterException("Failed to update mail configuration. Settings not found!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured(TenantId tenantId) {
|
||||
try {
|
||||
mailSender.testConnection();
|
||||
return true;
|
||||
} catch (MessagingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,9 +322,12 @@ public class DefaultMailService implements MailService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTwoFaVerificationEmail(String email, String verificationCode) throws ThingsboardException { // TODO [viacheslav]: mail template
|
||||
String subject = "ThingsBoard two-factor authentication";
|
||||
String message = "Your 2FA verification code: " + verificationCode;
|
||||
public void sendTwoFaVerificationEmail(String email, String verificationCode) throws ThingsboardException {
|
||||
String subject = messages.getMessage("2fa.verification.code.subject", null, Locale.US);
|
||||
String message = mergeTemplateIntoString("2fa.verification.code.ftl", Map.of(
|
||||
TARGET_EMAIL, email,
|
||||
"verificationCode", verificationCode
|
||||
));
|
||||
|
||||
sendMail(mailSender, mailFrom, email, subject, message);
|
||||
}
|
||||
|
||||
@ -68,6 +68,11 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkProvider(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException {
|
||||
getTwoFaProvider(providerType).check(tenantId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception {
|
||||
|
||||
@ -27,6 +27,8 @@ public interface TwoFactorAuthService {
|
||||
|
||||
boolean isTwoFaEnabled(TenantId tenantId, UserId userId);
|
||||
|
||||
void checkProvider(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException;
|
||||
|
||||
|
||||
void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception;
|
||||
|
||||
|
||||
@ -17,10 +17,13 @@ package org.thingsboard.server.service.security.auth.mfa.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.AdminSettings;
|
||||
import org.thingsboard.server.common.data.DataConstants;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||
@ -36,6 +39,7 @@ import org.thingsboard.server.dao.service.ConstraintValidator;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsDao;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||
import org.thingsboard.server.dao.user.UserAuthSettingsDao;
|
||||
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -51,6 +55,8 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
private final AdminSettingsService adminSettingsService;
|
||||
private final AdminSettingsDao adminSettingsDao;
|
||||
private final AttributesService attributesService;
|
||||
@Autowired @Lazy
|
||||
private TwoFactorAuthService twoFactorAuthService;
|
||||
|
||||
protected static final String TWO_FACTOR_AUTH_SETTINGS_KEY = "twoFaSettings";
|
||||
|
||||
@ -147,10 +153,13 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
|
||||
@SneakyThrows({InterruptedException.class, ExecutionException.class})
|
||||
@Override
|
||||
public void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) {
|
||||
public void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException {
|
||||
if (tenantId.equals(TenantId.SYS_TENANT_ID) || !twoFactorAuthSettings.isUseSystemTwoFactorAuthSettings()) {
|
||||
ConstraintValidator.validateFields(twoFactorAuthSettings);
|
||||
}
|
||||
for (TwoFaProviderConfig providerConfig : twoFactorAuthSettings.getProviders()) {
|
||||
twoFactorAuthService.checkProvider(tenantId, providerConfig.getProviderType());
|
||||
}
|
||||
if (tenantId.equals(TenantId.SYS_TENANT_ID)) {
|
||||
AdminSettings settings = Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(tenantId, TWO_FACTOR_AUTH_SETTINGS_KEY))
|
||||
.orElseGet(() -> {
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.server.service.security.auth.mfa.config;
|
||||
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
|
||||
@ -38,7 +39,7 @@ public interface TwoFaConfigManager {
|
||||
|
||||
Optional<PlatformTwoFaSettings> getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault);
|
||||
|
||||
void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings);
|
||||
void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException;
|
||||
|
||||
void deletePlatformTwoFaSettings(TenantId tenantId);
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.auth.mfa.provider;
|
||||
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
|
||||
@ -30,6 +31,8 @@ public interface TwoFaProvider<C extends TwoFaProviderConfig, A extends TwoFaAcc
|
||||
|
||||
boolean checkVerificationCode(SecurityUser user, String verificationCode, C providerConfig, A accountConfig);
|
||||
|
||||
default void check(TenantId tenantId) throws ThingsboardException {};
|
||||
|
||||
|
||||
TwoFaProviderType getType();
|
||||
|
||||
|
||||
@ -19,7 +19,9 @@ import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.rule.engine.api.MailService;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
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.security.model.mfa.account.EmailTwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.EmailTwoFaProviderConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
|
||||
@ -44,6 +46,13 @@ public class EmailTwoFaProvider extends OtpBasedTwoFaProvider<EmailTwoFaProvider
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(TenantId tenantId) throws ThingsboardException {
|
||||
if (!mailService.isConfigured(tenantId)) {
|
||||
throw new ThingsboardException("Mail service is not set up", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendVerificationCode(SecurityUser user, String verificationCode, EmailTwoFaProviderConfig providerConfig, EmailTwoFaAccountConfig accountConfig) throws ThingsboardException {
|
||||
mailService.sendTwoFaVerificationEmail(accountConfig.getEmail(), verificationCode);
|
||||
|
||||
@ -20,7 +20,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.rule.engine.api.SmsService;
|
||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
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.security.model.mfa.account.SmsTwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFaProviderConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
|
||||
@ -58,6 +60,13 @@ public class SmsTwoFaProvider extends OtpBasedTwoFaProvider<SmsTwoFaProviderConf
|
||||
smsService.sendSms(user.getTenantId(), user.getCustomerId(), new String[]{phoneNumber}, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(TenantId tenantId) throws ThingsboardException {
|
||||
if (!smsService.isConfigured(tenantId)) {
|
||||
throw new ThingsboardException("SMS service in not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TwoFaProviderType getType() {
|
||||
|
||||
@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.AdminSettings;
|
||||
import org.thingsboard.server.common.data.ApiUsageRecordKey;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
@ -87,6 +86,11 @@ public class DefaultSmsService implements SmsService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured(TenantId tenantId) {
|
||||
return smsSender != null;
|
||||
}
|
||||
|
||||
private int sendSms(String numberTo, String message) throws ThingsboardException {
|
||||
if (this.smsSender == null) {
|
||||
throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL);
|
||||
|
||||
@ -5,3 +5,4 @@ reset.password.subject=Thingsboard - Password reset has been requested
|
||||
password.was.reset.subject=Thingsboard - your account password has been reset
|
||||
account.lockout.subject=Thingsboard - User account has been lockout
|
||||
api.usage.state=Thingsboard - Account limits
|
||||
2fa.verification.code.subject=ThingsBoard - 2FA verification code
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
<#--
|
||||
|
||||
Copyright © 2016-2022 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.
|
||||
|
||||
-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>Thingsboard - Api Usage State</title>
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 800 !important;
|
||||
margin: 20px 0 5px !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 800 !important;
|
||||
margin: 20px 0 5px !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 800 !important;
|
||||
margin: 20px 0 5px !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 800 !important;
|
||||
margin: 20px 0 5px !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.invoice {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body itemscope itemtype="http://schema.org/EmailMessage"
|
||||
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
|
||||
bgcolor="#f6f6f6">
|
||||
|
||||
<table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
|
||||
<tbody>
|
||||
<tr style="box-sizing: border-box; margin: 0px;">
|
||||
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
|
||||
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 310px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
|
||||
</tr>
|
||||
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle">Your 2FA verification code: <strong>${verificationCode}</strong></td>
|
||||
</tr>
|
||||
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">— The ThingsBoard</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
|
||||
<tbody>
|
||||
<tr style="box-sizing: border-box; margin: 0px;">
|
||||
<td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to <a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a> by ThingsBoard.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@ -54,6 +54,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@ -67,11 +68,12 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
private CacheManager cacheManager;
|
||||
@Autowired
|
||||
private TwoFaConfigManager twoFaConfigManager;
|
||||
@Autowired
|
||||
@SpyBean
|
||||
private TwoFactorAuthService twoFactorAuthService;
|
||||
|
||||
@Before
|
||||
public void beforeEach() throws Exception {
|
||||
doNothing().when(twoFactorAuthService).checkProvider(any(), any());
|
||||
loginSysAdmin();
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.thingsboard.rule.engine.api.SmsService;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.audit.ActionStatus;
|
||||
@ -68,6 +69,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@ -75,7 +77,7 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
|
||||
@Autowired
|
||||
private TwoFaConfigManager twoFaConfigManager;
|
||||
@Autowired
|
||||
@SpyBean
|
||||
private TwoFactorAuthService twoFactorAuthService;
|
||||
@MockBean
|
||||
private SmsService smsService;
|
||||
@ -100,6 +102,7 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
|
||||
loginSysAdmin();
|
||||
user = createUser(user, password);
|
||||
doNothing().when(twoFactorAuthService).checkProvider(any(), any());
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.repeat;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
public static boolean isEmpty(String source) {
|
||||
@ -32,4 +34,20 @@ public class StringUtils {
|
||||
public static boolean isNotBlank(String source) {
|
||||
return source != null && !source.isEmpty() && !source.trim().isEmpty();
|
||||
}
|
||||
|
||||
public static String obfuscate(String input, int seenMargin, char obfuscationChar,
|
||||
int startIndexInclusive, int endIndexExclusive) {
|
||||
|
||||
String part = input.substring(startIndexInclusive, endIndexExclusive);
|
||||
String obfuscatedPart;
|
||||
if (part.length() <= seenMargin * 2) {
|
||||
obfuscatedPart = repeat(obfuscationChar, part.length());
|
||||
} else {
|
||||
obfuscatedPart = part.substring(0, seenMargin)
|
||||
+ repeat(obfuscationChar, part.length() - seenMargin * 2)
|
||||
+ part.substring(part.length() - seenMargin);
|
||||
}
|
||||
return input.substring(0, startIndexInclusive) + obfuscatedPart + input.substring(endIndexExclusive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ import java.util.UUID;
|
||||
@NoArgsConstructor
|
||||
@TypeDef(name = "json", typeClass = JsonStringType.class)
|
||||
@Entity
|
||||
@Table(name = ModelConstants.USER_AUTH_SETTINGS_COLUMN_FAMILY_NAME) // FIXME [viacheslav]: add to upgrade script
|
||||
@Table(name = ModelConstants.USER_AUTH_SETTINGS_COLUMN_FAMILY_NAME)
|
||||
public class UserAuthSettingsEntity extends BaseSqlEntity<UserAuthSettings> implements BaseEntity<UserAuthSettings> {
|
||||
|
||||
@Column(name = ModelConstants.USER_AUTH_SETTINGS_USER_ID_PROPERTY, nullable = false, unique = true)
|
||||
|
||||
@ -28,6 +28,8 @@ public interface MailService {
|
||||
|
||||
void updateMailConfiguration();
|
||||
|
||||
boolean isConfigured(TenantId tenantId);
|
||||
|
||||
void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException;
|
||||
|
||||
void sendTestMail(JsonNode config, String email) throws ThingsboardException;
|
||||
|
||||
@ -24,6 +24,8 @@ public interface SmsService {
|
||||
|
||||
void updateSmsConfiguration();
|
||||
|
||||
boolean isConfigured(TenantId tenantId);
|
||||
|
||||
void sendSms(TenantId tenantId, CustomerId customerId, String[] numbersTo, String message) throws ThingsboardException;;
|
||||
|
||||
void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user