Browse Source

Add activation link info API

pull/11578/head
ViacheslavKlimov 2 years ago
parent
commit
9c68204845
  1. 30
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  2. 32
      application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java
  3. 7
      common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java

30
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -229,19 +229,30 @@ public class UserController extends BaseController {
User user = checkNotNull(userService.findUserByEmail(getCurrentUser().getTenantId(), email));
accessControlService.checkPermission(getCurrentUser(), Resource.USER, Operation.READ, user.getId(), user);
String activationLink = getActivationLink(user.getId(), request);
mailService.sendActivationEmail(activationLink, email);
ActivationLink activationLink = getActivationLink(user.getId(), request);
mailService.sendActivationEmail(activationLink.value(), email);
}
@ApiOperation(value = "Get the activation link (getActivationLink)",
@ApiOperation(value = "Get activation link (getActivationLink)",
notes = "Get the activation link for the user. " +
"The base url for activation link is configurable in the general settings of system administrator. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/{userId}/activationLink", method = RequestMethod.GET, produces = "text/plain")
@GetMapping(value = "/user/{userId}/activationLink", produces = "text/plain")
@ResponseBody
public String getActivationLink(@Parameter(description = USER_ID_PARAM_DESCRIPTION)
@PathVariable(USER_ID) String strUserId,
HttpServletRequest request) throws ThingsboardException {
return getActivationLinkInfo(strUserId, request).value();
}
@ApiOperation(value = "Get activation link info (getActivationLinkInfo)",
notes = "Get the activation link info for the user. " +
"The base url for activation link is configurable in the general settings of system administrator. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/user/{userId}/activationLinkInfo")
public ActivationLink getActivationLinkInfo(@Parameter(description = USER_ID_PARAM_DESCRIPTION)
@PathVariable(USER_ID) String strUserId,
HttpServletRequest request) throws ThingsboardException {
checkParameter(USER_ID, strUserId);
UserId userId = new UserId(toUUID(strUserId));
checkUserId(userId, Operation.READ);
@ -587,17 +598,20 @@ public class UserController extends BaseController {
userService.removeMobileSession(user.getTenantId(), mobileToken);
}
private String getActivationLink(UserId userId, HttpServletRequest request) throws ThingsboardException {
private ActivationLink getActivationLink(UserId userId, HttpServletRequest request) throws ThingsboardException {
TenantId tenantId = getTenantId();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(tenantId, userId);
if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
if (System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(15) > userCredentials.getActivateTokenExpTime()) { // renew link if less than 15 minutes before expiration
long ttl = userCredentials.getActivationTokenTtl();
if (ttl < TimeUnit.MINUTES.toMillis(15)) { // renew link if less than 15 minutes before expiration
userCredentials = userService.generateUserActivationToken(userCredentials);
userCredentials = userService.saveUserCredentials(tenantId, userCredentials);
ttl = userCredentials.getActivationTokenTtl();
log.debug("[{}][{}] Regenerated expired user activation token", tenantId, userId);
}
String baseUrl = systemSecurityService.getBaseUrl(tenantId, getCurrentUser().getCustomerId(), request);
return String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken());
String link = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken());
return new ActivationLink(link, ttl);
} else {
throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
@ -609,4 +623,6 @@ public class UserController extends BaseController {
}
}
record ActivationLink(String value, long ttlMs) {}
}

32
application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.controller.UserController.ActivationLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.user.UserCredentialsDao;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@ -38,6 +39,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@ -217,8 +219,11 @@ public class AuthControllerTest extends AbstractControllerTest {
UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId());
assertThat(userCredentials.getActivateTokenExpTime()).isCloseTo(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(ttl), Offset.offset(120000L));
String initialActivationLink = doGet("/api/user/" + user.getId() + "/activationLink", String.class);
String initialActivationLink = getActivationLink(user);
String initialActivationToken = StringUtils.substringAfterLast(initialActivationLink, "activateToken=");
ActivationLink activationLinkInfo = getActivationLinkInfo(user);
assertThat(TimeUnit.MILLISECONDS.toHours(activationLinkInfo.ttlMs())).isCloseTo(ttl, within(1L));
assertThat(activationLinkInfo.value()).isEqualTo(initialActivationLink);
// expiring activation token
userCredentials.setActivateTokenExpTime(System.currentTimeMillis() - 1);
@ -231,19 +236,24 @@ public class AuthControllerTest extends AbstractControllerTest {
.andExpect(jsonPath("$.message", is("Activation token expired")));
// checking that activation link is regenerated when requested
String regeneratedActivationLink = doGet("/api/user/" + user.getId() + "/activationLink", String.class);
assertThat(regeneratedActivationLink).isNotEqualTo(initialActivationLink);
ActivationLink regeneratedActivationLink = getActivationLinkInfo(user);
assertThat(regeneratedActivationLink.value()).isNotEqualTo(initialActivationLink);
assertThat(TimeUnit.MILLISECONDS.toHours(regeneratedActivationLink.ttlMs())).isCloseTo(ttl, within(1L));
// checking link renewal if less than 15 minutes before expiration
userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId());
userCredentials.setActivateTokenExpTime(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(30));
userCredentialsDao.save(tenantId, userCredentials);
assertThat(doGet("/api/user/" + user.getId() + "/activationLink", String.class)).isEqualTo(regeneratedActivationLink);
activationLinkInfo = getActivationLinkInfo(user);
assertThat(activationLinkInfo.value()).isEqualTo(regeneratedActivationLink.value());
assertThat(TimeUnit.MILLISECONDS.toMinutes(activationLinkInfo.ttlMs())).isCloseTo(30, within(1L));
userCredentials.setActivateTokenExpTime(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
userCredentialsDao.save(tenantId, userCredentials);
String newActivationLink = doGet("/api/user/" + user.getId() + "/activationLink", String.class);
assertThat(newActivationLink).isNotEqualTo(regeneratedActivationLink);
String newActivationToken = StringUtils.substringAfterLast(newActivationLink, "activateToken=");
ActivationLink newActivationLink = getActivationLinkInfo(user);
assertThat(newActivationLink.value()).isNotEqualTo(regeneratedActivationLink.value());
assertThat(TimeUnit.MILLISECONDS.toHours(newActivationLink.ttlMs())).isCloseTo(ttl, within(1L));
String newActivationToken = StringUtils.substringAfterLast(newActivationLink.value(), "activateToken=");
userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId());
assertThat(userCredentials.getActivateTokenExpTime()).isCloseTo(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(ttl), Offset.offset(120000L));
@ -265,4 +275,12 @@ public class AuthControllerTest extends AbstractControllerTest {
doPost("/api/admin/securitySettings", securitySettings).andExpect(status().isOk());
}
private String getActivationLink(User user) throws Exception {
return doGet("/api/user/" + user.getId() + "/activationLink", String.class);
}
private ActivationLink getActivationLinkInfo(User user) throws Exception {
return doGet("/api/user/" + user.getId() + "/activationLinkInfo", ActivationLink.class);
}
}

7
common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java

@ -52,7 +52,12 @@ public class UserCredentials extends BaseDataWithAdditionalInfo<UserCredentialsI
@JsonIgnore
public boolean isActivationTokenExpired() {
return activateTokenExpTime == null || System.currentTimeMillis() > activateTokenExpTime;
return getActivationTokenTtl() == 0;
}
@JsonIgnore
public long getActivationTokenTtl() {
return activateTokenExpTime != null ? Math.max(activateTokenExpTime - System.currentTimeMillis(), 0) : 0;
}
@JsonIgnore

Loading…
Cancel
Save