From 6ea7af0918a82e81c77653f0c15259714a4e1715 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 27 Feb 2026 15:24:15 +0200 Subject: [PATCH] Improved Apple OAuth2 mapper and refactored OAuth2 client validation - Use ID token claims as the source of truth for Apple OAuth2 attributes - Added Apple mapper type to OAuth2 client data validation - Consolidated duplicated validation logic for BASIC, GITHUB, and APPLE mapper types Co-Authored-By: Claude Opus 4.6 --- .../auth/oauth2/AppleOAuth2ClientMapper.java | 10 ++++--- .../validator/Oauth2ClientDataValidator.java | 27 ++++++------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java index d6238524c4..a2862a5cda 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java @@ -79,9 +79,13 @@ public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen } } if (user.has(EMAIL)) { - JsonNode email = user.get(EMAIL); - if (email != null && email.isTextual()) { - updated.put(EMAIL, email.asText()); + JsonNode emailNode = user.get(EMAIL); + if (emailNode != null && emailNode.isTextual()) { + Object tokenEmail = attributes.get(EMAIL); + if (tokenEmail != null && !emailNode.asText().equals(tokenEmail.toString())) { + log.warn("Apple OAuth2 callback: ignoring email [{}] from user POST parameter " + + "that differs from validated ID token email [{}]", emailNode.asText(), tokenEmail); + } } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java index 5cff024d00..07fbc06114 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java @@ -35,12 +35,17 @@ public class Oauth2ClientDataValidator extends DataValidator { @Override protected void validateDataImpl(TenantId tenantId, OAuth2Client oAuth2Client) { OAuth2MapperConfig mapperConfig = oAuth2Client.getMapperConfig(); - if (mapperConfig.getType() == MapperType.BASIC) { + MapperType type = mapperConfig.getType(); + if (type == MapperType.BASIC || type == MapperType.GITHUB || type == MapperType.APPLE) { OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); if (basicConfig == null) { throw new DataValidationException("Basic config should be specified!"); } - if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + if (type == MapperType.GITHUB) { + if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); + } + } else if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { throw new DataValidationException("Email attribute key should be specified!"); } if (basicConfig.getTenantNameStrategy() == null) { @@ -51,23 +56,7 @@ public class Oauth2ClientDataValidator extends DataValidator { throw new DataValidationException("Tenant name pattern should be specified!"); } } - if (mapperConfig.getType() == MapperType.GITHUB) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } - } - if (mapperConfig.getType() == MapperType.CUSTOM) { + if (type == MapperType.CUSTOM) { OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); if (customConfig == null) { throw new DataValidationException("Custom config should be specified!");