Skip to content

Commit 4504883

Browse files
authored
Merge pull request #55 from ThaminduR/portal-url-resolver
Add PortalURLResolver
2 parents cc0cfa9 + 7964d39 commit 4504883

6 files changed

Lines changed: 356 additions & 2 deletions

File tree

components/org.wso2.carbon.identity.branding.preference.management.core/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
<groupId>org.wso2.carbon.identity.framework</groupId>
6666
<artifactId>org.wso2.carbon.identity.ai.service.mgt</artifactId>
6767
</dependency>
68+
<dependency>
69+
<groupId>org.wso2.carbon.identity.framework</groupId>
70+
<artifactId>org.wso2.carbon.identity.flow.execution.engine</artifactId>
71+
</dependency>
6872
<dependency>
6973
<groupId>commons-logging</groupId>
7074
<artifactId>commons-logging</artifactId>
@@ -179,8 +183,9 @@
179183
org.wso2.carbon.identity.ai.service.mgt.*; version="${carbon.identity.package.import.version.range}",
180184
org.wso2.carbon.utils;version="${carbon.kernel.package.import.version.range}",
181185
org.wso2.carbon.stratos.common.listeners;version="${carbon.commons.imp.pkg.version}",
182-
186+
org.wso2.carbon.identity.flow.execution.engine.*; version="${carbon.identity.package.import.version.range}",
183187
org.json; version="${json.wso2.version.range}",
188+
org.wso2.carbon.identity.core.*; version="${carbon.identity.package.import.version.range}",
184189
</Import-Package>
185190
</instructions>
186191
</configuration>

components/org.wso2.carbon.identity.branding.preference.management.core/src/main/java/org/wso2/carbon/identity/branding/preference/management/core/internal/BrandingPreferenceManagerComponent.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
import org.wso2.carbon.identity.branding.preference.management.core.ai.BrandingAIPreferenceManager;
3333
import org.wso2.carbon.identity.branding.preference.management.core.ai.BrandingAIPreferenceManagerImpl;
3434
import org.wso2.carbon.identity.branding.preference.management.core.listener.IdentityTenantMgtListener;
35+
import org.wso2.carbon.identity.branding.preference.management.core.listener.PortalURLResolver;
3536
import org.wso2.carbon.identity.configuration.mgt.core.ConfigurationManager;
3637
import org.wso2.carbon.identity.event.services.IdentityEventService;
38+
import org.wso2.carbon.identity.flow.execution.engine.listener.FlowExecutionListener;
3739
import org.wso2.carbon.stratos.common.listeners.TenantMgtListener;
3840

3941
/**
@@ -52,13 +54,16 @@ public class BrandingPreferenceManagerComponent {
5254
protected void activate(ComponentContext context) {
5355

5456
try {
57+
BrandingPreferenceManagerImpl brandingPreferenceManager = new BrandingPreferenceManagerImpl();
5558
context.getBundleContext()
56-
.registerService(BrandingPreferenceManager.class, new BrandingPreferenceManagerImpl(), null);
59+
.registerService(BrandingPreferenceManager.class, brandingPreferenceManager, null);
5760
context.getBundleContext()
5861
.registerService(BrandingAIPreferenceManager.class.getName(), new BrandingAIPreferenceManagerImpl(),
5962
null);
6063
context.getBundleContext()
6164
.registerService(TenantMgtListener.class.getName(), new IdentityTenantMgtListener(), null);
65+
context.getBundleContext().registerService(FlowExecutionListener.class.getName(),
66+
new PortalURLResolver(brandingPreferenceManager), null);
6267
if (LOG.isDebugEnabled()) {
6368
LOG.debug("BrandingPreferenceMgt Service Component is activated.");
6469
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.wso2.carbon.identity.branding.preference.management.core.listener;
20+
21+
import org.apache.commons.lang.StringUtils;
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
import org.wso2.carbon.identity.branding.preference.management.core.BrandingPreferenceManagerImpl;
25+
import org.wso2.carbon.identity.branding.preference.management.core.exception.BrandingPreferenceMgtClientException;
26+
import org.wso2.carbon.identity.branding.preference.management.core.exception.BrandingPreferenceMgtException;
27+
import org.wso2.carbon.identity.branding.preference.management.core.model.BrandingPreference;
28+
import org.wso2.carbon.identity.core.ServiceURLBuilder;
29+
import org.wso2.carbon.identity.core.URLBuilderException;
30+
import org.wso2.carbon.identity.flow.execution.engine.listener.AbstractFlowExecutionListener;
31+
import org.wso2.carbon.identity.flow.execution.engine.model.FlowExecutionContext;
32+
33+
import java.util.Map;
34+
35+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.APPLICATION_TYPE;
36+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.BRANDING_URLS;
37+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.DEFAULT_LOCALE;
38+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.ORGANIZATION_TYPE;
39+
40+
/**
41+
* This class is responsible for injecting the portal URL during flow execution.
42+
*/
43+
public class PortalURLResolver extends AbstractFlowExecutionListener {
44+
45+
private static final Log LOG = LogFactory.getLog(PortalURLResolver.class);
46+
private final BrandingPreferenceManagerImpl brandingPreferenceManager;
47+
public static final String SELF_SIGN_UP_URL = "selfSignUpURL";
48+
public static final String DEFAULT_REGISTRATION_PORTAL_URL = "/authenticationendpoint/register.do";
49+
public static final String REGISTRATION = "REGISTRATION";
50+
51+
public PortalURLResolver(BrandingPreferenceManagerImpl brandingPreferenceManager) {
52+
53+
this.brandingPreferenceManager = brandingPreferenceManager;
54+
}
55+
56+
@Override
57+
public int getExecutionOrderId() {
58+
59+
return 4;
60+
}
61+
62+
@Override
63+
public int getDefaultOrderId() {
64+
65+
return 4;
66+
}
67+
68+
@Override
69+
public boolean isEnabled() {
70+
71+
return true;
72+
}
73+
74+
@Override
75+
public boolean doPreExecute(FlowExecutionContext context) {
76+
77+
try {
78+
if (StringUtils.isNotBlank(context.getPortalUrl())) {
79+
return true;
80+
}
81+
String applicationId = context.getApplicationId();
82+
String tenantDomain = context.getTenantDomain();
83+
String type = StringUtils.isBlank(applicationId) ? ORGANIZATION_TYPE : APPLICATION_TYPE;
84+
String name = StringUtils.isBlank(applicationId) ? tenantDomain : applicationId;
85+
86+
BrandingPreference preference = brandingPreferenceManager.getBrandingPreference(type, name, DEFAULT_LOCALE);
87+
88+
if (preference != null) {
89+
Map<String, Object> prefMap = (Map<String, Object>) preference.getPreference();
90+
Map<String, String> urlMap = (Map<String, String>) prefMap.get(BRANDING_URLS);
91+
92+
if (REGISTRATION.equals(context.getFlowType())) {
93+
String signUpUrl = (urlMap != null) ? urlMap.get(SELF_SIGN_UP_URL) : null;
94+
95+
if (StringUtils.isNotBlank(signUpUrl)) {
96+
context.setPortalUrl(signUpUrl);
97+
} else {
98+
logMissingSelfSignupUrl(context);
99+
context.setPortalUrl(buildDefaultRegistrationUrl());
100+
}
101+
}
102+
}
103+
if (StringUtils.isBlank(context.getPortalUrl())) {
104+
logMissingSelfSignupUrl(context);
105+
context.setPortalUrl(buildDefaultRegistrationUrl());
106+
}
107+
return true;
108+
} catch (BrandingPreferenceMgtClientException e) {
109+
logMissingSelfSignupUrl(context);
110+
try {
111+
context.setPortalUrl(buildDefaultRegistrationUrl());
112+
} catch (URLBuilderException ex) {
113+
LOG.error("Failed to build default registration URL for tenant: " + context.getTenantDomain(), ex);
114+
return false;
115+
}
116+
return true;
117+
} catch (BrandingPreferenceMgtException e) {
118+
LOG.error("Error retrieving branding preference for tenant: " + context.getTenantDomain(), e);
119+
return false;
120+
} catch (URLBuilderException e) {
121+
LOG.error("Error building default registration portal URL for tenant: " + context.getTenantDomain(), e);
122+
return false;
123+
}
124+
}
125+
126+
private static void logMissingSelfSignupUrl(FlowExecutionContext context) {
127+
128+
LOG.debug("Self sign-up URL not configured for tenant: " + context.getTenantDomain() + ". Using default URL: "
129+
+ DEFAULT_REGISTRATION_PORTAL_URL);
130+
}
131+
132+
private String buildDefaultRegistrationUrl() throws URLBuilderException {
133+
134+
return ServiceURLBuilder.create()
135+
.addPath(DEFAULT_REGISTRATION_PORTAL_URL)
136+
.build()
137+
.getAbsolutePublicURL();
138+
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.wso2.carbon.identity.branding.preference.management.core.listener;
20+
21+
import org.mockito.Mock;
22+
import org.mockito.MockedStatic;
23+
import org.mockito.MockitoAnnotations;
24+
import org.testng.annotations.BeforeMethod;
25+
import org.testng.annotations.Test;
26+
import org.wso2.carbon.identity.branding.preference.management.core.BrandingPreferenceManagerImpl;
27+
import org.wso2.carbon.identity.branding.preference.management.core.exception.BrandingPreferenceMgtClientException;
28+
import org.wso2.carbon.identity.branding.preference.management.core.exception.BrandingPreferenceMgtException;
29+
import org.wso2.carbon.identity.branding.preference.management.core.model.BrandingPreference;
30+
import org.wso2.carbon.identity.core.ServiceURL;
31+
import org.wso2.carbon.identity.core.ServiceURLBuilder;
32+
import org.wso2.carbon.identity.core.URLBuilderException;
33+
import org.wso2.carbon.identity.flow.execution.engine.model.FlowExecutionContext;
34+
35+
import java.util.HashMap;
36+
import java.util.Map;
37+
38+
import static org.mockito.ArgumentMatchers.anyString;
39+
import static org.mockito.Mockito.any;
40+
import static org.mockito.Mockito.mock;
41+
import static org.mockito.Mockito.mockStatic;
42+
import static org.mockito.Mockito.never;
43+
import static org.mockito.Mockito.verify;
44+
import static org.mockito.Mockito.when;
45+
import static org.testng.Assert.assertFalse;
46+
import static org.testng.Assert.assertTrue;
47+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.DEFAULT_LOCALE;
48+
import static org.wso2.carbon.identity.branding.preference.management.core.constant.BrandingPreferenceMgtConstants.ORGANIZATION_TYPE;
49+
50+
/**
51+
* Unit tests for {@link PortalURLResolver}.
52+
*/
53+
public class PortalURLResolverTest {
54+
55+
public static final String PORTAL_URL = "https://signup.wso2.com";
56+
@Mock
57+
private BrandingPreferenceManagerImpl brandingPreferenceManager;
58+
59+
@Mock
60+
private FlowExecutionContext flowContext;
61+
62+
@Mock
63+
private BrandingPreference brandingPreference;
64+
65+
private PortalURLResolver resolver;
66+
67+
@BeforeMethod
68+
public void setup() {
69+
70+
MockitoAnnotations.openMocks(this);
71+
resolver = new PortalURLResolver(brandingPreferenceManager);
72+
}
73+
74+
@Test
75+
public void skipSetPortalUrl() {
76+
77+
when(flowContext.getPortalUrl()).thenReturn("https://existing.url");
78+
79+
boolean result = resolver.doPreExecute(flowContext);
80+
81+
assertTrue(result);
82+
verify(flowContext, never()).setPortalUrl(any());
83+
}
84+
85+
@Test
86+
public void validPref_setsCustomSignupUrl() throws Exception {
87+
88+
when(flowContext.getPortalUrl()).thenReturn(null);
89+
when(flowContext.getApplicationId()).thenReturn(null);
90+
when(flowContext.getTenantDomain()).thenReturn("wso2.com");
91+
when(flowContext.getFlowType()).thenReturn("REGISTRATION");
92+
93+
Map<String, String> urlsMap = new HashMap<>();
94+
urlsMap.put("selfSignUpURL", PORTAL_URL);
95+
Map<String, Object> prefMap = new HashMap<>();
96+
prefMap.put("urls", urlsMap);
97+
98+
when(brandingPreferenceManager.getBrandingPreference(ORGANIZATION_TYPE, "wso2.com", DEFAULT_LOCALE))
99+
.thenReturn(brandingPreference);
100+
when(brandingPreference.getPreference()).thenReturn(prefMap);
101+
102+
try (MockedStatic<ServiceURLBuilder> serviceURLBuilder = mockStatic(ServiceURLBuilder.class)) {
103+
104+
mockServiceURLBuilder();
105+
boolean result = resolver.doPreExecute(flowContext);
106+
assertTrue(result);
107+
verify(flowContext).setPortalUrl(PORTAL_URL);
108+
}
109+
}
110+
111+
@Test
112+
public void fallBackToDefaultUrl() throws Exception {
113+
114+
when(flowContext.getPortalUrl()).thenReturn("");
115+
when(flowContext.getApplicationId()).thenReturn(null);
116+
when(flowContext.getTenantDomain()).thenReturn("wso2.com");
117+
when(flowContext.getFlowType()).thenReturn("REGISTRATION");
118+
119+
when(brandingPreferenceManager.getBrandingPreference(ORGANIZATION_TYPE, "wso2.com", DEFAULT_LOCALE))
120+
.thenReturn(null);
121+
122+
try (MockedStatic<ServiceURLBuilder> serviceURLBuilder = mockStatic(ServiceURLBuilder.class)) {
123+
124+
mockServiceURLBuilder();
125+
boolean result = resolver.doPreExecute(flowContext);
126+
assertTrue(result);
127+
}
128+
}
129+
130+
@Test
131+
public void prefWithoutSelfSignupUrl() throws Exception {
132+
133+
when(flowContext.getPortalUrl()).thenReturn(null);
134+
when(flowContext.getApplicationId()).thenReturn(null);
135+
when(flowContext.getTenantDomain()).thenReturn("wso2.com");
136+
when(flowContext.getFlowType()).thenReturn("REGISTRATION");
137+
138+
Map<String, Object> prefMap = new HashMap<>();
139+
prefMap.put("urls", new HashMap<>());
140+
when(brandingPreferenceManager.getBrandingPreference(ORGANIZATION_TYPE, "wso2.com", DEFAULT_LOCALE))
141+
.thenReturn(brandingPreference);
142+
when(brandingPreference.getPreference()).thenReturn(prefMap);
143+
144+
try (MockedStatic<ServiceURLBuilder> serviceURLBuilder = mockStatic(ServiceURLBuilder.class)) {
145+
146+
mockServiceURLBuilder();
147+
boolean result = resolver.doPreExecute(flowContext);
148+
assertTrue(result);
149+
}
150+
}
151+
152+
private static void mockServiceURLBuilder() throws URLBuilderException {
153+
154+
ServiceURLBuilder serviceURLBuilderMock = mock(ServiceURLBuilder.class);
155+
when(ServiceURLBuilder.create()).thenReturn(serviceURLBuilderMock);
156+
when(serviceURLBuilderMock.addPath(anyString())).thenReturn(serviceURLBuilderMock);
157+
ServiceURL serviceURL = mock(ServiceURL.class);
158+
when(serviceURLBuilderMock.build()).thenReturn(serviceURL);
159+
String url = "https://default.url";
160+
when(serviceURL.getAbsolutePublicURL()).thenReturn(url);
161+
}
162+
163+
@Test
164+
public void clientExceptionForMissingConfig() throws Exception {
165+
166+
when(flowContext.getPortalUrl()).thenReturn(null);
167+
when(flowContext.getApplicationId()).thenReturn(null);
168+
when(flowContext.getTenantDomain()).thenReturn("foo.com");
169+
when(flowContext.getFlowType()).thenReturn("REGISTRATION");
170+
171+
BrandingPreferenceMgtClientException ex = new BrandingPreferenceMgtClientException("Not configured",
172+
"BRAND-60001");
173+
when(brandingPreferenceManager.getBrandingPreference(ORGANIZATION_TYPE, "foo.com",
174+
DEFAULT_LOCALE)).thenThrow(ex);
175+
176+
try (MockedStatic<ServiceURLBuilder> serviceURLBuilder = mockStatic(ServiceURLBuilder.class)) {
177+
178+
mockServiceURLBuilder();
179+
boolean result = resolver.doPreExecute(flowContext);
180+
assertTrue(result);
181+
}
182+
}
183+
184+
@Test
185+
public void internalErrorDuringURLResolving() throws Exception {
186+
187+
when(flowContext.getPortalUrl()).thenReturn(null);
188+
when(flowContext.getApplicationId()).thenReturn(null);
189+
when(flowContext.getTenantDomain()).thenReturn("foo.com");
190+
when(flowContext.getFlowType()).thenReturn("REGISTRATION");
191+
192+
when(brandingPreferenceManager.getBrandingPreference(any(), any(), any()))
193+
.thenThrow(new BrandingPreferenceMgtException("Something broke", "BRAND-600xx"));
194+
195+
boolean result = resolver.doPreExecute(flowContext);
196+
197+
assertFalse(result);
198+
}
199+
}

components/org.wso2.carbon.identity.branding.preference.management.core/src/test/resources/testng.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<class name="org.wso2.carbon.identity.branding.preference.management.core.dao.impl.OrgCustomContentDAOImplTest"/>
3030
<class name="org.wso2.carbon.identity.branding.preference.management.core.dao.impl.CustomContentPersistentDAOImplTest"/>
3131
<class name="org.wso2.carbon.identity.branding.preference.management.core.listener.IdentityTenantMgtListenerTest"/>
32+
<class name="org.wso2.carbon.identity.branding.preference.management.core.listener.PortalURLResolverTest"/>
3233
</classes>
3334
</test>
3435
</suite>

0 commit comments

Comments
 (0)