From f603c79957199eadc23f81b9860cb5e86e9db144 Mon Sep 17 00:00:00 2001 From: Pasindu Yeshan Date: Wed, 22 Jan 2025 12:24:54 +0530 Subject: [PATCH 1/5] Introduce profile-name query param --- .../identity/recovery/endpoint/ClaimsApi.java | 28 +++++++++++++++++-- .../recovery/endpoint/ClaimsApiService.java | 23 +++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApi.java b/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApi.java index 252f64d134..65eac01dcd 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApi.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApi.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2016-2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.recovery.endpoint; import org.wso2.carbon.identity.recovery.endpoint.dto.*; @@ -38,9 +56,15 @@ public class ClaimsApi { @io.swagger.annotations.ApiResponse(code = 500, message = "Server Error") }) - public Response claimsGet(@ApiParam(value = "tenant domain. Default `carbon.super`") @QueryParam("tenant-domain") String tenantDomain) + public Response claimsGet( + @ApiParam(value = "tenant domain. Default `carbon.super`") + @QueryParam("tenant-domain") String tenantDomain, + + @ApiParam(value = "profile name.") + @QueryParam("profile-name") String profileName) { - return delegate.claimsGet(tenantDomain); + + return delegate.claimsGet(tenantDomain, profileName); } } diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiService.java b/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiService.java index ba76f52d5b..028fde03cf 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiService.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/gen/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiService.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2016-2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.recovery.endpoint; import org.wso2.carbon.identity.recovery.endpoint.*; @@ -15,5 +33,10 @@ public abstract class ClaimsApiService { public abstract Response claimsGet(String tenantDomain); + + public Response claimsGet(String tenantDomain, String profileName) { + + return claimsGet(tenantDomain); + } } From 9427f0427d049dcee6609513912311e4ac20742d Mon Sep 17 00:00:00 2001 From: Pasindu Yeshan Date: Wed, 22 Jan 2025 12:25:20 +0530 Subject: [PATCH 2/5] Get profile supported local claims --- .../recovery/endpoint/Utils/RecoveryUtil.java | 58 ++++++++++++++ .../endpoint/impl/ClaimsApiServiceImpl.java | 76 ++++++++++++++++--- 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/Utils/RecoveryUtil.java b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/Utils/RecoveryUtil.java index 9aef837e70..ae62cfdbaa 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/Utils/RecoveryUtil.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/Utils/RecoveryUtil.java @@ -1,5 +1,24 @@ +/* + * Copyright (c) 2016-2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.recovery.endpoint.Utils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -14,6 +33,9 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.common.model.User; import org.wso2.carbon.identity.captcha.util.CaptchaConstants; +import org.wso2.carbon.identity.claim.metadata.mgt.ClaimMetadataManagementService; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.governance.IdentityGovernanceException; @@ -74,6 +96,12 @@ public static UserSelfRegistrationManager getUserSelfRegistrationManager() { .getOSGiService(UserSelfRegistrationManager.class, null); } + public static ClaimMetadataManagementService getClaimMetadataManagementService() { + + return (ClaimMetadataManagementService) PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getOSGiService(ClaimMetadataManagementService.class, null); + } + /** * To get identity governance service * @@ -201,6 +229,36 @@ public static ClaimDTO getClaimDTO(Claim claim) { return claimDTO; } + public static ClaimDTO[] getClaimDTOs(List claims) { + + if (claims == null) { + return new ClaimDTO[0]; + } + + ClaimDTO[] claimDTOs = new ClaimDTO[claims.size()]; + for (int i = 0; i < claims.size(); i++) { + claimDTOs[i] = getClaimDTO(claims.get(i)); + } + return claimDTOs; + } + + public static ClaimDTO getClaimDTO(LocalClaim claim) { + + ClaimDTO claimDTO = new ClaimDTO(); + claimDTO.setUri(claim.getClaimURI()); + claimDTO.setDialect(claim.getClaimDialectURI()); + + Map claimProperties = claim.getClaimProperties(); + if (MapUtils.isNotEmpty(claimProperties)) { + claimDTO.setDescription(claimProperties.get(ClaimConstants.DESCRIPTION_PROPERTY)); + claimDTO.setDisplayName(claimProperties.get(ClaimConstants.DISPLAY_NAME_PROPERTY)); + claimDTO.setRequired(Boolean.parseBoolean(claimProperties.get(ClaimConstants.REQUIRED_PROPERTY))); + claimDTO.setReadOnly(Boolean.parseBoolean(claimProperties.get(ClaimConstants.READ_ONLY_PROPERTY))); + claimDTO.setValidationRegex(claimProperties.get(ClaimConstants.REGULAR_EXPRESSION_PROPERTY)); + } + return claimDTO; + } + public static UserClaim[] getUserClaims(List claimDTOs) { UserClaim[] userClaims = new UserClaim[claimDTOs.size()]; for (int i = 0; i < claimDTOs.size(); i++) { diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java index 351d4f0355..20e436fb01 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java @@ -1,8 +1,29 @@ +/* + * Copyright (c) 2016-2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.recovery.endpoint.impl; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.cxf.common.util.CollectionUtils; +import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.recovery.IdentityRecoveryClientException; import org.wso2.carbon.identity.recovery.IdentityRecoveryConstants; @@ -30,26 +51,47 @@ public class ClaimsApiServiceImpl extends ClaimsApiService { private static final Log LOG = LogFactory.getLog(ClaimsApiServiceImpl.class); @Override - public Response claimsGet(String tenantDomain) { + public Response claimsGet(String tenantDomain, String profileName) { - if (IdentityUtil.threadLocalProperties.get().get(Constants.TENANT_NAME_FROM_CONTEXT) != null) { - tenantDomain = (String) IdentityUtil.threadLocalProperties.get().get(Constants.TENANT_NAME_FROM_CONTEXT); + if (StringUtils.isBlank(profileName)) { + return claimsGet(tenantDomain); } - if (StringUtils.isBlank(tenantDomain)) { - tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; - } else if (!RecoveryUtil.isValidTenantDomain(tenantDomain)) { - RecoveryUtil.handleBadRequest("Invalid tenant domain :" + tenantDomain, IdentityRecoveryConstants - .ErrorMessages.ERROR_CODE_INVALID_TENANT.getCode()); + tenantDomain = resolveTenantDomain(tenantDomain); + ClaimDTO[] claimDTOs = new ClaimDTO[0]; + try { + List localClaims = RecoveryUtil.getClaimMetadataManagementService() + .getSupportedLocalClaimsForProfile(tenantDomain, profileName); + if (CollectionUtils.isEmpty(localClaims)) { + claimDTOs = new ClaimDTO[0]; + } + claimDTOs = RecoveryUtil.getClaimDTOs(localClaims); + } catch (ClaimMetadataException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Client Error while getting all identity claims ", e); + } + RecoveryUtil.handleBadRequest(e.getMessage(), e.getErrorCode()); + } catch (Throwable throwable) { + RecoveryUtil.handleInternalServerError(Constants.SERVER_ERROR, IdentityRecoveryConstants + .ErrorMessages.ERROR_CODE_UNEXPECTED.getCode(), LOG, throwable); + } + return Response.ok(claimDTOs).build(); + } + + @Override + public Response claimsGet(String tenantDomain) { + + tenantDomain = resolveTenantDomain(tenantDomain); String dialect = IdentityRecoveryConstants.WSO2CARBON_CLAIM_DIALECT; NotificationUsernameRecoveryManager notificationBasedUsernameRecoveryManager = RecoveryUtil .getNotificationBasedUsernameRecoveryManager(); ClaimDTO[] claimDTOs = new ClaimDTO[0]; + try { - Claim[] userClaims = notificationBasedUsernameRecoveryManager.getIdentitySupportedClaims(dialect, tenantDomain); + Claim[] userClaims = + notificationBasedUsernameRecoveryManager.getIdentitySupportedClaims(dialect, tenantDomain); claimDTOs = RecoveryUtil.getClaimDTOs(userClaims); - } catch (IdentityRecoveryClientException e) { if (LOG.isDebugEnabled()) { LOG.debug("Client Error while getting all identity claims ", e); @@ -64,4 +106,18 @@ public Response claimsGet(String tenantDomain) { } return Response.ok(claimDTOs).build(); } + + private static String resolveTenantDomain(String tenantDomain) { + + if (IdentityUtil.threadLocalProperties.get().get(Constants.TENANT_NAME_FROM_CONTEXT) != null) { + tenantDomain = (String) IdentityUtil.threadLocalProperties.get().get(Constants.TENANT_NAME_FROM_CONTEXT); + } + if (StringUtils.isBlank(tenantDomain)) { + tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; + } else if (!RecoveryUtil.isValidTenantDomain(tenantDomain)) { + RecoveryUtil.handleBadRequest("Invalid tenant domain :" + tenantDomain, IdentityRecoveryConstants + .ErrorMessages.ERROR_CODE_INVALID_TENANT.getCode()); + } + return tenantDomain; + } } From f72cde7e3e2d36f08e6aad3eff4cce3d371e74f0 Mon Sep 17 00:00:00 2001 From: Pasindu Yeshan Date: Wed, 22 Jan 2025 21:02:00 +0530 Subject: [PATCH 3/5] Add unit tests and update yaml --- .../main/resources/api.identity.recovery.yaml | 5 +++ .../endpoint/ClaimsApiServiceImplTest.java | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/main/resources/api.identity.recovery.yaml b/components/org.wso2.carbon.identity.api.user.recovery/src/main/resources/api.identity.recovery.yaml index 9582788ea9..263464da8b 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/main/resources/api.identity.recovery.yaml +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/main/resources/api.identity.recovery.yaml @@ -270,6 +270,11 @@ paths: required: false type: string + - name: profile-name + in: query + description: profile name` + required: false + type: string responses: 200: diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/test/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiServiceImplTest.java b/components/org.wso2.carbon.identity.api.user.recovery/src/test/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiServiceImplTest.java index fccb4dcd2d..44d54a924c 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/test/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiServiceImplTest.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/test/java/org/wso2/carbon/identity/recovery/endpoint/ClaimsApiServiceImplTest.java @@ -26,13 +26,22 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.claim.metadata.mgt.ClaimMetadataManagementService; +import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.recovery.endpoint.Exceptions.BadRequestException; import org.wso2.carbon.identity.recovery.endpoint.Utils.RecoveryUtil; import org.wso2.carbon.identity.recovery.endpoint.impl.ClaimsApiServiceImpl; import org.wso2.carbon.identity.recovery.password.NotificationPasswordRecoveryManager; import org.wso2.carbon.identity.recovery.username.NotificationUsernameRecoveryManager; import org.wso2.carbon.user.core.claim.Claim; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.anyString; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; /** * Unit tests for ClaimsApiServiceImpl.java @@ -47,6 +56,9 @@ public class ClaimsApiServiceImplTest { @Mock NotificationUsernameRecoveryManager notificationUsernameRecoveryManager; + @Mock + ClaimMetadataManagementService claimMetadataManagementService; + @InjectMocks ClaimsApiServiceImpl claimsApiService; @@ -74,6 +86,30 @@ public void testClaimsGet() throws IdentityException { assertEquals(claimsApiService.claimsGet(null).getStatus(), 200); } + @Test + public void testClaimsGetWithProfile() throws IdentityException { + + mockedRecoveryUtil.when(RecoveryUtil::getClaimMetadataManagementService).thenReturn( + claimMetadataManagementService); + List localClaims = new ArrayList<>(); + Mockito.when(claimMetadataManagementService + .getSupportedLocalClaimsForProfile("carbon.super", "selfRegistration")) + .thenReturn(localClaims); + assertEquals(claimsApiService.claimsGet("carbon.super", "selfRegistration") + .getStatus(), 200); + + // Case 2: Error while retrieving claims. + Mockito.when(claimMetadataManagementService + .getSupportedLocalClaimsForProfile("carbon.super", "selfRegistration")) + .thenThrow(new ClaimMetadataException("Error")); + + mockedRecoveryUtil.when(() -> RecoveryUtil.handleBadRequest(anyString(), anyString())) + .thenThrow(new BadRequestException()); + + assertThrows(BadRequestException.class, () -> + claimsApiService.claimsGet("carbon.super", "selfRegistration")); + } + @Test public void testThrowableinClaimsGet() throws IdentityException { From 6d76ac6095a4f3ed7fc96bc71c90b5a42eee0413 Mon Sep 17 00:00:00 2001 From: Pasindu Yeshan Date: Wed, 22 Jan 2025 21:25:27 +0530 Subject: [PATCH 4/5] Update framework version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1600bb50b7..7fd3d2eb61 100644 --- a/pom.xml +++ b/pom.xml @@ -700,7 +700,7 @@ [1.0.1, 2.0.0) - 7.7.14 + 7.7.114 [7.3.6, 8.0.0) From ba17aa3fbe99044264bb496873153f4ef0263881 Mon Sep 17 00:00:00 2001 From: Pasindu Yeshan Date: Wed, 22 Jan 2025 21:26:45 +0530 Subject: [PATCH 5/5] Refactor --- .../identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java index 20e436fb01..8b4c18fe1e 100644 --- a/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java +++ b/components/org.wso2.carbon.identity.api.user.recovery/src/main/java/org/wso2/carbon/identity/recovery/endpoint/impl/ClaimsApiServiceImpl.java @@ -74,7 +74,6 @@ public Response claimsGet(String tenantDomain, String profileName) { } catch (Throwable throwable) { RecoveryUtil.handleInternalServerError(Constants.SERVER_ERROR, IdentityRecoveryConstants .ErrorMessages.ERROR_CODE_UNEXPECTED.getCode(), LOG, throwable); - } return Response.ok(claimDTOs).build(); }