From e4140ee30ce27ccb9f229e6aceaf7ab7cae96a2f Mon Sep 17 00:00:00 2001 From: Pavindu Lakshan Date: Sat, 19 Sep 2026 21:55:22 +0530 Subject: [PATCH] Restrict updating blocked claims during self registration Validate the claims sent in the self registration request against the SCIM2.Me blocked and extended blocked claim lists configured in identity.xml, and reject the request with a bad request error when a blocked claim is present. Ported from wso2-support/identity-governance#912. Co-Authored-By: Claude Opus 5 (1M context) --- .../pom.xml | 5 ++ .../identity/user/endpoint/Constants.java | 4 ++ .../user/endpoint/impl/MeApiServiceImpl.java | 48 +++++++++++++++++- .../endpoint/impl/MeApiServiceImplTest.java | 49 ++++++++++++++++++- 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/components/org.wso2.carbon.identity.api.user.governance/pom.xml b/components/org.wso2.carbon.identity.api.user.governance/pom.xml index 970c124927..31ed73759c 100644 --- a/components/org.wso2.carbon.identity.api.user.governance/pom.xml +++ b/components/org.wso2.carbon.identity.api.user.governance/pom.xml @@ -187,6 +187,11 @@ + + org.wso2.carbon.identity.governance + org.wso2.carbon.identity.governance + provided + org.wso2.carbon org.wso2.carbon.utils diff --git a/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/Constants.java b/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/Constants.java index 0ce78ed020..423720ca9a 100644 --- a/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/Constants.java +++ b/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/Constants.java @@ -53,4 +53,8 @@ public final class Constants { public static final String ENABLE_DETAILED_API_RESPONSE = "SelfRegistration.API.EnableDetailedResponseBody"; + // SCIM2 Me endpoint blocked claims configuration keys. + public static final String SCIM2_ME_BLOCKED_CLAIMS = "SCIM2.Me.BlockedClaims.BlockedClaim"; + public static final String SCIM2_ME_EXTENDED_BLOCKED_CLAIMS = "SCIM2.Me.ExtendedBlockedClaims.ExtendedBlockedClaim"; + } diff --git a/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImpl.java b/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImpl.java index a249d35b22..934b1ac274 100644 --- a/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImpl.java +++ b/components/org.wso2.carbon.identity.api.user.governance/src/main/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImpl.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.common.model.ResolvedUser; +import org.wso2.carbon.identity.core.util.IdentityConfigParser; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.governance.service.notification.NotificationChannels; import org.wso2.carbon.identity.recovery.IdentityRecoveryClientException; @@ -35,6 +36,7 @@ import org.wso2.carbon.identity.recovery.signup.UserSelfRegistrationManager; import org.wso2.carbon.identity.user.endpoint.Constants; import org.wso2.carbon.identity.user.endpoint.MeApiService; +import org.wso2.carbon.identity.user.endpoint.dto.ClaimDTO; import org.wso2.carbon.identity.user.endpoint.dto.ErrorDTO; import org.wso2.carbon.identity.user.endpoint.dto.MeCodeValidationRequestDTO; import org.wso2.carbon.identity.user.endpoint.dto.MeResendCodeRequestDTO; @@ -49,6 +51,7 @@ import org.wso2.carbon.identity.workflow.mgt.exception.WorkflowException; import org.wso2.carbon.user.core.util.UserCoreUtil; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -99,6 +102,8 @@ public Response mePost(SelfUserRegistrationRequestDTO selfUserRegistrationReques ERROR_CODE_BAD_SELF_REGISTER_REQUEST.getCode()); } + validateIdentityClaimsNotPresent(selfUserRegistrationRequestDTO); + if (StringUtils.isNotBlank(tenantFromContext)) { selfUserRegistrationRequestDTO.getUser().setTenantDomain(tenantFromContext); } @@ -178,6 +183,48 @@ public Response meValidateCodePost(MeCodeValidationRequestDTO meCodeValidationRe return Response.accepted().build(); } + private void validateIdentityClaimsNotPresent(SelfUserRegistrationRequestDTO selfUserRegistrationRequestDTO) { + + if (selfUserRegistrationRequestDTO == null || selfUserRegistrationRequestDTO.getUser() == null || + selfUserRegistrationRequestDTO.getUser().getClaims() == null) { + return; + } + + List blockedClaims = getBlockedClaims(); + if (blockedClaims.isEmpty()) { + return; + } + + for (ClaimDTO claim : selfUserRegistrationRequestDTO.getUser().getClaims()) { + if (claim != null && StringUtils.isNotBlank(claim.getUri()) && + blockedClaims.contains(claim.getUri())) { + Utils.handleBadRequest(String.format("Claim '%s' is not allowed to be updated in " + + "self-registration.", claim.getUri()), + ERROR_CODE_BAD_SELF_REGISTER_REQUEST.getCode()); + } + } + } + + private List getBlockedClaims() { + + List blockedClaims = new ArrayList<>(); + Map config = IdentityConfigParser.getInstance().getConfiguration(); + addClaimsFromConfig(config, Constants.SCIM2_ME_BLOCKED_CLAIMS, blockedClaims); + addClaimsFromConfig(config, Constants.SCIM2_ME_EXTENDED_BLOCKED_CLAIMS, blockedClaims); + return blockedClaims; + } + + @SuppressWarnings("unchecked") + private void addClaimsFromConfig(Map config, String key, List claimsList) { + + Object value = config.get(key); + if (value instanceof List) { + claimsList.addAll((List) value); + } else if (value instanceof String) { + claimsList.add((String) value); + } + } + /** * Build response for a successful user self registration. * @@ -466,4 +513,3 @@ private boolean isWorkflowAssociated(NotificationResponseBean notificationRespon return false; } } - diff --git a/components/org.wso2.carbon.identity.api.user.governance/src/test/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImplTest.java b/components/org.wso2.carbon.identity.api.user.governance/src/test/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImplTest.java index 0be2862a60..247b0df034 100644 --- a/components/org.wso2.carbon.identity.api.user.governance/src/test/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImplTest.java +++ b/components/org.wso2.carbon.identity.api.user.governance/src/test/java/org/wso2/carbon/identity/user/endpoint/impl/MeApiServiceImplTest.java @@ -31,6 +31,7 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.common.model.ResolvedUser; import org.wso2.carbon.identity.application.common.model.User; +import org.wso2.carbon.identity.core.util.IdentityConfigParser; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.recovery.IdentityRecoveryClientException; import org.wso2.carbon.identity.recovery.IdentityRecoveryConstants; @@ -43,6 +44,7 @@ import org.wso2.carbon.identity.recovery.model.Property; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; import org.wso2.carbon.identity.recovery.signup.UserSelfRegistrationManager; +import org.wso2.carbon.identity.user.endpoint.Constants; import org.wso2.carbon.identity.user.endpoint.dto.ClaimDTO; import org.wso2.carbon.identity.user.endpoint.dto.MeCodeValidationRequestDTO; import org.wso2.carbon.identity.user.endpoint.dto.MeResendCodeRequestDTO; @@ -50,6 +52,7 @@ import org.wso2.carbon.identity.user.endpoint.dto.ResendCodeRequestDTO; import org.wso2.carbon.identity.user.endpoint.dto.SelfRegistrationUserDTO; import org.wso2.carbon.identity.user.endpoint.dto.SelfUserRegistrationRequestDTO; +import org.wso2.carbon.identity.user.endpoint.exceptions.BadRequestException; import org.wso2.carbon.identity.user.endpoint.util.Utils; import org.wso2.carbon.identity.user.export.core.UserExportException; import org.wso2.carbon.identity.workflow.mgt.WorkflowManagementService; @@ -58,7 +61,9 @@ import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -66,6 +71,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.expectThrows; import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.SELF_REGISTER_USER_EVENT; /** @@ -76,6 +82,8 @@ public class MeApiServiceImplTest { private static final String USERNAME = "dummyUser"; private MockedStatic mockedIdentityUtil; private MockedStatic mockedUtils; + private MockedStatic mockedIdentityConfigParser; + private IdentityConfigParser identityConfigParserMock; private ResendConfirmationManager resendConfirmationManager; @Mock @@ -101,6 +109,10 @@ public void setUp() { resendConfirmationManager = Mockito.mock(ResendConfirmationManager.class); mockedUtils = Mockito.mockStatic(Utils.class); mockedUtils.when(Utils::getUserSelfRegistrationManager).thenReturn(userSelfRegistrationManager); + identityConfigParserMock = Mockito.mock(IdentityConfigParser.class); + Mockito.when(identityConfigParserMock.getConfiguration()).thenReturn(new HashMap<>()); + mockedIdentityConfigParser = Mockito.mockStatic(IdentityConfigParser.class); + mockedIdentityConfigParser.when(IdentityConfigParser::getInstance).thenReturn(identityConfigParserMock); } @AfterMethod @@ -108,6 +120,7 @@ public void tearDown() { mockedIdentityUtil.close(); mockedUtils.close(); + mockedIdentityConfigParser.close(); } @Test @@ -119,6 +132,26 @@ public void testMePost() throws IdentityRecoveryException { assertEquals(meApiService.mePost(null).getStatus(), 201); } + @Test + public void testMePostRejectsIdentityClaims() throws IdentityRecoveryException { + + mockedUtils.when(() -> Utils.handleBadRequest(anyString(), anyString())).thenCallRealMethod(); + mockedUtils.when(() -> Utils.buildBadRequestException(anyString(), anyString())).thenCallRealMethod(); + mockedUtils.when(() -> Utils.getErrorDTO(anyString(), anyString(), anyString())).thenCallRealMethod(); + + String blockedClaimUri = "http://wso2.org/claims/identity/emailVerified"; + Map config = new HashMap<>(); + config.put(Constants.SCIM2_ME_BLOCKED_CLAIMS, blockedClaimUri); + Mockito.when(identityConfigParserMock.getConfiguration()).thenReturn(config); + + BadRequestException exception = expectThrows(BadRequestException.class, + () -> meApiService.mePost(selfUserRegistrationRequestDTOWithClaimUri(blockedClaimUri))); + assertEquals(exception.getMessage(), String.format("Claim '%s' is not allowed to be updated in " + + "self-registration.", blockedClaimUri)); + Mockito.verify(userSelfRegistrationManager, Mockito.never()).registerUser(any(User.class), anyString(), + any(Claim[].class), any(Property[].class)); + } + @Test public void testMePostWorkflowEngaged() throws IdentityRecoveryException, WorkflowException { @@ -327,8 +360,13 @@ private PropertyDTO buildSelfUserRegistrationRequestDTO() { private ClaimDTO buildClaimDTO() { + return buildClaimDTO("http://wso2.org.email"); + } + + private ClaimDTO buildClaimDTO(String claimUri) { + ClaimDTO claimDTO = new ClaimDTO(); - claimDTO.setUri("http://wso2.org.email"); + claimDTO.setUri(claimUri); claimDTO.setValue("test@gmail.com"); return claimDTO; } @@ -346,6 +384,15 @@ private SelfUserRegistrationRequestDTO selfUserRegistrationRequestDTO() { return selfUserRegistrationRequestDTO; } + private SelfUserRegistrationRequestDTO selfUserRegistrationRequestDTOWithClaimUri(String claimUri) { + + SelfUserRegistrationRequestDTO selfUserRegistrationRequestDTO = selfUserRegistrationRequestDTO(); + List listClaimDTO = new ArrayList<>(); + listClaimDTO.add(buildClaimDTO(claimUri)); + selfUserRegistrationRequestDTO.getUser().setClaims(listClaimDTO); + return selfUserRegistrationRequestDTO; + } + private MeCodeValidationRequestDTO createMeCodeValidationRequestDTO() { MeCodeValidationRequestDTO codeValidationRequestDTO = new MeCodeValidationRequestDTO();