From 1e608fcff5d98e9c1bdb2556788dac2df8afa701 Mon Sep 17 00:00:00 2001 From: longyan Date: Mon, 13 Jul 2026 21:25:34 +0800 Subject: [PATCH] feat(oauth2): Add DingTalk OAuth2 provider backend services and mapper registration Add DINGTALK mapper type to MapperType enum, register DingTalkClientMapper in OAuth2ClientMapperProvider, and implement DingTalk-specific OAuth2 components: auth code filter, user service, token response client, client mapper, and configuration XML template. fix(auth): Fix NPE in Oauth2AuthenticationFailureHandler when error message is null --- .../dingtalk_config.json | 28 ++++ .../auth/oauth2/DingTalkAuthCodeFilter.java | 85 +++++++++++ .../oauth2/DingTalkOAuth2ClientMapper.java | 50 +++++++ .../oauth2/DingTalkOAuth2UserService.java | 107 +++++++++++++ .../oauth2/DingTalkTokenResponseClient.java | 141 ++++++++++++++++++ .../oauth2/OAuth2ClientMapperProvider.java | 6 + .../Oauth2AuthenticationFailureHandler.java | 6 +- .../server/common/data/oauth2/MapperType.java | 2 +- .../dao/model/sql/OAuth2ClientEntity.java | 2 +- 9 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 application/src/main/data/json/system/oauth2_config_templates/dingtalk_config.json create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkAuthCodeFilter.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2ClientMapper.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2UserService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkTokenResponseClient.java diff --git a/application/src/main/data/json/system/oauth2_config_templates/dingtalk_config.json b/application/src/main/data/json/system/oauth2_config_templates/dingtalk_config.json new file mode 100644 index 0000000000..adc43fd3b1 --- /dev/null +++ b/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" +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkAuthCodeFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkAuthCodeFilter.java new file mode 100644 index 0000000000..1d36044fe7 --- /dev/null +++ b/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 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 getParameterMap() { + return Collections.unmodifiableMap(modifiedParams); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(modifiedParams.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + return modifiedParams.get(name); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2ClientMapper.java new file mode 100644 index 0000000000..61c0df193a --- /dev/null +++ b/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 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); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2UserService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkOAuth2UserService.java new file mode 100644 index 0000000000..cb7ea14e19 --- /dev/null +++ b/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 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 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 + ); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkTokenResponseClient.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/DingTalkTokenResponseClient.java new file mode 100644 index 0000000000..46b8271777 --- /dev/null +++ b/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 { + + 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 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 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> entity = new HttpEntity<>(body, headers); + + Map 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 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); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java index 8604461390..967e21d25e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java +++ b/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!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 3b9d325c38..9690def78f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/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)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java index fe8971a618..5c11045880 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java +++ b/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; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java index 3db2cbc39a..6cbc828109 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java @@ -177,7 +177,7 @@ public class OAuth2ClientEntity extends BaseSqlEntity { .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)