Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void invalidateSession(final HttpServletRequest request) {
private void doLogout(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
final HttpSession session = request.getSession();
final String idToken = (String)session.getAttribute(SESSION_PARAM_ID_TOKEN);
final String redirectUri = request.getRequestURL().toString().replace("/do/logout.action", "");
final String redirectUri = UrlUtils.determineFrontendURL(request).replace("/do/logout.action", "");
session.invalidate();
response.sendRedirect(oidcService.getLogoutUrl(redirectUri, idToken));
}
Expand All @@ -224,7 +224,7 @@ private void doLogin(final HttpServletRequest request, final HttpServletResponse
final HttpSession session = request.getSession();
final String authorizationCode = request.getParameter("code");
final String stateParameter = request.getParameter("state");
final String redirectUri = request.getRequestURL().toString();
final String redirectUri = UrlUtils.determineFrontendURL(request);
final String redirectTo = request.getParameter("redirectTo");
final String error = request.getParameter("error");
final String errorDescription = request.getParameter("error_description");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.entando.entando.aps.util.UrlUtils;
import org.entando.entando.ent.exception.EntException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -58,7 +59,7 @@ public int service(RequestContext reqCtx, int status) {
} else if (!currentUser.getUsername().equalsIgnoreCase(SystemConstants.GUEST_USER_NAME)) {
return this.returnUserNotAuthorized(reqCtx);
} else {
StringBuilder targetUrl = new StringBuilder(req.getRequestURL());
StringBuilder targetUrl = new StringBuilder(UrlUtils.determineFrontendURL(req));
targetUrl.append("?");
String queryString = req.getQueryString();
if (null != queryString && queryString.trim().length() > 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<DynamicMapping>
<enabled>false</enabled>
<persist>FULL</persist>
<mappings>
<mapping>
<enabled>true</enabled>
<path>groups</path>
<kind>GROUPCLAIM</kind>
</mapping>
<mapping>
<enabled>true</enabled>
<path>realm_access.roles</path>
<kind>ROLECLAIM</kind>
</mapping>
<mapping>
<enabled>false</enabled>
<path>realm_access.roles</path>
<kind>ROLEGROUPCLAIM</kind>
<separator>_SEP_</separator>
</mapping>
<mapping>
<enabled>false</enabled>
<attribute>AD_ROLE</attribute>
<kind>ROLE</kind>
</mapping>
<mapping>
<enabled>false</enabled>
<attribute>AD_GROUP</attribute>
<kind>GROUP</kind>
</mapping>
<mapping>
<enabled>false</enabled>
<attribute>AD_GROUPROLE</attribute>
<kind>ROLEGROUP</kind>
<separator>_r_</separator>
</mapping>
</mappings>
<exclusions>
<exclusion>default-roles-entando-development</exclusion>
<exclusion>offline_access</exclusion>
<exclusion>uma_authorization</exclusion>
</exclusions>
<roles>
<role>imported_role</role>
<role>imported_role2</role>
</roles>
<groups>
<group>imported_group</group>
<group>imported_group2</group>
</groups>
</DynamicMapping>
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public void setUp() {
Mockito.lenient().when(session.getServletContext()).thenReturn(svCtx);
Mockito.lenient().when(wac.getBean(ITenantManager.class)).thenReturn(tenantManager);
Mockito.lenient().when(request.getServerName()).thenReturn("dev.entando.org");
Mockito.lenient().when(request.getScheme()).thenReturn("https");
Mockito.lenient().when(request.getServerPort()).thenReturn(443);
Mockito.lenient().when(request.getHeader("X-Forwarded-Proto")).thenReturn("https");
Mockito.lenient().when(request.getHeader("Host")).thenReturn("dev.entando.org");
}
Expand All @@ -117,7 +119,7 @@ void testAuthenticationFlow() throws IOException, ServletException, EntException
when(request.getRequestURL()).thenReturn(new StringBuffer(loginEndpoint));
Mockito.lenient().when(request.getParameter(eq("redirectTo"))).thenReturn(requestRedirect);

final String redirect = "http://dev.entando.org/auth/realms/entando/protocol/openid-connect/auth";
final String redirect = "https://dev.entando.org/auth/realms/entando/protocol/openid-connect/auth";
when(oidcService.getRedirectUrl(any(), any(), any())).thenReturn(redirect);

keycloakFilter.doFilter(request, response, filterChain);
Expand Down Expand Up @@ -169,7 +171,6 @@ void testAuthenticationFlow() throws IOException, ServletException, EntException

@Test
void testAuthenticationFlowWithError() {
final String loginEndpoint = "https://dev.entando.org/entando-app/do/login";
final String state = "0ca97afd-f0b0-4860-820a-b7cd1414f69c";
final String authorizationCode = "the-authorization-code-from-keycloak";

Expand All @@ -185,7 +186,6 @@ void testAuthenticationFlowWithError() {
when(request.getServletPath()).thenReturn("/do/login");
when(request.getParameter(eq("code"))).thenReturn(authorizationCode);
when(request.getParameter(eq("state"))).thenReturn(state);
when(request.getRequestURL()).thenReturn(new StringBuffer(loginEndpoint));
Mockito.lenient().when(request.getContextPath()).thenReturn("/entando-app");


Expand All @@ -196,15 +196,12 @@ void testAuthenticationFlowWithError() {
Mockito.lenient().when(auth.getRefreshToken()).thenReturn("refresh-token-over-here");
try ( MockedStatic<WebApplicationContextUtils> wacUtil = Mockito.mockStatic(WebApplicationContextUtils.class)) {
wacUtil.when(() -> WebApplicationContextUtils.getWebApplicationContext(svCtx)).thenReturn(wac);
Assertions.assertThrows(EntandoTokenException.class, () -> {
keycloakFilter.doFilter(request, response, filterChain);
});
Assertions.assertThrows(EntandoTokenException.class, () -> keycloakFilter.doFilter(request, response, filterChain));
}
}

@Test
void testAuthenticationWithInvalidAuthCode() throws IOException, ServletException {
final String loginEndpoint = "https://dev.entando.org/entando-app/do/login";
final String state = "0ca97afd-f0b0-4860-820a-b7cd1414f69c";
final String authorizationCode = "the-authorization-code-from-keycloak";

Expand All @@ -216,7 +213,6 @@ void testAuthenticationWithInvalidAuthCode() throws IOException, ServletExceptio
when(request.getServletPath()).thenReturn("/do/login");
when(request.getParameter(eq("code"))).thenReturn(authorizationCode);
when(request.getParameter(eq("state"))).thenReturn(state);
when(request.getRequestURL()).thenReturn(new StringBuffer(loginEndpoint));
when(request.getContextPath()).thenReturn("/entando-app");

final HttpClientErrorException exception = Mockito.mock(HttpClientErrorException.class);
Expand All @@ -235,11 +231,9 @@ void testAuthenticationWithInvalidAuthCode() throws IOException, ServletExceptio
@Test
void shouldLoginWithInvalidRedirectURLHostnameNotThrowError() throws IOException, ServletException {
final String requestRedirect = "https://not.authorized.url";
final String loginEndpoint = "https://dev.entando.org/entando-app/do/login";

when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn("/do/login");
when(request.getRequestURL()).thenReturn(new StringBuffer(loginEndpoint));
Mockito.lenient().when(request.getParameter(eq("redirectTo"))).thenReturn(requestRedirect);

final String redirect = "http://dev.entando.org/auth/realms/entando/protocol/openid-connect/auth";
Expand All @@ -254,11 +248,8 @@ void shouldLoginWithInvalidRedirectURLHostnameNotThrowError() throws IOException

@Test
void testLogout() throws IOException, ServletException {
final String loginEndpoint = "https://dev.entando.org/entando-app/do/logout.action";

when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn("/do/logout.action");
when(request.getRequestURL()).thenReturn(new StringBuffer(loginEndpoint));

final String redirect = "http://dev.entando.org/auth/realms/entando/protocol/openid-connect/logout";
when(oidcService.getLogoutUrl(any(),any())).thenReturn(redirect);
Expand Down Expand Up @@ -437,11 +428,9 @@ void apiCallShouldNotSaveUserOnSession() throws Exception {
void testLoginWithAuthorizationCode() throws Exception {

final String path = "/do/login";
final String endpoint = "https://dev.entando.org/entando-app" + path;

when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn(path);
when(request.getRequestURL()).thenReturn(new StringBuffer(endpoint));
when(request.getParameter("state")).thenReturn("<state>");
when(request.getParameter("code")).thenReturn("<code>");

Expand Down Expand Up @@ -593,7 +582,6 @@ void shouldLoginWithRedirectToDifferentDomainNotThrowException() throws Exceptio
String path = "/do/login.action";
when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn(path);
when(request.getRequestURL()).thenReturn(new StringBuffer("http://dev.entando.org/entando-de-app/do/login.action"));
when(request.getParameter("code")).thenReturn(null);
when(request.getParameter("state")).thenReturn(null);
when(request.getParameter("redirectTo")).thenReturn("http://fakedomain.entando.org/entando-de-app/pages/en/homepage/");
Expand All @@ -616,15 +604,14 @@ void shouldLoginWithRedirectWithUrlExecuteFine() throws Exception {
final String contextRoot = "/entando-de-app";
final String protoAndServerName = "http://dev.entando.org";

testLoginExecuteFine(protoAndServerName, contextRoot, path, protoAndServerName+contextRoot+redirectPath, redirectPath);
testLoginExecuteFine(contextRoot, path, protoAndServerName+contextRoot+redirectPath, redirectPath);

}

private void testLoginExecuteFine(final String protoAndServerName, final String contextRoot, final String path,
private void testLoginExecuteFine(final String contextRoot, final String path,
final String redirectToUri, final String redirectToPath) throws Exception {
when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn(path);
when(request.getRequestURL()).thenReturn(new StringBuffer(protoAndServerName+contextRoot+path));
when(request.getParameter("code")).thenReturn(null);
when(request.getParameter("state")).thenReturn(null);
when(request.getParameter("redirectTo")).thenReturn(redirectToUri);
Expand All @@ -649,10 +636,8 @@ void shouldLoginWithRedirectToWithPathExecuteFine() throws Exception {
final String path = "/do/login.action";
final String redirectPath = "/pages/en/homepage/";
final String contextRoot = "/entando-de-app";
final String protoAndServerName = "http://dev.entando.org";

testLoginExecuteFine(protoAndServerName, contextRoot, path, contextRoot+redirectPath, redirectPath);

testLoginExecuteFine(contextRoot, path, contextRoot+redirectPath, redirectPath);
}

@Test
Expand All @@ -661,7 +646,6 @@ void shouldLoginWithRedirectToSameDomainWithDifferentSchemaExecuteFine() throws
String path = "/do/login.action";
when(configuration.isEnabled()).thenReturn(true);
when(request.getServletPath()).thenReturn(path);
when(request.getRequestURL()).thenReturn(new StringBuffer("http://dev.entando.org/entando-de-app/do/login.action"));
when(request.getParameter("code")).thenReturn(null);
when(request.getParameter("state")).thenReturn(null);
when(request.getParameter("redirectTo")).thenReturn("https://dev.entando.org/entando-de-app/pages/en/mypage");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private String validatedParameter(String name) {
try {
value = ESAPI.validator().getValidInput("HTTP parameter value: " + value, value, "HTTPParameterValue", 2000, true);
} catch (ValidationException e) {
log.error("Invalid parameter ('{}' - '{}'), encoding as HTML attribute", name, value, e);
log.warn("Invalid parameter ('{}' - '{}'), encoding as HTML attribute", name, value);
value = ESAPI.encoder().encodeForHTMLAttribute(value);
} catch (Throwable e) {
log.debug("Invalid parameter ('{}' - '{}') - error message {}", name, value, e.getMessage());
Expand All @@ -78,7 +78,7 @@ private String validatedHeader(String name) {
try {
value = ESAPI.validator().getValidInput("HTTP header value: " + value, value, "HTTPHeaderValue", 150, false);
} catch (ValidationException e) {
log.error("Invalid header ('{}' - '{}'), encoding as HTML attribute", name, value, e);
log.warn("Invalid header ('{}' - '{}'), encoding as HTML attribute", name, value);
value = ESAPI.encoder().encodeForHTMLAttribute(value);
} catch (Throwable e) {
log.debug("Invalid header ('{}' - '{}') - error message {}", name, value, e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<column name="titles" valueClobFile="clob/production/widgetcatalog/message_form_title.xml"/>
<column name="parameters" valueClobFile="clob/production/widgetcatalog/message_form_parameters.xml"/>
<column name="plugincode" value="jpwebdynamicform"/>
<column name="configui" value='{"customElement":"LEGACY_CONFIG","resources":[]}'/>
<column name="locked" valueNumeric="1"/>
</insert>

Expand All @@ -56,7 +57,6 @@
<column name="parenttypecode" value="formAction"/>
<column name="defaultconfig"
valueClobFile="clob/production/widgetcatalog/message_choice_defaultconfig.xml"/>
<column name="configui" value='{"customElement":"LEGACY_CONFIG","resources":[]}'/>
<column name="locked" valueNumeric="1"/>
</insert>

Expand Down Expand Up @@ -451,4 +451,25 @@

</changeSet>

<changeSet id="00000000000001_jpwebdynamicform_dataPort_guifragments_dateSubmitHandler" author="entando" context="production">

<preConditions onFail="MARK_RAN">
<!-- Use uppercase table name to avoid MySQL issue -->
<sqlCheck expectedResult="0">
SELECT COUNT(*)
FROM GUIFRAGMENT
WHERE code = 'jpwebdynform_is_front-DateSubmitHandler'
</sqlCheck>
</preConditions>

<insert tableName="guifragment">
<column name="code" value="jpwebdynform_is_front-DateSubmitHandler"/>
<column name="plugincode" value="jpwebdynamicform"/>
<column name="defaultgui"
valueClobFile="clob/production/guifragment/jpwebdynform_is_front-DateSubmitHandler.ftl"/>
<column name="locked" valueNumeric="1"/>
</insert>

</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,5 @@
useTabindexAutoIncrement=true
value="%{#attr.labelSubmit}" />
</p>
</form>
</form>
<@wp.fragment code="jpwebdynform_is_front-DateSubmitHandler" escapeXml=false />
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
<#assign wp=JspTaglibs["/aps-core"]>
<#--
jpwebdynamicform_message_form - Message Form Widget
====================================================
This widget renders a dynamic message form via internalServlet.
The Struts action dispatches to GUI fragments (stored in DB, tenant-aware)
with JSP fallback.

Struts action results GUI fragments (see userMessage.xml):
- jpwebdynamicform_is_entryMessage : Main form with all attribute fields
- jpwebdynamicform_is_messageTypeFinding : Message type selection (if multiple types)
- jpwebdynamicform_is_captchaPage : CAPTCHA confirmation step
- jpwebdynamicform_is_messageSaveConfirmed : Success confirmation page

Attribute type fragments (customizable per-tenant via DB):
- jpwebdynform_is_front-BooleanAttribute : Boolean (Yes/No radio buttons)
- jpwebdynform_is_front-CheckboxAttribute : CheckBox (single checkbox)
- jpwebdynform_is_front-DateAttribute : Date (native HTML5 date input)
- jpwebdynform_is_front-EnumeratorAttribute : Enumerator (select dropdown)
- jpwebdynform_is_front-EnumeratorMapAttribute : EnumeratorMap (select with key/value)
- jpwebdynform_is_front-LongtextAttribute : Longtext (textarea)
- jpwebdynform_is_front-NumberAttribute : Number (text input)
- jpwebdynform_is_front-MonotextAttribute : Monotext/Text (text input, also default fallback)
- jpwebdynform_is_front-ThreeStateAttribute : ThreeState (Yes/No/None radio buttons)
- jpwebdynform_is_front-CompositeAttribute : Composite (group of sub-attributes)

Utility fragments:
- jpwebdynform_is_front_AttributeInfo : Required field indicator (*)
- jpwebdynform_is_front_attributeInfo-help-block : Validation hints (min/max length, OGNL help)
- jpwebdynform_is_front-DateSubmitHandler : Date format conversion (yyyy-MM-dd -> dd/MM/yyyy)
- jpwebdynamicform_is_captchaInclude : Google reCAPTCHA v2/v3 integration
-->
<@wp.internalServlet actionPath="/ExtStr2/do/jpwebdynamicform/Message/User/new" />
Loading
Loading