diff --git a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandler.java b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandler.java index 99950fb52..db08d60b2 100644 --- a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandler.java +++ b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandler.java @@ -19,6 +19,7 @@ package org.wso2.carbon.identity.event.handler.notification; import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.base.IdentityRuntimeException; import org.wso2.carbon.identity.core.bean.context.MessageContext; import org.wso2.carbon.identity.event.IdentityEventConstants; @@ -266,15 +267,32 @@ private PushNotificationData buildPushNotificationData(Event event) throws Ident placeholderValues.put(ORGANIZATION_NAME_PLACEHOLDER, organizationName); } - /* - * If the tenant domain is different from the organization name, then it is an organization user. Hence, - * the organization ID is the tenant domain. - */ String organizationId = null; - if (!tenantDomain.equals(organizationName)) { - organizationId = tenantDomain; + String primaryTenantDomain = null; + + if (NotificationUtil.isOrganization(tenantDomain)) { + LOG.debug("Tenant domain is an organization."); + try { + organizationId = NotificationHandlerDataHolder.getInstance() + .getOrganizationManager().resolveOrganizationId(tenantDomain); + } catch (OrganizationManagementException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Error while resolving organization ID for tenant domain: " + tenantDomain, e); + } + throw new IdentityEventException(e.getMessage(), e); + } + + /* If an accessing organization ID is present, this flow is for a tenant organization path + * (/t/{tenant-domain}/o/{org-id}). + * Therefore, include the primary tenant domain in the PushNotificationData. */ + if (StringUtils.isNotEmpty(PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getAccessingOrganizationId())) { + LOG.debug("Adding primary tenant domain to the push notification data."); + primaryTenantDomain = NotificationUtil.getPrimaryTenantDomain(organizationId); + } } else { // If tenant user, organizationName is null. + LOG.debug("Tenant domain is not an organization."); organizationName = null; } @@ -289,6 +307,7 @@ private PushNotificationData buildPushNotificationData(Event event) throws Ident .setTenantDomain(tenantDomain) .setOrganizationId(organizationId) .setOrganizationName(organizationName) + .setPrimaryTenantDomain(primaryTenantDomain) .setUserStoreDomain((String) eventProperties.get( IdentityEventConstants.EventProperty.USER_STORE_DOMAIN)) .setApplicationName((String) eventProperties.get(IdentityEventConstants.EventProperty.APPLICATION_NAME)) diff --git a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtil.java b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtil.java index ac41c6b0c..83b281d98 100644 --- a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtil.java +++ b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/main/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2016-2026, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -869,6 +869,41 @@ public static String resolveHumanReadableOrganizationName(String tenantDomain) t return organizationName; } + /** + * Check whether the given tenant domain belongs to an organization. + * + * @param tenantDomain Tenant domain. + * @return true if the tenant domain belongs to an organization, false otherwise. + * @throws IdentityEventException Error while checking whether the tenant domain belongs to an organization. + */ + public static boolean isOrganization(String tenantDomain) throws IdentityEventException { + + try { + return OrganizationManagementUtil.isOrganization(tenantDomain); + } catch (OrganizationManagementException e) { + throw new IdentityEventException(e.getMessage(), e); + } + } + + /** + * Get the primary tenant domain of the given organization ID. + * + * @param organizationId Organization ID. + * @return Primary tenant domain. + * @throws IdentityEventException If an error occurred while getting the primary tenant domain. + */ + public static String getPrimaryTenantDomain(String organizationId) throws IdentityEventException { + + try { + OrganizationManager organizationManager = NotificationHandlerDataHolder.getInstance() + .getOrganizationManager(); + String primaryOrgId = organizationManager.getPrimaryOrganizationId(organizationId); + return organizationManager.resolveTenantDomain(primaryOrgId); + } catch (OrganizationManagementException e) { + throw new IdentityEventException(e.getMessage(), e); + } + } + /** * Get the notification locale. * diff --git a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandlerTest.java b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandlerTest.java index a83208556..7bade3b62 100644 --- a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandlerTest.java +++ b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/PushNotificationHandlerTest.java @@ -21,14 +21,17 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; +import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.event.IdentityEventException; @@ -757,6 +760,159 @@ public void testHandleEventWithProviderNameVariations() throws Exception { eq("carbon.super")); } + @Test + public void testHandleEventOrganizationTenantWithAccessingOrgId() throws Exception { + + String orgTenantDomain = "org-tenant"; + String orgId = "org-uuid-123"; + String primaryOrgTenantDomain = "primary-tenant"; + + Event event = createPushNotificationEvent("FCM", orgTenantDomain); + + try (MockedStatic mockedDataHolder = mockStatic( + NotificationHandlerDataHolder.class); + MockedStatic mockedNotificationUtil = mockStatic(NotificationUtil.class); + MockedStatic mockedPrivilegedCarbonContext = + mockStatic(PrivilegedCarbonContext.class)) { + + mockedDataHolder.when(NotificationHandlerDataHolder::getInstance) + .thenReturn(notificationHandlerDataHolder); + when(notificationHandlerDataHolder.getOrganizationManager()).thenReturn(organizationManager); + when(organizationManager.resolveOrganizationId(orgTenantDomain)).thenReturn(orgId); + + mockedNotificationUtil.when(() -> NotificationUtil.resolveHumanReadableOrganizationName(anyString())) + .thenReturn(SAMPLE_ORGANIZATION_NAME); + mockedNotificationUtil.when(() -> NotificationUtil.isOrganization(orgTenantDomain)) + .thenReturn(true); + mockedNotificationUtil.when(() -> NotificationUtil.getPrimaryTenantDomain(orgId)) + .thenReturn(primaryOrgTenantDomain); + + PrivilegedCarbonContext mockPrivilegedContext = mock(PrivilegedCarbonContext.class); + mockedPrivilegedCarbonContext.when(PrivilegedCarbonContext::getThreadLocalCarbonContext) + .thenReturn(mockPrivilegedContext); + when(mockPrivilegedContext.getAccessingOrganizationId()).thenReturn("accessing-org-id"); + + when(notificationHandlerDataHolder.getNotificationSenderManagementService()) + .thenReturn(notificationSenderManagementService); + PushSenderDTO sender = new PushSenderDTO(); + sender.setName("PushPublisher"); + sender.setProvider("FCM"); + sender.setProviderId("fcm-provider-id"); + List pushSenders = new ArrayList<>(); + pushSenders.add(sender); + when(notificationSenderManagementService.getPushSenders(true)).thenReturn(pushSenders); + when(notificationHandlerDataHolder.getPushProvider("FCM")).thenReturn(pushProvider); + + pushNotificationHandler.handleEvent(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PushNotificationData.class); + verify(pushProvider).sendNotification(captor.capture(), any(PushSenderData.class), + eq(orgTenantDomain)); + PushNotificationData data = captor.getValue(); + Assert.assertEquals(data.getOrganizationId(), orgId); + Assert.assertEquals(data.getPrimaryTenantDomain(), primaryOrgTenantDomain); + Assert.assertEquals(data.getOrganizationName(), SAMPLE_ORGANIZATION_NAME); + } + } + + @Test + public void testHandleEventOrganizationTenantWithoutAccessingOrgId() throws Exception { + + String orgTenantDomain = "org-tenant"; + String orgId = "org-uuid-123"; + + Event event = createPushNotificationEvent("FCM", orgTenantDomain); + + try (MockedStatic mockedDataHolder = mockStatic( + NotificationHandlerDataHolder.class); + MockedStatic mockedNotificationUtil = mockStatic(NotificationUtil.class); + MockedStatic mockedPrivilegedCarbonContext = + mockStatic(PrivilegedCarbonContext.class)) { + + mockedDataHolder.when(NotificationHandlerDataHolder::getInstance) + .thenReturn(notificationHandlerDataHolder); + when(notificationHandlerDataHolder.getOrganizationManager()).thenReturn(organizationManager); + when(organizationManager.resolveOrganizationId(orgTenantDomain)).thenReturn(orgId); + + mockedNotificationUtil.when(() -> NotificationUtil.resolveHumanReadableOrganizationName(anyString())) + .thenReturn(SAMPLE_ORGANIZATION_NAME); + mockedNotificationUtil.when(() -> NotificationUtil.isOrganization(orgTenantDomain)) + .thenReturn(true); + + PrivilegedCarbonContext mockPrivilegedContext = mock(PrivilegedCarbonContext.class); + mockedPrivilegedCarbonContext.when(PrivilegedCarbonContext::getThreadLocalCarbonContext) + .thenReturn(mockPrivilegedContext); + when(mockPrivilegedContext.getAccessingOrganizationId()).thenReturn(null); + + when(notificationHandlerDataHolder.getNotificationSenderManagementService()) + .thenReturn(notificationSenderManagementService); + PushSenderDTO sender = new PushSenderDTO(); + sender.setName("PushPublisher"); + sender.setProvider("FCM"); + sender.setProviderId("fcm-provider-id"); + List pushSenders = new ArrayList<>(); + pushSenders.add(sender); + when(notificationSenderManagementService.getPushSenders(true)).thenReturn(pushSenders); + when(notificationHandlerDataHolder.getPushProvider("FCM")).thenReturn(pushProvider); + + pushNotificationHandler.handleEvent(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PushNotificationData.class); + verify(pushProvider).sendNotification(captor.capture(), any(PushSenderData.class), + eq(orgTenantDomain)); + PushNotificationData data = captor.getValue(); + Assert.assertEquals(data.getOrganizationId(), orgId); + Assert.assertNull(data.getPrimaryTenantDomain()); + Assert.assertEquals(data.getOrganizationName(), SAMPLE_ORGANIZATION_NAME); + } + } + + /** + * Test that when the tenant domain is not an organization, the built PushNotificationData has + * null organizationId, organizationName, and primaryTenantDomain. + */ + @Test + public void testHandleEventNonOrganizationTenant() throws Exception { + + Event event = createPushNotificationEvent("FCM", "carbon.super"); + + try (MockedStatic mockedDataHolder = mockStatic( + NotificationHandlerDataHolder.class); + MockedStatic mockedNotificationUtil = mockStatic(NotificationUtil.class)) { + + mockedDataHolder.when(NotificationHandlerDataHolder::getInstance) + .thenReturn(notificationHandlerDataHolder); + when(notificationHandlerDataHolder.getOrganizationManager()).thenReturn(organizationManager); + when(organizationManager.resolveOrganizationId(anyString())).thenReturn("orgId"); + + mockedNotificationUtil.when(() -> NotificationUtil.resolveHumanReadableOrganizationName(anyString())) + .thenReturn(SAMPLE_ORGANIZATION_NAME); + mockedNotificationUtil.when(() -> NotificationUtil.isOrganization("carbon.super")) + .thenReturn(false); + + when(notificationHandlerDataHolder.getNotificationSenderManagementService()) + .thenReturn(notificationSenderManagementService); + PushSenderDTO sender = new PushSenderDTO(); + sender.setName("PushPublisher"); + sender.setProvider("FCM"); + sender.setProviderId("fcm-provider-id"); + List pushSenders = new ArrayList<>(); + pushSenders.add(sender); + when(notificationSenderManagementService.getPushSenders(true)).thenReturn(pushSenders); + when(notificationHandlerDataHolder.getPushProvider("FCM")).thenReturn(pushProvider); + + pushNotificationHandler.handleEvent(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PushNotificationData.class); + verify(pushProvider).sendNotification(captor.capture(), any(PushSenderData.class), + eq("carbon.super")); + PushNotificationData data = captor.getValue(); + Assert.assertNull(data.getOrganizationId()); + Assert.assertNull(data.getOrganizationName()); + Assert.assertNull(data.getPrimaryTenantDomain()); + } + } + // ==================== Helper Methods ==================== /** diff --git a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtilTest.java b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtilTest.java index 4d62dab19..a9cdf9a4b 100644 --- a/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtilTest.java +++ b/components/event-handler-notification/org.wso2.carbon.identity.event.handler.notification/src/test/java/org/wso2/carbon/identity/event/handler/notification/util/NotificationUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2025, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2022-2026, 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 @@ -48,6 +48,10 @@ import org.wso2.carbon.identity.event.event.Event; import org.wso2.carbon.identity.event.handler.notification.NotificationConstants; import org.wso2.carbon.identity.event.handler.notification.internal.NotificationHandlerDataHolder; +import org.wso2.carbon.identity.event.IdentityEventException; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil; import org.wso2.carbon.identity.organization.management.service.util.Utils; import org.wso2.carbon.utils.CarbonUtils; @@ -129,6 +133,9 @@ public class NotificationUtilTest { private static final String SAMPLE_ORGANIZATION_NAME = "OrganizationA"; private static final String SAMPLE_LOCALE = "fr-FR"; private static final String SAMPLE_EMAIL_BODY = "SampleEmailBody"; + private static final String SAMPLE_TENANT_DOMAIN = "sample.com"; + private static final String SAMPLE_ORG_UUID = "673b507c-6e29-40e1-8e87-0b24e9a97e12"; + private static final int SAMPLE_TENANT_ID = 5; private static final String ACCOUNT_RECOVERY_ENDPOINT_URL = "https://example.com/account/recovery"; private static final String AUTHENTICATION_ENDPOINT_URL = "https://example.com/authentication"; @@ -521,4 +528,87 @@ private void mockCarbonUtils(MockedStatic carbonUtils) { carbonUtils.when(() -> CarbonUtils.getServerConfiguration()).thenReturn(serverConfiguration); carbonUtils.when(CarbonUtils::getManagementTransport).thenReturn(DUMMY_PROTOCOL); } + + @DataProvider(name = "isOrganizationDataProvider") + public Object[][] isOrganizationDataProvider() { + + return new Object[][] { + {true}, + {false} + }; + } + + @Test(dataProvider = "isOrganizationDataProvider") + public void testIsOrganization(boolean expected) throws IdentityEventException { + + try (MockedStatic mockedOrgManagementUtil = + mockStatic(OrganizationManagementUtil.class)) { + + mockedOrgManagementUtil.when(() -> OrganizationManagementUtil.isOrganization(SAMPLE_TENANT_DOMAIN)) + .thenReturn(expected); + + boolean result = NotificationUtil.isOrganization(SAMPLE_TENANT_DOMAIN); + assertEquals(result, expected); + } + } + + @Test(expectedExceptions = IdentityEventException.class) + public void testIsOrganizationThrowsException() throws IdentityEventException { + + try (MockedStatic mockedOrgManagementUtil = + mockStatic(OrganizationManagementUtil.class)) { + + mockedOrgManagementUtil.when(() -> OrganizationManagementUtil.isOrganization(SAMPLE_TENANT_DOMAIN)) + .thenThrow(new OrganizationManagementException("Organization management error")); + + NotificationUtil.isOrganization(SAMPLE_TENANT_DOMAIN); + } + } + + /** + * Test getPrimaryTenantDomain returns the correct tenant domain when both + * getPrimaryOrganizationId and resolveTenantDomain succeed. + */ + @Test + public void testGetPrimaryTenantDomain() throws Exception { + + try (MockedStatic mockedDataHolder = + mockStatic(NotificationHandlerDataHolder.class)) { + + NotificationHandlerDataHolder mockDataHolder = mock(NotificationHandlerDataHolder.class); + OrganizationManager mockOrgManager = mock(OrganizationManager.class); + + mockedDataHolder.when(NotificationHandlerDataHolder::getInstance).thenReturn(mockDataHolder); + when(mockDataHolder.getOrganizationManager()).thenReturn(mockOrgManager); + when(mockOrgManager.getPrimaryOrganizationId(SAMPLE_ORG_UUID)).thenReturn("primary-org-id"); + when(mockOrgManager.resolveTenantDomain("primary-org-id")).thenReturn(SAMPLE_TENANT_DOMAIN); + + String result = NotificationUtil.getPrimaryTenantDomain(SAMPLE_ORG_UUID); + assertEquals(result, SAMPLE_TENANT_DOMAIN); + } + } + + /** + * Test getPrimaryTenantDomain throws IdentityEventException when + * resolveTenantDomain fails with OrganizationManagementException. + */ + @Test(expectedExceptions = IdentityEventException.class) + public void testGetPrimaryTenantDomainThrowsWhenResolveTenantDomainFails() + throws Exception { + + try (MockedStatic mockedDataHolder = + mockStatic(NotificationHandlerDataHolder.class)) { + + NotificationHandlerDataHolder mockDataHolder = mock(NotificationHandlerDataHolder.class); + OrganizationManager mockOrgManager = mock(OrganizationManager.class); + + mockedDataHolder.when(NotificationHandlerDataHolder::getInstance).thenReturn(mockDataHolder); + when(mockDataHolder.getOrganizationManager()).thenReturn(mockOrgManager); + when(mockOrgManager.getPrimaryOrganizationId(SAMPLE_ORG_UUID)).thenReturn("primary-org-id"); + when(mockOrgManager.resolveTenantDomain("primary-org-id")) + .thenThrow(new OrganizationManagementServerException("Error resolving tenant domain")); + + NotificationUtil.getPrimaryTenantDomain(SAMPLE_ORG_UUID); + } + } } diff --git a/pom.xml b/pom.xml index 4221dc1bc..44555fd89 100644 --- a/pom.xml +++ b/pom.xml @@ -536,7 +536,7 @@ 1.1.29 [1.0.1, 2.0.0) - 1.1.1 + 1.1.4 [1.0.0, 2.0.0) 3.0.0.wso2v4 @@ -560,7 +560,7 @@ [3.2.0,4.0.0) - 4.10.126 + 4.12.29 4.9.10 [4.5.0, 5.0.0)