diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 571f32d1719..f95dfcb6485 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -34,8 +33,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -49,7 +48,6 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.config.annotations.ApiOperation; -import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.ActivateUserRequest; @@ -105,9 +103,8 @@ public void logout(HttpServletRequest request) throws ThingsboardException { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public ObjectNode changePassword( - @Parameter(description = "Change Password Request") - @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException { + public JwtPair changePassword(@Parameter(description = "Change Password Request") + @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException { String currentPassword = changePasswordRequest.getCurrentPassword(); String newPassword = changePasswordRequest.getNewPassword(); SecurityUser securityUser = getCurrentUser(); @@ -123,10 +120,7 @@ public ObjectNode changePassword( userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); - ObjectNode response = JacksonUtil.newObjectNode(); - response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken()); - response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken()); - return response; + return tokenFactory.createTokenPair(securityUser); } @ApiOperation(value = "Get the current User password policy (getUserPasswordPolicy)", diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java index 43cac31744c..7b3224a138d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.security.model; +import lombok.Getter; +import lombok.Setter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.thingsboard.server.common.data.User; @@ -30,9 +32,12 @@ public class SecurityUser extends User { private static final long serialVersionUID = -797397440703066079L; private Collection authorities; + @Getter @Setter private boolean enabled; + @Getter @Setter private UserPrincipal userPrincipal; - private String sessionId; + @Getter @Setter + private String sessionId = UUID.randomUUID().toString(); public SecurityUser() { super(); @@ -46,7 +51,6 @@ public SecurityUser(User user, boolean enabled, UserPrincipal userPrincipal) { super(user); this.enabled = enabled; this.userPrincipal = userPrincipal; - this.sessionId = UUID.randomUUID().toString(); } public Collection getAuthorities() { @@ -58,27 +62,4 @@ public Collection getAuthorities() { return authorities; } - public boolean isEnabled() { - return enabled; - } - - public UserPrincipal getUserPrincipal() { - return userPrincipal; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public void setUserPrincipal(UserPrincipal userPrincipal) { - this.userPrincipal = userPrincipal; - } - - public String getSessionId() { - return sessionId; - } - - public void setSessionId(String sessionId) { - this.sessionId = sessionId; - } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index a47d0034a77..f68d954aa16 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -232,6 +232,7 @@ public Jws parseTokenClaims(String token) { } public JwtPair createTokenPair(SecurityUser securityUser) { + securityUser.setSessionId(UUID.randomUUID().toString()); JwtToken accessToken = createAccessJwtToken(securityUser); JwtToken refreshToken = createRefreshToken(securityUser); return new JwtPair(accessToken.getToken(), refreshToken.getToken()); diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java index e6c6cfbac48..e9d874084d4 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java @@ -73,16 +73,7 @@ public void beforeEach() { @Test public void testCreateAndParseAccessJwtToken() { - SecurityUser securityUser = new SecurityUser(); - securityUser.setId(new UserId(UUID.randomUUID())); - securityUser.setEmail("tenant@thingsboard.org"); - securityUser.setAuthority(Authority.TENANT_ADMIN); - securityUser.setTenantId(new TenantId(UUID.randomUUID())); - securityUser.setEnabled(true); - securityUser.setFirstName("A"); - securityUser.setLastName("B"); - securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail())); - securityUser.setCustomerId(new CustomerId(UUID.randomUUID())); + SecurityUser securityUser = createSecurityUser(); testCreateAndParseAccessJwtToken(securityUser); @@ -111,18 +102,12 @@ public void testCreateAndParseAccessJwtToken(SecurityUser securityUser) { assertThat(parsedSecurityUser.getCustomerId()).isEqualTo(securityUser.getCustomerId()); assertThat(parsedSecurityUser.getFirstName()).isEqualTo(securityUser.getFirstName()); assertThat(parsedSecurityUser.getLastName()).isEqualTo(securityUser.getLastName()); + assertThat(parsedSecurityUser.getSessionId()).isNotNull().isEqualTo(securityUser.getSessionId()); } @Test public void testCreateAndParseRefreshJwtToken() { - SecurityUser securityUser = new SecurityUser(); - securityUser.setId(new UserId(UUID.randomUUID())); - securityUser.setEmail("tenant@thingsboard.org"); - securityUser.setAuthority(Authority.TENANT_ADMIN); - securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail())); - securityUser.setEnabled(true); - securityUser.setTenantId(new TenantId(UUID.randomUUID())); - securityUser.setCustomerId(new CustomerId(UUID.randomUUID())); + SecurityUser securityUser = createSecurityUser(); JwtToken refreshToken = tokenFactory.createRefreshToken(securityUser); checkExpirationTime(refreshToken, jwtSettings.getRefreshTokenExpTime()); @@ -138,15 +123,7 @@ public void testCreateAndParseRefreshJwtToken() { @Test public void testCreateAndParsePreVerificationJwtToken() { - SecurityUser securityUser = new SecurityUser(); - securityUser.setId(new UserId(UUID.randomUUID())); - securityUser.setEmail("tenant@thingsboard.org"); - securityUser.setAuthority(Authority.TENANT_ADMIN); - securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail())); - securityUser.setEnabled(true); - securityUser.setTenantId(new TenantId(UUID.randomUUID())); - securityUser.setCustomerId(new CustomerId(UUID.randomUUID())); - + SecurityUser securityUser = createSecurityUser(); int tokenLifetime = (int) TimeUnit.MINUTES.toSeconds(30); JwtToken preVerificationToken = tokenFactory.createPreVerificationToken(securityUser, tokenLifetime); checkExpirationTime(preVerificationToken, tokenLifetime); @@ -162,6 +139,34 @@ public void testCreateAndParsePreVerificationJwtToken() { }); } + @Test + public void testSessionId() { + SecurityUser securityUser = createSecurityUser(); + String sessionId = securityUser.getSessionId(); + + String accessToken = tokenFactory.createAccessJwtToken(securityUser).getToken(); + securityUser = tokenFactory.parseAccessJwtToken(accessToken); + assertThat(securityUser.getSessionId()).isNotNull().isEqualTo(sessionId); + + String newAccessToken = tokenFactory.createTokenPair(securityUser).getToken(); + securityUser = tokenFactory.parseAccessJwtToken(newAccessToken); + assertThat(securityUser.getSessionId()).isNotNull().isNotEqualTo(sessionId); + } + + private SecurityUser createSecurityUser() { + SecurityUser securityUser = new SecurityUser(); + securityUser.setId(new UserId(UUID.randomUUID())); + securityUser.setEmail("tenant@thingsboard.org"); + securityUser.setAuthority(Authority.TENANT_ADMIN); + securityUser.setTenantId(new TenantId(UUID.randomUUID())); + securityUser.setEnabled(true); + securityUser.setFirstName("A"); + securityUser.setLastName("B"); + securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail())); + securityUser.setCustomerId(new CustomerId(UUID.randomUUID())); + return securityUser; + } + private void mockJwtSettings(JwtSettings settings) { AdminSettings adminJwtSettings = new AdminSettings(); adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(settings)); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java index 5b6d20adfda..f6b0bdb9c6a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java @@ -18,17 +18,17 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.validation.NoXss; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -64,6 +64,23 @@ public void setAdditionalInfo(JsonNode addInfo) { setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); } + public void setAdditionalInfoField(String field, JsonNode value) { + JsonNode additionalInfo = getAdditionalInfo(); + if (!(additionalInfo instanceof ObjectNode)) { + additionalInfo = mapper.createObjectNode(); + } + ((ObjectNode) additionalInfo).set(field, value); + setAdditionalInfo(additionalInfo); + } + + public T getAdditionalInfoField(String field, Function mapper, T defaultValue) { + JsonNode additionalInfo = getAdditionalInfo(); + if (additionalInfo != null && additionalInfo.has(field)) { + return mapper.apply(additionalInfo.get(field)); + } + return defaultValue; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java index ae484136ce7..d95855fe048 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.security.model; +import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; @@ -25,6 +26,7 @@ @Schema(description = "JWT Pair") @Data @NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class JwtPair implements Serializable { @Schema(description = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..")