184 changed files with 5161 additions and 504 deletions
@ -0,0 +1,154 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.controller; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.Parameter; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.Valid; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.DeleteMapping; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.PutMapping; |
||||
|
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.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.ApiKeyId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKey; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKeyInfo; |
||||
|
import org.thingsboard.server.config.annotations.ApiOperation; |
||||
|
import org.thingsboard.server.dao.pat.ApiKeyService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.security.permission.Operation; |
||||
|
import org.thingsboard.server.service.security.permission.Resource; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.thingsboard.server.controller.ControllerConstants.API_KEY_ID_PARAM_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.API_KEY_TEXT_SEARCH_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION; |
||||
|
|
||||
|
@RestController |
||||
|
@TbCoreComponent |
||||
|
@Slf4j |
||||
|
@RequestMapping("/api") |
||||
|
@RequiredArgsConstructor |
||||
|
public class ApiKeyController extends BaseController { |
||||
|
|
||||
|
private final ApiKeyService apiKeyService; |
||||
|
|
||||
|
@ApiOperation(value = "Save API key for user (saveApiKey)", |
||||
|
notes = "Creates an API key for the given user and returns the token ONCE as 'ApiKey <value>'." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@PostMapping(value = "/apiKey") |
||||
|
public ApiKey saveApiKey( |
||||
|
@Parameter(description = "A JSON value representing the Api Key token.") |
||||
|
@RequestBody @Valid ApiKeyInfo apiKeyInfo) throws ThingsboardException { |
||||
|
User user = checkUserId(apiKeyInfo.getUserId(), Operation.WRITE); |
||||
|
apiKeyInfo.setTenantId(user.getTenantId()); |
||||
|
checkEntity(apiKeyInfo.getId(), apiKeyInfo, Resource.API_KEY); |
||||
|
return checkNotNull(apiKeyService.saveApiKey(apiKeyInfo.getTenantId(), apiKeyInfo)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "Get User Api Keys (getUserApiKeys)", |
||||
|
notes = "Returns a page of api keys owned by user. " + |
||||
|
PAGE_DATA_PARAMETERS + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@GetMapping(value = "/apiKeys/{userId}") |
||||
|
public PageData<ApiKeyInfo> getUserApiKeys( |
||||
|
@Parameter(description = USER_ID_PARAM_DESCRIPTION) |
||||
|
@PathVariable("userId") String userIdStr, |
||||
|
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
||||
|
@RequestParam int pageSize, |
||||
|
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
||||
|
@RequestParam int page, |
||||
|
@Parameter(description = API_KEY_TEXT_SEARCH_DESCRIPTION) |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "expirationTime", "description", "enabled"})) |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) |
||||
|
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
||||
|
SecurityUser securityUser = getCurrentUser(); |
||||
|
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
||||
|
UserId userId = new UserId(toUUID(userIdStr)); |
||||
|
accessControlService.checkPermission(securityUser, Resource.API_KEY, Operation.READ); |
||||
|
User user = checkUserId(userId, Operation.READ); |
||||
|
return apiKeyService.findApiKeysByUserId(user.getTenantId(), userId, pageLink); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "Update API key Description", |
||||
|
notes = "Updates the description of the existing API key by apiKeyId. " + |
||||
|
"Only the description can be updated. " + |
||||
|
"Referencing a non-existing ApiKey Id will cause a 'Not Found' error." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@PutMapping("/apiKey/{id}/description") |
||||
|
public ApiKeyInfo updateApiKeyDescription( |
||||
|
@Parameter(description = API_KEY_ID_PARAM_DESCRIPTION, required = true) |
||||
|
@PathVariable UUID id, |
||||
|
@Parameter(description = "New description for the API key", example = "Description") |
||||
|
@RequestBody Optional<String> description) throws Exception { |
||||
|
ApiKeyId apiKeyId = new ApiKeyId(id); |
||||
|
ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE); |
||||
|
checkUserId(apiKey.getUserId(), Operation.WRITE); |
||||
|
apiKey.setDescription(description.orElse(null)); |
||||
|
return apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "Enable or disable API key (enableApiKey)", |
||||
|
notes = "Updates api key with enabled = true/false. " + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@PutMapping(value = "/apiKey/{id}/enabled/{enabledValue}") |
||||
|
public ApiKeyInfo enableApiKey( |
||||
|
@Parameter(description = "Unique identifier of the API key to enable/disable", required = true) |
||||
|
@PathVariable UUID id, |
||||
|
@Parameter(description = "Enabled or disabled api key", required = true) |
||||
|
@PathVariable(value = "enabledValue") Boolean enabledValue) throws ThingsboardException { |
||||
|
ApiKeyId apiKeyId = new ApiKeyId(id); |
||||
|
ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE); |
||||
|
checkUserId(apiKey.getUserId(), Operation.WRITE); |
||||
|
apiKey.setEnabled(enabledValue); |
||||
|
return apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "Delete API key by ID (deleteApiKey)", |
||||
|
notes = "Deletes the API key. Referencing non-existing ApiKey Id will cause an error." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@DeleteMapping(value = "/apiKey/{id}") |
||||
|
public void deleteApiKey(@PathVariable UUID id) throws ThingsboardException { |
||||
|
ApiKeyId apiKeyId = new ApiKeyId(id); |
||||
|
ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.DELETE); |
||||
|
checkUserId(apiKey.getUserId(), Operation.WRITE); |
||||
|
apiKeyService.deleteApiKey(apiKey.getTenantId(), apiKey, false); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.extractor; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.API_KEY_HEADER_PREFIX; |
||||
|
|
||||
|
@Component(value = "apiKeyHeaderTokenExtractor") |
||||
|
public class ApiKeyHeaderTokenExtractor extends AbstractHeaderTokenExtractor { |
||||
|
|
||||
|
public ApiKeyHeaderTokenExtractor() { |
||||
|
super(API_KEY_HEADER_PREFIX); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.extractor; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.BEARER_HEADER_PREFIX; |
||||
|
|
||||
|
@Component(value = "jwtHeaderTokenExtractor") |
||||
|
public class JwtHeaderTokenExtractor extends AbstractHeaderTokenExtractor { |
||||
|
|
||||
|
public JwtHeaderTokenExtractor() { |
||||
|
super(BEARER_HEADER_PREFIX); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.pat; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.security.authentication.BadCredentialsException; |
||||
|
import org.springframework.security.authentication.CredentialsExpiredException; |
||||
|
import org.springframework.security.authentication.DisabledException; |
||||
|
import org.springframework.security.authentication.InsufficientAuthenticationException; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.security.core.AuthenticationException; |
||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.StringUtils; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.UserAuthDetails; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKey; |
||||
|
import org.thingsboard.server.dao.pat.ApiKeyService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.security.model.UserPrincipal; |
||||
|
import org.thingsboard.server.service.security.model.token.RawApiKey; |
||||
|
import org.thingsboard.server.service.user.cache.UserAuthDetailsCache; |
||||
|
|
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class ApiKeyAuthenticationProvider implements org.springframework.security.authentication.AuthenticationProvider { |
||||
|
|
||||
|
private final ApiKeyService apiKeyService; |
||||
|
private final UserAuthDetailsCache userAuthDetailsCache; |
||||
|
|
||||
|
@Override |
||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
||||
|
RawApiKey rawApiKey = (RawApiKey) authentication.getCredentials(); |
||||
|
SecurityUser securityUser = authenticate(rawApiKey.apiKey()); |
||||
|
return new ApiKeyAuthenticationToken(securityUser); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean supports(Class<?> authentication) { |
||||
|
return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication); |
||||
|
} |
||||
|
|
||||
|
private SecurityUser authenticate(String key) { |
||||
|
if (StringUtils.isEmpty(key)) { |
||||
|
throw new BadCredentialsException("Empty API key"); |
||||
|
} |
||||
|
ApiKey apiKey = apiKeyService.findApiKeyByValue(key); |
||||
|
if (apiKey == null) { |
||||
|
throw new BadCredentialsException("User not found for the provided API key"); |
||||
|
} |
||||
|
if (!apiKey.isEnabled()) { |
||||
|
throw new DisabledException("API key auth is not active"); |
||||
|
} |
||||
|
if (apiKey.getExpirationTime() != 0 && apiKey.getExpirationTime() < System.currentTimeMillis()) { |
||||
|
throw new CredentialsExpiredException("API key is expired"); |
||||
|
} |
||||
|
UserAuthDetails userAuthDetails = userAuthDetailsCache.getUserAuthDetails(apiKey.getTenantId(), apiKey.getUserId()); |
||||
|
if (userAuthDetails == null) { |
||||
|
throw new UsernameNotFoundException("User with credentials not found"); |
||||
|
} |
||||
|
if (!userAuthDetails.credentialsEnabled()) { |
||||
|
throw new DisabledException("User is not active"); |
||||
|
} |
||||
|
|
||||
|
User user = userAuthDetails.user(); |
||||
|
if (user.getAuthority() == null) { |
||||
|
throw new InsufficientAuthenticationException("User has no authority assigned"); |
||||
|
} |
||||
|
UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); |
||||
|
return new SecurityUser(user, true, userPrincipal); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.pat; |
||||
|
|
||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.security.model.token.RawApiKey; |
||||
|
|
||||
|
import java.io.Serial; |
||||
|
|
||||
|
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { |
||||
|
|
||||
|
@Serial |
||||
|
private static final long serialVersionUID = 2978710889397403536L; |
||||
|
|
||||
|
private RawApiKey rawApiKey; |
||||
|
private SecurityUser securityUser; |
||||
|
|
||||
|
public ApiKeyAuthenticationToken(RawApiKey rawApiKey) { |
||||
|
super(null); |
||||
|
this.rawApiKey = rawApiKey; |
||||
|
setAuthenticated(false); |
||||
|
} |
||||
|
|
||||
|
public ApiKeyAuthenticationToken(SecurityUser securityUser) { |
||||
|
super(securityUser.getAuthorities()); |
||||
|
this.eraseCredentials(); |
||||
|
this.securityUser = securityUser; |
||||
|
super.setAuthenticated(true); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object getCredentials() { |
||||
|
return rawApiKey; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object getPrincipal() { |
||||
|
return this.securityUser; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void eraseCredentials() { |
||||
|
super.eraseCredentials(); |
||||
|
this.rawApiKey = null; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.pat; |
||||
|
|
||||
|
import jakarta.servlet.FilterChain; |
||||
|
import jakarta.servlet.ServletException; |
||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.security.core.AuthenticationException; |
||||
|
import org.springframework.security.core.context.SecurityContext; |
||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; |
||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler; |
||||
|
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
|
import org.thingsboard.server.service.security.auth.extractor.TokenExtractor; |
||||
|
import org.thingsboard.server.service.security.model.token.RawApiKey; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
|
||||
|
import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.API_KEY_HEADER_PREFIX; |
||||
|
import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.AUTHORIZATION_HEADER; |
||||
|
import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.AUTHORIZATION_HEADER_V2; |
||||
|
|
||||
|
public class ApiKeyTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { |
||||
|
|
||||
|
private final AuthenticationFailureHandler failureHandler; |
||||
|
private final TokenExtractor tokenExtractor; |
||||
|
|
||||
|
@Autowired |
||||
|
public ApiKeyTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, |
||||
|
@Qualifier("apiKeyHeaderTokenExtractor") TokenExtractor tokenExtractor, RequestMatcher matcher) { |
||||
|
super(matcher); |
||||
|
this.failureHandler = failureHandler; |
||||
|
this.tokenExtractor = tokenExtractor; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { |
||||
|
RawApiKey rawApiKey = new RawApiKey(tokenExtractor.extract(request)); |
||||
|
return getAuthenticationManager().authenticate(new ApiKeyAuthenticationToken(rawApiKey)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, |
||||
|
Authentication authResult) throws IOException, ServletException { |
||||
|
SecurityContext context = SecurityContextHolder.createEmptyContext(); |
||||
|
context.setAuthentication(authResult); |
||||
|
SecurityContextHolder.setContext(context); |
||||
|
chain.doFilter(request, response); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { |
||||
|
if (!super.requiresAuthentication(request, response)) { |
||||
|
return false; |
||||
|
} |
||||
|
String header = request.getHeader(AUTHORIZATION_HEADER); |
||||
|
if (header == null) { |
||||
|
header = request.getHeader(AUTHORIZATION_HEADER_V2); |
||||
|
} |
||||
|
return header != null && header.startsWith(API_KEY_HEADER_PREFIX); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, |
||||
|
AuthenticationException failed) throws IOException, ServletException { |
||||
|
SecurityContextHolder.clearContext(); |
||||
|
failureHandler.onAuthenticationFailure(request, response, failed); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.model.token; |
||||
|
|
||||
|
public record RawApiKey(String apiKey) {} |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.ttl; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.pat.ApiKeyDao; |
||||
|
import org.thingsboard.server.queue.discovery.PartitionService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@ConditionalOnExpression("${sql.ttl.api_keys.enabled:true} && ${sql.ttl.api_keys.ttl:0} > 0") |
||||
|
public class ApiKeysCleanUpService extends AbstractCleanUpService { |
||||
|
|
||||
|
public static final String RANDOM_DELAY_INTERVAL_MS_EXPRESSION = |
||||
|
"#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.api_keys.checking_interval_ms})}"; |
||||
|
|
||||
|
private final ApiKeyDao apiKeyDao; |
||||
|
|
||||
|
public ApiKeysCleanUpService(PartitionService partitionService, ApiKeyDao apiKeyDao) { |
||||
|
super(partitionService); |
||||
|
this.apiKeyDao = apiKeyDao; |
||||
|
} |
||||
|
|
||||
|
@Scheduled( |
||||
|
initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, |
||||
|
fixedDelayString = "${sql.ttl.api_keys.checking_interval_ms:86400000}" |
||||
|
) |
||||
|
public void cleanUp() { |
||||
|
long threshold = System.currentTimeMillis(); |
||||
|
if (isSystemTenantPartitionMine()) { |
||||
|
int deleted = apiKeyDao.deleteAllByExpirationTimeBefore(threshold); |
||||
|
if (deleted > 0) { |
||||
|
log.info("API key cleanup removed {} keys (thresholdTs={})", deleted, threshold); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.user.cache; |
||||
|
|
||||
|
import com.github.benmanes.caffeine.cache.Cache; |
||||
|
import com.github.benmanes.caffeine.cache.Caffeine; |
||||
|
import jakarta.annotation.PostConstruct; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.event.EventListener; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.UserAuthDetails; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class DefaultUserAuthDetailsCache implements UserAuthDetailsCache { |
||||
|
|
||||
|
private final UserService userService; |
||||
|
|
||||
|
@Value("${cache.userAuthDetails.maxSize:1000}") |
||||
|
private int cacheMaxSize; |
||||
|
@Value("${cache.userAuthDetails.timeToLiveInMinutes:30}") |
||||
|
private int cacheValueTtl; |
||||
|
private Cache<UserId, UserAuthDetails> cache; |
||||
|
|
||||
|
@PostConstruct |
||||
|
private void init() { |
||||
|
cache = Caffeine.newBuilder() |
||||
|
.maximumSize(cacheMaxSize) |
||||
|
.expireAfterAccess(cacheValueTtl, TimeUnit.MINUTES) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
@EventListener(ComponentLifecycleMsg.class) |
||||
|
public void onComponentLifecycleEvent(ComponentLifecycleMsg event) { |
||||
|
if (event.getEntityId() != null) { |
||||
|
if (event.getEntityId().getEntityType() == EntityType.USER) { |
||||
|
evict(new UserId(event.getEntityId().getId())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public UserAuthDetails getUserAuthDetails(TenantId tenantId, UserId userId) { |
||||
|
log.trace("Retrieving user with enabled credentials status for id {} for tenant {} from cache", userId, tenantId); |
||||
|
return cache.get(userId, id -> userService.findUserAuthDetailsByUserId(tenantId, id)); |
||||
|
} |
||||
|
|
||||
|
public void evict(UserId userId) { |
||||
|
cache.invalidate(userId); |
||||
|
log.trace("Evicted record for user {} from cache", userId); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.user.cache; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.UserAuthDetails; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
|
||||
|
public interface UserAuthDetailsCache { |
||||
|
|
||||
|
UserAuthDetails getUserAuthDetails(TenantId tenantId, UserId userId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,144 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.controller; |
||||
|
|
||||
|
import com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKey; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKeyInfo; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class ApiKeyControllerTest extends AbstractControllerTest { |
||||
|
|
||||
|
@Before |
||||
|
public void setUp() throws Exception { |
||||
|
loginTenantAdmin(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testSaveApiKey() throws Exception { |
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo("New API key description", true); |
||||
|
|
||||
|
doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
|
||||
|
PageData<ApiKeyInfo> pageData = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
|
||||
|
ApiKeyInfo savedApiKey = pageData.getData().get(0); |
||||
|
Assert.assertNotNull(savedApiKey); |
||||
|
Assert.assertEquals(apiKeyInfo.getDescription(), savedApiKey.getDescription()); |
||||
|
Assert.assertEquals(apiKeyInfo.isEnabled(), savedApiKey.isEnabled()); |
||||
|
Assert.assertEquals(tenantId, savedApiKey.getTenantId()); |
||||
|
Assert.assertEquals(tenantAdminUser.getId(), savedApiKey.getUserId()); |
||||
|
|
||||
|
doDelete("/api/apiKey/" + savedApiKey.getId()).andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void tesFindUserApiKeys() throws Exception { |
||||
|
PageData<ApiKeyInfo> pageData = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertTrue(pageData.getData().isEmpty()); |
||||
|
|
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo("Test API key description", true); |
||||
|
int expectedSize = 10; |
||||
|
for (int i = 0; i < expectedSize; i++) { |
||||
|
doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
} |
||||
|
|
||||
|
PageData<ApiKeyInfo> pageData2 = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertEquals(expectedSize, pageData2.getData().size()); |
||||
|
|
||||
|
pageData2.getData().forEach(apiKey -> { |
||||
|
try { |
||||
|
doDelete("/api/apiKey/" + apiKey.getId()).andExpect(status().isOk()); |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testUpdateApiKeyDescription() throws Exception { |
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo("Test API key description", true); |
||||
|
doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
|
||||
|
PageData<ApiKeyInfo> pageData = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
|
||||
|
ApiKeyInfo savedApiKey = pageData.getData().get(0); |
||||
|
|
||||
|
String newDescription = "Updated API Key Description"; |
||||
|
|
||||
|
ApiKeyInfo updatedApiKeyInfo = doPut("/api/apiKey/" + savedApiKey.getId().getId() + "/description", newDescription, ApiKeyInfo.class); |
||||
|
Assert.assertNotNull(updatedApiKeyInfo); |
||||
|
Assert.assertEquals(newDescription, updatedApiKeyInfo.getDescription()); |
||||
|
|
||||
|
doDelete("/api/apiKey/" + savedApiKey.getId()).andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEnableApiKey() throws Exception { |
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo("Test API key description", true); |
||||
|
doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
|
||||
|
PageData<ApiKeyInfo> pageData = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
|
||||
|
ApiKeyInfo savedApiKey = pageData.getData().get(0); |
||||
|
|
||||
|
ApiKeyInfo disabledApiKeyInfo = doPut("/api/apiKey/" + savedApiKey.getId().getId() + "/enabled/false", Boolean.FALSE, ApiKeyInfo.class); |
||||
|
Assert.assertNotNull(disabledApiKeyInfo); |
||||
|
Assert.assertFalse(disabledApiKeyInfo.isEnabled()); |
||||
|
|
||||
|
ApiKeyInfo enabledApiKeyInfo = doPut("/api/apiKey/" + savedApiKey.getId().getId() + "/enabled/true", Boolean.TRUE, ApiKeyInfo.class); |
||||
|
Assert.assertNotNull(enabledApiKeyInfo); |
||||
|
Assert.assertTrue(enabledApiKeyInfo.isEnabled()); |
||||
|
|
||||
|
doDelete("/api/apiKey/" + savedApiKey.getId()).andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testDeleteApiKey() throws Exception { |
||||
|
doDelete("/api/apiKey/" + UUID.randomUUID()).andExpect(status().isNotFound()); |
||||
|
|
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo("Test API key description", false); |
||||
|
doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
|
||||
|
PageData<ApiKeyInfo> pageData = doGetTypedWithPageLink("/api/apiKeys/" + tenantAdminUserId + "?", new TypeReference<>() {}, new PageLink(10, 0)); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
ApiKeyInfo savedApiKey = pageData.getData().get(0); |
||||
|
|
||||
|
doDelete("/api/apiKey/" + savedApiKey.getId().getId()).andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
private ApiKeyInfo constructApiKeyInfo(String description, boolean enabled) { |
||||
|
ApiKeyInfo apiKeyInfo = new ApiKeyInfo(); |
||||
|
apiKeyInfo.setDescription(description); |
||||
|
apiKeyInfo.setEnabled(enabled); |
||||
|
apiKeyInfo.setUserId(tenantAdminUserId); |
||||
|
return apiKeyInfo; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,112 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.security.auth.pat; |
||||
|
|
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.mockito.Mockito; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.edge.Edge; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKey; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKeyInfo; |
||||
|
import org.thingsboard.server.controller.AbstractControllerTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class ApiKeyAuthenticationProviderTest extends AbstractControllerTest { |
||||
|
|
||||
|
ApiKey savedApiKey; |
||||
|
|
||||
|
@Before |
||||
|
public void setUp() throws Exception { |
||||
|
loginTenantAdmin(); |
||||
|
|
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo(); |
||||
|
savedApiKey = doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
setApiKey(savedApiKey.getValue()); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void cleanUp() throws Exception { |
||||
|
resetApiKey(); |
||||
|
doDelete("/api/apiKey/" + savedApiKey.getId()).andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testSaveEdgeWithApiKey() throws Exception { |
||||
|
Edge edge = constructEdge("My edge", "default"); |
||||
|
|
||||
|
Mockito.reset(tbClusterService, auditLogService); |
||||
|
|
||||
|
Edge savedEdge = doPostWithApiKey("/api/edge", edge, Edge.class); |
||||
|
|
||||
|
Assert.assertNotNull(savedEdge); |
||||
|
Assert.assertNotNull(savedEdge.getId()); |
||||
|
Assert.assertTrue(savedEdge.getCreatedTime() > 0); |
||||
|
Assert.assertEquals(tenantId, savedEdge.getTenantId()); |
||||
|
Assert.assertNotNull(savedEdge.getCustomerId()); |
||||
|
Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId()); |
||||
|
Assert.assertEquals(edge.getName(), savedEdge.getName()); |
||||
|
|
||||
|
testNotifyEdgeStateChangeEventManyTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), |
||||
|
tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), |
||||
|
ActionType.ADDED, 2); |
||||
|
|
||||
|
savedEdge.setName("My new edge"); |
||||
|
doPostWithApiKey("/api/edge", savedEdge, Edge.class); |
||||
|
|
||||
|
Edge foundEdge = doGetWithApiKey("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); |
||||
|
Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); |
||||
|
|
||||
|
testNotifyEdgeStateChangeEventManyTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(), |
||||
|
tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), |
||||
|
ActionType.UPDATED, 1); |
||||
|
|
||||
|
doDeleteWithApiKey("/api/edge/" + savedEdge.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testUnauthorizedWhenKeyDisabled() throws Exception { |
||||
|
ApiKeyInfo disabledApiKeyInfo = doPut("/api/apiKey/" + savedApiKey.getId().getId() + "/enabled/false", Boolean.FALSE, ApiKeyInfo.class); |
||||
|
Assert.assertFalse(disabledApiKeyInfo.isEnabled()); |
||||
|
doGetWithApiKey("/api/admin/featuresInfo").andExpect(status().isUnauthorized()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testUnauthorizedWhenKeyExpired() throws Exception { |
||||
|
ApiKeyInfo apiKeyInfo = constructApiKeyInfo(); |
||||
|
apiKeyInfo.setExpirationTime(System.currentTimeMillis() - 1000); |
||||
|
ApiKey savedApiKeyWithBad = doPost("/api/apiKey", apiKeyInfo, ApiKey.class); |
||||
|
setApiKey(savedApiKeyWithBad.getValue()); |
||||
|
doPost("/api/apiKey", savedApiKey, ApiKeyInfo.class); |
||||
|
doGetWithApiKey("/api/admin/featuresInfo").andExpect(status().isUnauthorized()); |
||||
|
} |
||||
|
|
||||
|
private ApiKeyInfo constructApiKeyInfo() { |
||||
|
ApiKeyInfo apiKeyInfo = new ApiKeyInfo(); |
||||
|
apiKeyInfo.setDescription("New API key description"); |
||||
|
apiKeyInfo.setEnabled(true); |
||||
|
apiKeyInfo.setUserId(tenantAdminUserId); |
||||
|
return apiKeyInfo; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.dao.pat; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.ApiKeyId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKey; |
||||
|
import org.thingsboard.server.common.data.pat.ApiKeyInfo; |
||||
|
import org.thingsboard.server.dao.entity.EntityDaoService; |
||||
|
|
||||
|
public interface ApiKeyService extends EntityDaoService { |
||||
|
|
||||
|
ApiKey saveApiKey(TenantId tenantId, ApiKeyInfo apiKey); |
||||
|
|
||||
|
void deleteApiKey(TenantId tenantId, ApiKey apiKey, boolean force); |
||||
|
|
||||
|
void deleteByUserId(TenantId tenantId, UserId userId); |
||||
|
|
||||
|
ApiKey findApiKeyByValue(String value); |
||||
|
|
||||
|
ApiKey findApiKeyById(TenantId tenantId, ApiKeyId apiKeyId); |
||||
|
|
||||
|
PageData<ApiKeyInfo> findApiKeysByUserId(TenantId tenantId, UserId userId, PageLink pageLink); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data; |
||||
|
|
||||
|
public record UserAuthDetails(User user, boolean credentialsEnabled) {} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.alarm; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
|
||||
|
public enum AlarmCommentSubType { |
||||
|
|
||||
|
ACKED_BY_USER("Alarm was acknowledged by user %s"), |
||||
|
CLEARED_BY_USER("Alarm was cleared by user %s"), |
||||
|
ASSIGNED_TO_USER("Alarm was assigned by user %s to user %s"), |
||||
|
UNASSIGNED_BY_USER("Alarm was unassigned by user %s"), |
||||
|
UNASSIGNED_FROM_DELETED_USER("Alarm was unassigned because user %s - was deleted"), |
||||
|
COMMENT_DELETED("User %s deleted his comment"), |
||||
|
SEVERITY_CHANGED("Alarm severity was updated from %s to %s"); |
||||
|
|
||||
|
@Getter |
||||
|
private final String text; |
||||
|
|
||||
|
AlarmCommentSubType(String text) { |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class AttributesImmediateOutputStrategy implements AttributesOutputStrategy { |
||||
|
|
||||
|
private boolean updateAttributesOnlyOnValueChange; |
||||
|
|
||||
|
private boolean saveAttribute; |
||||
|
private boolean sendWsUpdate; |
||||
|
private boolean processCfs; |
||||
|
|
||||
|
@Override |
||||
|
public OutputStrategyType getType() { |
||||
|
return OutputStrategyType.IMMEDIATE; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.AttributeScope; |
||||
|
|
||||
|
@Data |
||||
|
public class AttributesOutput implements Output { |
||||
|
|
||||
|
private String name; |
||||
|
private AttributeScope scope; |
||||
|
private Integer decimalsByDefault; |
||||
|
|
||||
|
private AttributesOutputStrategy strategy; |
||||
|
|
||||
|
public AttributesOutput() { |
||||
|
this.strategy = new AttributesRuleChainOutputStrategy(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public OutputType getType() { |
||||
|
return OutputType.ATTRIBUTES; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
|
||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||
|
@JsonTypeInfo( |
||||
|
use = JsonTypeInfo.Id.NAME, |
||||
|
include = JsonTypeInfo.As.PROPERTY, |
||||
|
property = "type" |
||||
|
) |
||||
|
@JsonSubTypes({ |
||||
|
@JsonSubTypes.Type(value = AttributesImmediateOutputStrategy.class, name = "IMMEDIATE"), |
||||
|
@JsonSubTypes.Type(value = AttributesRuleChainOutputStrategy.class, name = "RULE_CHAIN"), |
||||
|
}) |
||||
|
public interface AttributesOutputStrategy extends OutputStrategy { |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class AttributesRuleChainOutputStrategy implements AttributesOutputStrategy { |
||||
|
|
||||
|
@Override |
||||
|
public OutputStrategyType getType() { |
||||
|
return OutputStrategyType.RULE_CHAIN; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
|
||||
|
public interface OutputStrategy { |
||||
|
|
||||
|
@JsonIgnore |
||||
|
OutputStrategyType getType(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
public enum OutputStrategyType { |
||||
|
|
||||
|
IMMEDIATE, RULE_CHAIN |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class TimeSeriesImmediateOutputStrategy implements TimeSeriesOutputStrategy { |
||||
|
|
||||
|
private long ttl; |
||||
|
|
||||
|
private boolean saveTimeSeries; |
||||
|
private boolean saveLatest; |
||||
|
private boolean sendWsUpdate; |
||||
|
private boolean processCfs; |
||||
|
|
||||
|
@Override |
||||
|
public OutputStrategyType getType() { |
||||
|
return OutputStrategyType.IMMEDIATE; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class TimeSeriesOutput implements Output { |
||||
|
|
||||
|
private String name; |
||||
|
private Integer decimalsByDefault; |
||||
|
|
||||
|
private TimeSeriesOutputStrategy strategy; |
||||
|
|
||||
|
public TimeSeriesOutput() { |
||||
|
this.strategy = new TimeSeriesRuleChainOutputStrategy(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public OutputType getType() { |
||||
|
return OutputType.TIME_SERIES; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
|
||||
|
@JsonTypeInfo( |
||||
|
use = JsonTypeInfo.Id.NAME, |
||||
|
include = JsonTypeInfo.As.PROPERTY, |
||||
|
property = "type" |
||||
|
) |
||||
|
@JsonSubTypes({ |
||||
|
@JsonSubTypes.Type(value = TimeSeriesImmediateOutputStrategy.class, name = "IMMEDIATE"), |
||||
|
@JsonSubTypes.Type(value = TimeSeriesRuleChainOutputStrategy.class, name = "RULE_CHAIN") |
||||
|
}) |
||||
|
public interface TimeSeriesOutputStrategy extends OutputStrategy { |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.cf.configuration; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class TimeSeriesRuleChainOutputStrategy implements TimeSeriesOutputStrategy { |
||||
|
|
||||
|
@Override |
||||
|
public OutputStrategyType getType() { |
||||
|
return OutputStrategyType.RULE_CHAIN; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.id; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
|
||||
|
import java.io.Serial; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public class ApiKeyId extends UUIDBased implements EntityId { |
||||
|
|
||||
|
@Serial |
||||
|
private static final long serialVersionUID = -273913539653684641L; |
||||
|
|
||||
|
@JsonCreator |
||||
|
public ApiKeyId(@JsonProperty("id") UUID id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public static ApiKeyId fromString(String secretId) { |
||||
|
return new ApiKeyId(UUID.fromString(secretId)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "API_KEY", allowableValues = "API_KEY") |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.API_KEY; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.pat; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.thingsboard.server.common.data.id.ApiKeyId; |
||||
|
import org.thingsboard.server.common.data.validation.NoXss; |
||||
|
|
||||
|
import java.io.Serial; |
||||
|
|
||||
|
@Schema |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class ApiKey extends ApiKeyInfo { |
||||
|
|
||||
|
@Serial |
||||
|
private static final long serialVersionUID = -2313196723950490263L; |
||||
|
|
||||
|
@NoXss |
||||
|
@Schema(description = "Api key value", requiredMode = Schema.RequiredMode.REQUIRED) |
||||
|
private String value; |
||||
|
|
||||
|
public ApiKey() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public ApiKey(ApiKeyId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public ApiKey(ApiKey apiKey) { |
||||
|
super(apiKey); |
||||
|
this.value = apiKey.getValue(); |
||||
|
} |
||||
|
|
||||
|
public ApiKey(ApiKeyInfo apiKeyInfo) { |
||||
|
super(apiKeyInfo); |
||||
|
this.value = null; |
||||
|
} |
||||
|
|
||||
|
public ApiKey(ApiKeyInfo apiKeyInfo, String value) { |
||||
|
super(apiKeyInfo); |
||||
|
this.value = value; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.common.data.pat; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.thingsboard.server.common.data.BaseData; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.id.ApiKeyId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.validation.Length; |
||||
|
import org.thingsboard.server.common.data.validation.NoXss; |
||||
|
|
||||
|
import java.io.Serial; |
||||
|
|
||||
|
@Schema |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class ApiKeyInfo extends BaseData<ApiKeyId> implements HasTenantId { |
||||
|
|
||||
|
@Serial |
||||
|
private static final long serialVersionUID = -2313196723950490263L; |
||||
|
|
||||
|
@Schema(description = "JSON object with Tenant Id. Tenant Id of the api key cannot be changed.", accessMode = Schema.AccessMode.READ_ONLY) |
||||
|
private TenantId tenantId; |
||||
|
|
||||
|
@Schema(description = "JSON object with User Id. User Id of the api key cannot be changed.") |
||||
|
private UserId userId; |
||||
|
|
||||
|
@Schema(description = "Expiration time of the api key.") |
||||
|
private long expirationTime; |
||||
|
|
||||
|
@NoXss |
||||
|
@NotBlank |
||||
|
@Length(fieldName = "description") |
||||
|
@Schema(description = "Api Key description.", example = "Api Key description") |
||||
|
private String description; |
||||
|
|
||||
|
@Schema(description = "Enabled/disabled api key.", example = "true") |
||||
|
private boolean enabled; |
||||
|
|
||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY) |
||||
|
@Schema(description = "Indicates if the api key is expired based on current time. Returns false if expirationTime is 0 (no expiry).", |
||||
|
example = "false", |
||||
|
accessMode = Schema.AccessMode.READ_ONLY) |
||||
|
public boolean isExpired() { |
||||
|
if (expirationTime == 0) { |
||||
|
return false; |
||||
|
} |
||||
|
return System.currentTimeMillis() > expirationTime; |
||||
|
} |
||||
|
|
||||
|
@Schema(description = "JSON object with the Api Key Id. " + |
||||
|
"Specify this field to update the Api Key. " + |
||||
|
"Referencing non-existing Api Key Id will cause error. " + |
||||
|
"Omit this field to create new Api Key.") |
||||
|
@Override |
||||
|
public ApiKeyId getId() { |
||||
|
return super.getId(); |
||||
|
} |
||||
|
|
||||
|
public ApiKeyInfo() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public ApiKeyInfo(ApiKeyId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public ApiKeyInfo(ApiKeyInfo apiKeyInfo) { |
||||
|
super(apiKeyInfo); |
||||
|
this.tenantId = apiKeyInfo.getTenantId(); |
||||
|
this.userId = apiKeyInfo.getUserId(); |
||||
|
this.expirationTime = apiKeyInfo.getExpirationTime(); |
||||
|
this.enabled = apiKeyInfo.isEnabled(); |
||||
|
this.description = apiKeyInfo.getDescription(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue