longyan 7 days ago
committed by GitHub
parent
commit
b1f4b0ceca
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 28
      application/src/main/data/json/system/oauth2_config_templates/dingtalk_config.json
  2. 4
      application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
  3. 18
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  4. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  5. 85
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkAuthCodeFilter.java
  6. 50
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2ClientMapper.java
  7. 107
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2UserService.java
  8. 141
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkTokenResponseClient.java
  9. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  11. 2
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java
  12. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java
  13. 237
      ui-ngx/src/app/shared/models/constants.ts
  14. 8
      ui-ngx/src/app/shared/models/icon.models.ts
  15. 7
      ui-ngx/src/app/shared/models/oauth2.models.ts

28
application/src/main/data/json/system/oauth2_config_templates/dingtalk_config.json

@ -0,0 +1,28 @@
{
"providerId": "DingTalk",
"additionalInfo": {
"providerName": "DingTalk"
},
"accessTokenUri": "https://api.dingtalk.com/v1.0/oauth2/userAccessToken",
"authorizationUri": "https://login.dingtalk.com/oauth2/auth",
"scope": [
"corpid"
],
"jwkSetUri": null,
"userInfoUri": "https://api.dingtalk.com/v1.0/contact/users/me",
"clientAuthenticationMethod": "POST",
"userNameAttributeName": "userid",
"mapperConfig": {
"type": "DINGTALK",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "nick",
"lastNameAttributeKey": null,
"tenantNameStrategy": "DOMAIN"
}
},
"comment": null,
"loginButtonIcon": "dingtalk-logo",
"loginButtonLabel": "DingTalk",
"helpLink": "https://open.dingtalk.com/document/isvapp/obtain-user-token"
}

4
application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java

@ -170,6 +170,10 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
addPkceParameters(attributes, additionalParameters);
}
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri();
if (authorizationUri != null && authorizationUri.contains("login.dingtalk.com")) {
additionalParameters.put("prompt", "consent");
}
builder.additionalParameters(additionalParameters);
} else {
throw new IllegalArgumentException("Invalid Authorization Grant Type (" +

18
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -33,6 +33,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@ -53,6 +54,9 @@ import org.thingsboard.server.service.security.auth.jwt.JwtTokenAuthenticationPr
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.SkipPathRequestMatcher;
import org.thingsboard.server.service.security.auth.oauth2.DingTalkAuthCodeFilter;
import org.thingsboard.server.service.security.auth.oauth2.DingTalkOAuth2UserService;
import org.thingsboard.server.service.security.auth.oauth2.DingTalkTokenResponseClient;
import org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import org.thingsboard.server.service.security.auth.pat.ApiKeyAuthenticationProvider;
import org.thingsboard.server.service.security.auth.pat.ApiKeyTokenAuthenticationProcessingFilter;
@ -126,6 +130,15 @@ public class ThingsboardSecurityConfiguration {
@Autowired(required = false)
OAuth2Configuration oauth2Configuration;
@Autowired(required = false)
private DingTalkTokenResponseClient dingTalkTokenResponseClient;
@Autowired(required = false)
private DingTalkOAuth2UserService dingTalkOAuth2UserService;
@Autowired(required = false)
private DingTalkAuthCodeFilter dingTalkAuthCodeFilter;
@Autowired
@Qualifier("jwtHeaderTokenExtractor")
private TokenExtractor jwtHeaderTokenExtractor;
@ -279,12 +292,17 @@ public class ThingsboardSecurityConfiguration {
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authExceptionHandler, buildRestLoginProcessingFilter().getClass());
if (oauth2Configuration != null) {
http.addFilterBefore(dingTalkAuthCodeFilter, OAuth2AuthorizationCodeGrantFilter.class);
http.oauth2Login(login -> login
.authorizationEndpoint(config -> config
.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
.authorizationRequestResolver(oAuth2AuthorizationRequestResolver))
.loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
.tokenEndpoint(config -> config.accessTokenResponseClient(dingTalkTokenResponseClient))
.userInfoEndpoint(config -> {
config.userService(dingTalkOAuth2UserService);
})
.successHandler(oauth2AuthenticationSuccessHandler)
.failureHandler(oauth2AuthenticationFailureHandler));
}

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -124,6 +124,7 @@ public class ThingsboardInstallService {
systemDataLoaderService.loadSystemWidgets();
installScripts.loadSystemLwm2mResources();
installScripts.loadSystemImagesAndResources();
systemDataLoaderService.createOAuth2Templates();
databaseSchemaVersionService.updateSchemaVersion();
}
log.info("Upgrade finished successfully!");

85
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkAuthCodeFilter.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2026 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.oauth2;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@Component
@TbCoreComponent
public class DingTalkAuthCodeFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authCode = request.getParameter("authCode");
if (authCode != null && !authCode.isEmpty() && request.getParameter(OAuth2ParameterNames.CODE) == null) {
HttpServletRequest wrappedRequest = new DingTalkAuthCodeRequestWrapper(request, authCode);
filterChain.doFilter(wrappedRequest, response);
return;
}
filterChain.doFilter(request, response);
}
private static class DingTalkAuthCodeRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> modifiedParams;
DingTalkAuthCodeRequestWrapper(HttpServletRequest request, String authCode) {
super(request);
this.modifiedParams = new HashMap<>(request.getParameterMap());
this.modifiedParams.put(OAuth2ParameterNames.CODE, new String[]{authCode});
}
@Override
public String getParameter(String name) {
if (OAuth2ParameterNames.CODE.equals(name)) {
return modifiedParams.get(OAuth2ParameterNames.CODE)[0];
}
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(modifiedParams);
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(modifiedParams.keySet());
}
@Override
public String[] getParameterValues(String name) {
return modifiedParams.get(name);
}
}
}

50
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2ClientMapper.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2026 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.oauth2;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;
@Service(value = "dingTalkOAuth2ClientMapper")
@Slf4j
@TbCoreComponent
public class DingTalkOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client oAuth2Client) {
Map<String, Object> attributes = token.getPrincipal().getAttributes();
String email = attributes.containsKey("email") ? String.valueOf(attributes.get("email")) : null;
String nick = attributes.containsKey("nick") ? String.valueOf(attributes.get("nick")) : null;
if (email == null || email.isEmpty()) {
throw new UsernameNotFoundException("Please try again after configuring personal email account in DingTalk.");
}
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, oAuth2Client.getMapperConfig());
oauth2User.setFirstName(nick);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, oAuth2Client);
}
}

107
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2UserService.java

@ -0,0 +1,107 @@
/**
* Copyright © 2016-2026 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.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
@TbCoreComponent
public class DingTalkOAuth2UserService extends DefaultOAuth2UserService {
private static final String DINGTALK_USER_INFO_URI = "https://api.dingtalk.com/v1.0/contact/users/me";
private final RestTemplate restTemplate;
public DingTalkOAuth2UserService() {
this.restTemplate = new RestTemplate();
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
String userInfoUri = providerDetails.getUserInfoEndpoint().getUri();
if (!DINGTALK_USER_INFO_URI.equals(userInfoUri)) {
return super.loadUser(userRequest);
}
String accessToken = userRequest.getAccessToken().getTokenValue();
HttpHeaders headers = new HttpHeaders();
headers.set("x-acs-dingtalk-access-token", accessToken);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<?> entity = new HttpEntity<>(headers);
Map<String, Object> responseBody;
try {
responseBody = restTemplate.exchange(userInfoUri, HttpMethod.GET, entity, Map.class).getBody();
} catch (HttpClientErrorException | HttpServerErrorException e) {
log.error("Failed to get user info from DingTalk: status={}, body={}", e.getStatusCode(), e.getResponseBodyAsString());
throw new OAuth2AuthenticationException("Failed to get user info from DingTalk: HTTP " + e.getStatusCode() + " - " + e.getResponseBodyAsString());
} catch (Exception e) {
log.error("Failed to get user info from DingTalk: {}", e.getMessage(), e);
throw new OAuth2AuthenticationException("Failed to get user info from DingTalk: " + e.getMessage());
}
if (responseBody == null) {
throw new OAuth2AuthenticationException("Empty response from DingTalk user info endpoint");
}
Map<String, Object> userAttributes = new HashMap<>(responseBody);
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
if (userAttributes.get(userNameAttributeName) == null) {
if (userAttributes.get("unionId") != null) {
userAttributes.put(userNameAttributeName, userAttributes.get("unionId"));
log.warn("DingTalk response missing '{}', using 'unionId' as fallback", userNameAttributeName);
} else if (userAttributes.get("openContactId") != null) {
userAttributes.put(userNameAttributeName, userAttributes.get("openContactId"));
log.warn("DingTalk response missing '{}', using 'openContactId' as fallback", userNameAttributeName);
} else {
log.error("DingTalk response missing '{}' and no fallback fields available. Response: {}", userNameAttributeName, responseBody);
throw new OAuth2AuthenticationException("DingTalk response does not contain '" + userNameAttributeName + "' or any fallback identifier (unionId, openContactId)");
}
}
return new DefaultOAuth2User(
Collections.emptyList(),
userAttributes,
userNameAttributeName
);
}
}

141
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkTokenResponseClient.java

@ -0,0 +1,141 @@
/**
* Copyright © 2016-2026 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.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
@TbCoreComponent
public class DingTalkTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
private static final String DINGTALK_TOKEN_URI = "https://api.dingtalk.com/v1.0/oauth2/userAccessToken";
private static final long DEFAULT_EXPIRE_SECONDS = 7200;
private final RestTemplate restTemplate;
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> defaultClient;
public DingTalkTokenResponseClient() {
this.restTemplate = new RestTemplate();
this.defaultClient = new DefaultAuthorizationCodeTokenResponseClient();
}
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest grantRequest) {
if (supports(grantRequest.getClientRegistration())) {
return getTokenResponseInternal(grantRequest);
}
return defaultClient.getTokenResponse(grantRequest);
}
private OAuth2AccessTokenResponse getTokenResponseInternal(OAuth2AuthorizationCodeGrantRequest grantRequest) {
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
Map<String, String> body = new HashMap<>();
body.put("clientId", clientRegistration.getClientId());
body.put("clientSecret", clientRegistration.getClientSecret());
body.put("code", grantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
body.put("grantType", "authorization_code");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
Map<String, Object> responseBody;
try {
responseBody = restTemplate.exchange(tokenUri, HttpMethod.POST, entity, Map.class).getBody();
} catch (Exception e) {
log.error("Failed to get access token from DingTalk: {}", e.getMessage(), e);
throw new OAuth2AuthenticationException("Failed to get access token from DingTalk: " + e.getMessage());
}
if (responseBody == null) {
throw new OAuth2AuthenticationException("Empty response from DingTalk token endpoint");
}
if (responseBody.containsKey("code")) {
String responseCode = String.valueOf(responseBody.get("code"));
if (!"0".equals(responseCode)) {
String message = responseBody.containsKey("message") ? String.valueOf(responseBody.get("message")) : "Unknown error";
throw new OAuth2AuthenticationException("DingTalk error [" + responseCode + "]: " + message);
}
}
Object accessToken = responseBody.get("accessToken");
if (accessToken == null) {
throw new OAuth2AuthenticationException("DingTalk token response missing 'accessToken'");
}
String refreshToken = responseBody.containsKey("refreshToken") ? String.valueOf(responseBody.get("refreshToken")) : null;
long expireIn = DEFAULT_EXPIRE_SECONDS;
if (responseBody.containsKey("expireIn")) {
try {
expireIn = Long.parseLong(String.valueOf(responseBody.get("expireIn")));
} catch (NumberFormatException e) {
log.warn("Failed to parse expireIn from DingTalk response, using default: {}", DEFAULT_EXPIRE_SECONDS);
}
}
Map<String, Object> additionalParameters = new HashMap<>();
if (responseBody.containsKey("corpId")) {
additionalParameters.put("corpId", responseBody.get("corpId"));
}
if (!CollectionUtils.isEmpty(additionalParameters)) {
return OAuth2AccessTokenResponse.withToken(String.valueOf(accessToken))
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(expireIn)
.refreshToken(refreshToken)
.scopes(grantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes())
.additionalParameters(additionalParameters)
.build();
}
return OAuth2AccessTokenResponse.withToken(String.valueOf(accessToken))
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(expireIn)
.refreshToken(refreshToken)
.scopes(grantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes())
.build();
}
public boolean supports(ClientRegistration clientRegistration) {
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
return DINGTALK_TOKEN_URI.equals(tokenUri);
}
}

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java

@ -43,6 +43,10 @@ public class OAuth2ClientMapperProvider {
@Qualifier("appleOAuth2ClientMapper")
private OAuth2ClientMapper appleOAuth2ClientMapper;
@Autowired
@Qualifier("dingTalkOAuth2ClientMapper")
private OAuth2ClientMapper dingTalkOAuth2ClientMapper;
public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) {
switch (oauth2MapperType) {
case CUSTOM:
@ -53,6 +57,8 @@ public class OAuth2ClientMapperProvider {
return githubOAuth2ClientMapper;
case APPLE:
return appleOAuth2ClientMapper;
case DINGTALK:
return dingTalkOAuth2ClientMapper;
default:
throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!");
}

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java

@ -67,7 +67,11 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
errorPrefix = "/login?loginError=";
}
httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
String errorMessage = exception.getMessage();
if (errorMessage == null) {
errorMessage = "Unknown error";
}
getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
URLEncoder.encode(errorMessage, StandardCharsets.UTF_8));
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.data.oauth2;
public enum MapperType {
BASIC, CUSTOM, GITHUB, APPLE;
BASIC, CUSTOM, GITHUB, APPLE, DINGTALK;
}

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java

@ -177,7 +177,7 @@ public class OAuth2ClientEntity extends BaseSqlEntity<OAuth2Client> {
.activateUser(activateUser)
.type(type)
.basic(
(type == MapperType.BASIC || type == MapperType.GITHUB || type == MapperType.APPLE) ?
(type == MapperType.BASIC || type == MapperType.GITHUB || type == MapperType.APPLE || type == MapperType.DINGTALK) ?
OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)

237
ui-ngx/src/app/shared/models/constants.ts

@ -97,135 +97,136 @@ export const docPlatformPrefix = '';
/* eslint-disable max-len */
export const HelpLinks = {
linksMap: {
outgoingMailSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/mail-settings/`,
smsProviderSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/sms-provider-settings/`,
slackSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/slack-settings/`,
securitySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/`,
oauth2Settings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/oauth-2-support/`,
outgoingMailSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/mail-settings`,
smsProviderSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/sms-provider-settings`,
slackSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/slack-settings`,
securitySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/security-settings`,
oauth2Settings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/oauth-2-support/`,
oauth2Apple: 'https://developer.apple.com/sign-in-with-apple/get-started/',
oauth2Facebook: 'https://developers.facebook.com/docs/facebook-login/web#logindialog',
oauth2Github: 'https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app',
oauth2Google: 'https://developers.google.com/identity/protocols/oauth2',
ruleEngine: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine/`,
ruleNodeCheckRelation: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/check-relation-presence/`,
ruleNodeCheckExistenceFields: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/check-fields-presence/`,
ruleNodeGpsGeofencingFilter: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/gps-geofencing-filter/`,
ruleNodeJsFilter: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/script/`,
ruleNodeJsSwitch: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/switch/`,
ruleNodeAssetProfileSwitch: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/asset-profile-switch/`,
ruleNodeDeviceProfileSwitch: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/device-profile-switch/`,
ruleNodeCheckAlarmStatus: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/alarm-status-filter/`,
ruleNodeMessageTypeFilter: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/message-type-filter/`,
ruleNodeMessageTypeSwitch: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/message-type-switch/`,
ruleNodeOriginatorTypeFilter: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/entity-type-filter/`,
ruleNodeOriginatorTypeSwitch: `${helpBaseUrl}/docs/reference/rule-engine/nodes/filter/entity-type-switch/`,
ruleNodeOriginatorAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/originator-attributes/`,
ruleNodeOriginatorFields: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/originator-fields/`,
ruleNodeOriginatorTelemetry: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/originator-telemetry/`,
ruleNodeCustomerAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/customer-attributes/`,
ruleNodeCustomerDetails: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/customer-details/`,
ruleNodeFetchDeviceCredentials: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/fetch-device-credentials/`,
ruleNodeDeviceAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/related-device-attributes/`,
ruleNodeRelatedAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/related-entity-data/`,
ruleNodeTenantAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/tenant-attributes/`,
ruleNodeTenantDetails: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/tenant-details/`,
ruleNodeChangeOriginator: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/change-originator/`,
ruleNodeCopyKeyValuePairs: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/copy-key-value-pairs/`,
ruleNodeDeduplication: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/deduplication/`,
ruleNodeDeleteKeyValuePairs: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/delete-key-value-pairs/`,
ruleNodeJsonPath: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/json-path/`,
ruleNodeRenameKeys: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/rename-keys/`,
ruleNodeTransformMsg: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/script/`,
ruleNodeSplitArrayMsg: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/split-array-msg/`,
ruleNodeMsgToEmail: `${helpBaseUrl}/docs/reference/rule-engine/nodes/transformation/to-email/`,
ruleNodeAssignToCustomer: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/assign-to-customer/`,
ruleNodeUnassignFromCustomer: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/unassign-from-customer/`,
ruleNodeCalculatedFields: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/calculated-fields/`,
ruleNodeClearAlarm: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/clear-alarm/`,
ruleNodeCreateAlarm: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/create-alarm/`,
ruleNodeCopyToView: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/copy-to-view/`,
ruleNodeCreateRelation: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/create-relation/`,
ruleNodeDeleteRelation: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/delete-relation/`,
ruleNodeDeviceState: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/device-state/`,
ruleNodeMessageCount: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/message-count/`,
ruleNodeMsgDelay: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/delay/`,
ruleNodeMsgGenerator: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/generator/`,
ruleNodeGpsGeofencingEvents: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/gps-geofencing-events/`,
ruleNodeLog: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/log/`,
ruleNodeRpcCallReply: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/rpc-call-reply/`,
ruleNodeRpcCallRequest: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/rpc-call-request/`,
ruleNodeSaveAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/save-attributes/`,
ruleNodeDeleteAttributes: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/delete-attributes/`,
ruleNodeSaveTimeseries: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/save-timeseries/`,
ruleNodeSaveToCustomTable: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/save-to-custom-table/`,
ruleNodeRuleChain: `${helpBaseUrl}/docs/reference/rule-engine/nodes/flow/rule-chain/`,
ruleNodeOutputNode: `${helpBaseUrl}/docs/reference/rule-engine/nodes/flow/output/`,
ruleNodeAiRequest: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/ai-request/`,
ruleNodeAwsLambda: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/aws-lambda/`,
ruleNodeAwsSns: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/aws-sns/`,
ruleNodeAwsSqs: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/aws-sqs/`,
ruleNodeKafka: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/kafka/`,
ruleNodeMqtt: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/mqtt/`,
ruleNodeAzureIotHub: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/azure-iot-hub/`,
ruleNodeGcpPubSub: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/gcp-pubsub/`,
ruleNodeRabbitMq: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/rabbitmq/`,
ruleNodeRestApiCall: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/rest-api-call/`,
ruleNodeSendEmail: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/send-email/`,
ruleNodeSendSms: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/send-sms/`,
ruleNodeMath: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/math-function/`,
ruleNodeCalculateDelta: `${helpBaseUrl}/docs/reference/rule-engine/nodes/enrichment/calculate-delta/`,
ruleNodeRestCallReply: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/rest-call-reply/`,
ruleNodePushToCloud: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/push-to-cloud/`,
ruleNodePushToEdge: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/push-to-edge/`,
ruleNodeDeviceProfile: `${helpBaseUrl}/docs/reference/rule-engine/nodes/action/device-profile/`,
ruleNodeAcknowledge: `${helpBaseUrl}/docs/reference/rule-engine/nodes/flow/acknowledge/`,
ruleNodeCheckpoint: `${helpBaseUrl}/docs/reference/rule-engine/nodes/flow/checkpoint/`,
ruleNodeSendNotification: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/send-notification/`,
ruleNodeSendSlack: `${helpBaseUrl}/docs/reference/rule-engine/nodes/external/send-to-slack/`,
tenants: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/multi-tenancy/`,
tenantProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/tenant-profiles/`,
customers: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/customers/`,
users: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/users/`,
devices: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/devices/`,
deviceProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/device-profiles/`,
assetProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/asset-profiles/`,
edges: `${helpBaseUrl}/docs/edge/why-thingsboard-edge/`,
assets: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/assets/`,
entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/entity-views/`,
entitiesImport: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/provisioning/#bulk-provisioning`,
rulechains: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine/`,
lwm2mResourceLibrary: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/lwm2m-api/getting-started/`,
jsExtension: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/advanced/`,
dashboards: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/dashboards/`,
otaUpdates: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ota-updates/`,
widgetTypes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/#widget-types`,
widgetsBundles: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/widgets/widget-library/`,
widgetsConfig: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/widgets/widget-library/`,
widgetsConfigTimeseries: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/time-series/`,
widgetsConfigLatest: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/latest-values/`,
widgetsConfigRpc: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/rpc-control/`,
widgetsConfigAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/alarm-widget/`,
widgetsConfigStatic: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/widgets-development/static-widget/`,
queue: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/architecture/queue/`,
oauth2DingTalk: 'https://open.dingtalk.com/document/development/obtain-the-access-token-of-an-internal-app',
ruleEngine: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/overview/`,
ruleNodeCheckRelation: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/check-relation-presence/`,
ruleNodeCheckExistenceFields: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/check-fields-presence/`,
ruleNodeGpsGeofencingFilter: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/gps-geofencing-filter/`,
ruleNodeJsFilter: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/script/`,
ruleNodeJsSwitch: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/switch/`,
ruleNodeAssetProfileSwitch: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/asset-profile-switch/`,
ruleNodeDeviceProfileSwitch: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/device-profile-switch/`,
ruleNodeCheckAlarmStatus: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/alarm-status-filter/`,
ruleNodeMessageTypeFilter: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/message-type-filter/`,
ruleNodeMessageTypeSwitch: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/message-type-switch/`,
ruleNodeOriginatorTypeFilter: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/entity-type-filter/`,
ruleNodeOriginatorTypeSwitch: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/filter/entity-type-switch/`,
ruleNodeOriginatorAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/originator-attributes/`,
ruleNodeOriginatorFields: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/originator-fields/`,
ruleNodeOriginatorTelemetry: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/originator-telemetry/`,
ruleNodeCustomerAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/customer-attributes/`,
ruleNodeCustomerDetails: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/customer-details/`,
ruleNodeFetchDeviceCredentials: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/fetch-device-credentials/`,
ruleNodeDeviceAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/related-device-attributes/`,
ruleNodeRelatedAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/related-entity-data/`,
ruleNodeTenantAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/tenant-attributes/`,
ruleNodeTenantDetails: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/tenant-details/`,
ruleNodeChangeOriginator: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/change-originator/`,
ruleNodeCopyKeyValuePairs: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/copy-key-value-pairs/`,
ruleNodeDeduplication: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/deduplication/`,
ruleNodeDeleteKeyValuePairs: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/delete-key-value-pairs/`,
ruleNodeJsonPath: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/json-path/`,
ruleNodeRenameKeys: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/rename-keys/`,
ruleNodeTransformMsg: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/script/`,
ruleNodeSplitArrayMsg: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/split-array-msg/`,
ruleNodeMsgToEmail: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/transformation/to-email/`,
ruleNodeAssignToCustomer: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/assign-to-customer/`,
ruleNodeUnassignFromCustomer: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/unassign-from-customer/`,
ruleNodeCalculatedFields: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/calculated-fields/`,
ruleNodeClearAlarm: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/clear-alarm/`,
ruleNodeCreateAlarm: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/create-alarm/`,
ruleNodeCopyToView: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/copy-to-view/`,
ruleNodeCreateRelation: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/create-relation/`,
ruleNodeDeleteRelation: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/delete-relation/`,
ruleNodeDeviceState: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/device-state/`,
ruleNodeMessageCount: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/message-count/`,
ruleNodeMsgDelay: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/delay/`,
ruleNodeMsgGenerator: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/generator/`,
ruleNodeGpsGeofencingEvents: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/gps-geofencing-events/`,
ruleNodeLog: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/log/`,
ruleNodeRpcCallReply: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/rpc-call-reply/`,
ruleNodeRpcCallRequest: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/rpc-call-request/`,
ruleNodeSaveAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/save-attributes/`,
ruleNodeDeleteAttributes: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/delete-attributes/`,
ruleNodeSaveTimeseries: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/save-timeseries/`,
ruleNodeSaveToCustomTable: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/save-to-custom-table/`,
ruleNodeRuleChain: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/flow/rule-chain/`,
ruleNodeOutputNode: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/flow/output/`,
ruleNodeAiRequest: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/ai-request/`,
ruleNodeAwsLambda: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/aws-lambda/`,
ruleNodeAwsSns: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/aws-sns/`,
ruleNodeAwsSqs: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/aws-sqs/`,
ruleNodeKafka: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/kafka/`,
ruleNodeMqtt: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/mqtt/`,
ruleNodeAzureIotHub: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/azure-iot-hub/`,
ruleNodeGcpPubSub: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/gcp-pubsub/`,
ruleNodeRabbitMq: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/rabbitmq/`,
ruleNodeRestApiCall: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/rest-api-call/`,
ruleNodeSendEmail: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/send-email/`,
ruleNodeSendSms: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/send-sms/`,
ruleNodeMath: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/math-function/`,
ruleNodeCalculateDelta: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/enrichment/calculate-delta/`,
ruleNodeRestCallReply: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/rest-call-reply/`,
ruleNodePushToCloud: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/push-to-cloud/`,
ruleNodePushToEdge: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/push-to-edge/`,
ruleNodeDeviceProfile: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/action/device-profile/`,
ruleNodeAcknowledge: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/flow/acknowledge/`,
ruleNodeCheckpoint: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/flow/checkpoint/`,
ruleNodeSendNotification: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/send-notification/`,
ruleNodeSendSlack: `${helpBaseUrl}/docs/user-guide/rule-engine-2-0/nodes/external/send-to-slack/`,
tenants: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/tenants`,
tenantProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/tenant-profiles`,
customers: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/customers`,
users: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/users`,
devices: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/devices`,
deviceProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/device-profiles`,
assetProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/asset-profiles`,
edges: `${helpBaseUrl}/docs/edge/getting-started-guides/what-is-edge`,
assets: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/assets`,
entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/entity-views`,
entitiesImport: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/bulk-provisioning`,
rulechains: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/rule-chains`,
lwm2mResourceLibrary: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/lwm2m-api`,
jsExtension: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/contribution/ui/advanced-development`,
dashboards: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/dashboards`,
otaUpdates: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ota-updates`,
widgetTypes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library/#widget-types`,
widgetsBundles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library/#widgets-library-bundles`,
widgetsConfig: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library`,
widgetsConfigTimeseries: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library#time-series`,
widgetsConfigLatest: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library#latest-values`,
widgetsConfigRpc: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library#control-widget`,
widgetsConfigAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library#alarm-widget`,
widgetsConfigStatic: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/widget-library#static`,
queue: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/queue`,
repositorySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/version-control/#git-settings-configuration`,
autoCommitSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/version-control/#auto-commit`,
twoFactorAuthentication: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/two-factor-authentication/`,
twoFactorAuthentication: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/two-factor-authentication`,
sentNotification: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/notifications/#send-notification`,
templateNotifications: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/notifications/#templates`,
recipientNotifications: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/notifications/#recipients`,
ruleNotifications: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/notifications/#rules`,
jwtSecuritySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/#jwt-security-settings`,
gatewayInstall: `${helpBaseUrl}/docs/iot-gateway/installation/docker-installation/`,
scada: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada/`,
scadaSymbolDev: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/`,
scadaSymbolDevAnimation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#scadasymbolanimation`,
scadaSymbolDevConnectorAnimation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada-symbol-dev/#connectorscadasymbolanimation`,
domains: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/domains/`,
mobileApplication: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/mobile-app-center/applications/`,
mobileBundle: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/mobile-app-center/`,
mobileQrCode: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/mobile-app-center/qr-code-widget/`,
jwtSecuritySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/jwt-security-settings`,
gatewayInstall: `${helpBaseUrl}/docs/iot-gateway/install/docker-installation`,
scada: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada`,
scadaSymbolDev: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada/scada-symbols-dev-guide/`,
scadaSymbolDevAnimation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada/scada-symbols-dev-guide/#scadasymbolanimation`,
scadaSymbolDevConnectorAnimation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/scada/scada-symbols-dev-guide/#connectorscadasymbolanimation`,
domains: `${helpBaseUrl}/docs${docPlatformPrefix}/domains`,
mobileApplication: `${helpBaseUrl}/docs${docPlatformPrefix}/mobile-center/applications/`,
mobileBundle: `${helpBaseUrl}/docs${docPlatformPrefix}/mobile-center/mobile-center/`,
mobileQrCode: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/mobile-qr-code/`,
calculatedField: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/calculated-fields/`,
aiModels: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ai-models/`,
aiModels: `${helpBaseUrl}/docs${docPlatformPrefix}/samples/analytics/ai-models/`,
apiKeys: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/security/api-keys/`,
timewindowSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/dashboards/#time-window`,
trendzSettings: `${helpBaseUrl}/docs/trendz/`,

8
ui-ngx/src/app/shared/models/icon.models.ts

@ -49,6 +49,14 @@ export const svgIcons: {[key: string]: string} = {
'C185.218331,36.9088319 192.607958,18.4081019 190.591988,0 C174.766312,0.636050225 155.629514,10.5457909 ' +
'144.278109,23.8283506 C134.10507,35.5906758 125.195775,54.4170275 127.599657,72.4607932 C145.239231,73.8255433 ' +
'163.259413,63.4970262 174.239142,50.1987249" fill="#000000"></path></svg>',
'dingtalk-logo': '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" t="1692170938485" ' +
'class="icon" viewBox="0 0 1024 1024" version="1.1" p-id="4019" width="128" height="128"><path d="M908.474 381.92c-1.767 7.47 ' +
'-6.128 18.419-12.246 31.624h0.132l-0.7 1.223c-35.695 76.492 128.894 226.512-128.894 226.512s-0.119-0.354-0.486-0.938l-27.235 ' +
'47.485h131.254L619.61 1021.705l56.903-227.129H573.236l35.886-150.19 c-29.033 7.011-63.35 16.654-103.993 29.74 0 0-54.982 32.249-158.382 ' +
'-62.037 0 0-69.736-61.533-29.304-76.909 17.196-6.538 83.49-14.832 135.645-21.894 70.487-9.543 113.848-14.596 113.848-14.596s-217.316 ' +
'3.255-268.87-4.866c-51.557-8.12-116.959-94.275-130.89-170.019 0 0-21.547-41.58 46.334-21.894s348.855 76.632 348.855 76.632 ' +
'-365.423-112.204-389.74-139.578c-24.312-27.374-71.557-149.423-65.41 -224.42 0 0 2.665-18.698 21.804-13.687 0 0 270.149 123.64 ' +
'454.875 191.323 184.724 67.683 345.33 102.108 324.58 189.739z" fill="#007FFF" p-id="4020"/></svg>',
'queues-list': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">' +
'<path fill="#fff" d="M9 4V2H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h5v-2H4V4h5z"/>' +
'<path fill="#fff" d="M7 18V6h2v12H7zM11 6v12h2V6h-2zM15 20v2h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5v2h5v16h-5z"/>' +

7
ui-ngx/src/app/shared/models/oauth2.models.ts

@ -115,7 +115,8 @@ export enum MapperType {
BASIC = 'BASIC',
CUSTOM = 'CUSTOM',
GITHUB = 'GITHUB',
APPLE = 'APPLE'
APPLE = 'APPLE',
DINGTALK = 'DINGTALK'
}
export interface OAuth2BasicMapperConfig {
@ -174,7 +175,8 @@ export enum Provider {
FACEBOOK = 'Facebook',
GOOGLE = 'Google',
GITHUB = 'Github',
APPLE = 'Apple'
APPLE = 'Apple',
DINGTALK = 'DingTalk'
}
const providerHelpLinkMap = new Map<Provider, string>(
@ -184,5 +186,6 @@ const providerHelpLinkMap = new Map<Provider, string>(
[Provider.FACEBOOK, 'oauth2Facebook'],
[Provider.GITHUB, 'oauth2Github'],
[Provider.GOOGLE, 'oauth2Google'],
[Provider.DINGTALK, 'oauth2DingTalk'],
]
)

Loading…
Cancel
Save