Skip to content

Commit

Permalink
Merge pull request #63 from prime-framework/degroff/configure_alterna…
Browse files Browse the repository at this point in the history
…te_action

Allow configuration to enable or disable alternate actions URI
  • Loading branch information
robotdan authored Nov 19, 2024
2 parents d552f44 + 48f5271 commit 7030013
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 34 deletions.
2 changes: 1 addition & 1 deletion build.savant
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ logbackVersion = "1.4.14"
slf4jVersion = "2.0.13"
testngVersion = "7.8.0"

project(group: "org.primeframework", name: "prime-mvc", version: "4.26.1", licenses: ["ApacheV2_0"]) {
project(group: "org.primeframework", name: "prime-mvc", version: "4.27.0", licenses: ["ApacheV2_0"]) {
workflow {
fetch {
// Dependency resolution order:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.primeframework</groupId>
<artifactId>prime-mvc</artifactId>
<version>4.26.1</version>
<version>4.27.0</version>
<packaging>jar</packaging>

<name>FusionAuth App</name>
Expand Down
23 changes: 12 additions & 11 deletions prime-mvc.ipr
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypes">
<set>
<option value="java.lang.String" />
<option value="java.util.List" />
</set>
</option>
Expand Down Expand Up @@ -544,7 +545,7 @@
<fileSet type="namedScope" name="control-templates" pattern="file[prime-mvc]:src/main/ftl/WEB-INF/control-templates/*" />
</list>
</option>
<DB2CodeStyleSettings version="6">
<DB2CodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -584,7 +585,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</DB2CodeStyleSettings>
<DerbyCodeStyleSettings version="6">
<DerbyCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -628,7 +629,7 @@
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
</GroovyCodeStyleSettings>
<H2CodeStyleSettings version="6">
<H2CodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -668,7 +669,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</H2CodeStyleSettings>
<HSQLCodeStyleSettings version="6">
<HSQLCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -732,7 +733,7 @@
<option name="JD_PRESERVE_LINE_FEEDS" value="true" />
<option name="JD_INDENT_ON_CONTINUATION" value="true" />
</JavaCodeStyleSettings>
<MSSQLCodeStyleSettings version="6">
<MSSQLCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -775,7 +776,7 @@
<MarkdownNavigatorCodeStyleSettings>
<option name="WRAP_ON_TYPING" value="0" />
</MarkdownNavigatorCodeStyleSettings>
<MySQLCodeStyleSettings version="6">
<MySQLCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -815,7 +816,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</MySQLCodeStyleSettings>
<OracleCodeStyleSettings version="6">
<OracleCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -861,7 +862,7 @@
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
</PHPCodeStyleSettings>
<PostgresCodeStyleSettings version="6">
<PostgresCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -904,7 +905,7 @@
<Properties>
<option name="KEEP_BLANK_LINES" value="true" />
</Properties>
<SQLiteCodeStyleSettings version="6">
<SQLiteCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -947,7 +948,7 @@
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
<SqlCodeStyleSettings version="6">
<SqlCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down Expand Up @@ -1012,7 +1013,7 @@
<option name="WRAP_PARENTHESIZED_EXPRESSION_INSIDE_VALUES" value="0" />
<option name="NEW_LINE_AFTER_SELECT_ITEM" value="false" />
</SqlCodeStyleSettings>
<SybaseCodeStyleSettings version="6">
<SybaseCodeStyleSettings version="7">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2001-2023, Inversoft Inc., All Rights Reserved
* Copyright (c) 2001-2024, Inversoft Inc., All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,7 @@
import io.fusionauth.http.server.HTTPRequest;
import io.fusionauth.http.server.HTTPResponse;
import org.primeframework.mvc.NotAllowedException;
import org.primeframework.mvc.config.MVCConfiguration;
import org.primeframework.mvc.http.HTTPTools;
import org.primeframework.mvc.http.Status;
import org.primeframework.mvc.parameter.DefaultParameterParser;
Expand All @@ -47,6 +48,8 @@ public class DefaultActionMappingWorkflow implements ActionMappingWorkflow {

private final ActionMapper actionMapper;

private final MVCConfiguration configuration;

private final HTTPRequest request;

private final HTTPResponse response;
Expand All @@ -55,11 +58,12 @@ public class DefaultActionMappingWorkflow implements ActionMappingWorkflow {

@Inject
public DefaultActionMappingWorkflow(HTTPRequest request, HTTPResponse response, ActionInvocationStore actionInvocationStore,
ActionMapper actionMapper) {
ActionMapper actionMapper, MVCConfiguration configuration) {
this.request = request;
this.response = response;
this.actionInvocationStore = actionInvocationStore;
this.actionMapper = actionMapper;
this.configuration = configuration;
}

/**
Expand Down Expand Up @@ -143,20 +147,24 @@ public void perform(WorkflowChain chain) throws IOException {

private String determineURI() {
String uri = null;
Set<String> keys = request.getParameters().keySet();
for (String key : keys) {
if (key.startsWith(DefaultParameterParser.ACTION_PREFIX)) {
String actionParameterName = key.substring(4);
String actionParameterValue = request.getParameter(key);
if (request.getParameter(actionParameterName) != null && actionParameterValue.trim().length() > 0) {
uri = actionParameterValue;

// Handle relative URIs
if (!uri.startsWith("/")) {
String requestURI = HTTPTools.getRequestURI(request);
int index = requestURI.lastIndexOf('/');
if (index >= 0) {
uri = requestURI.substring(0, index) + "/" + uri;

if (configuration.allowActionParameterDuringActionMappingWorkflow()) {
Set<String> keys = request.getParameters().keySet();
for (String key : keys) {
if (key.startsWith(DefaultParameterParser.ACTION_PREFIX)) {

String actionParameterName = key.substring(4);
String actionParameterValue = request.getParameter(key);
if (request.getParameter(actionParameterName) != null && actionParameterValue.trim().length() > 0) {
uri = actionParameterValue;

// Handle relative URIs
if (!uri.startsWith("/")) {
String requestURI = HTTPTools.getRequestURI(request);
int index = requestURI.lastIndexOf('/');
if (index >= 0) {
uri = requestURI.substring(0, index) + "/" + uri;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023, Inversoft Inc., All Rights Reserved
* Copyright (c) 2012-2024, Inversoft Inc., All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,6 +40,8 @@ public abstract class AbstractMVCConfiguration implements MVCConfiguration {

public static final long MAX_SIZE = 1024000;

public boolean allowActionParameterDuringActionMappingWorkflow = true;

public boolean autoHTMLEscapingEnabled = true;

public String controlTemplateDirectory = "control-templates";
Expand Down Expand Up @@ -76,6 +78,11 @@ public abstract class AbstractMVCConfiguration implements MVCConfiguration {

public List<Class<? extends Annotation>> unwrapAnnotations = Collections.singletonList(FieldUnwrapped.class);

@Override
public boolean allowActionParameterDuringActionMappingWorkflow() {
return allowActionParameterDuringActionMappingWorkflow;
}

@Override
public boolean autoHTMLEscapingEnabled() {
return autoHTMLEscapingEnabled;
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/org/primeframework/mvc/config/MVCConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2023, Inversoft Inc., All Rights Reserved
* Copyright (c) 2012-2024, Inversoft Inc., All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@
import java.util.Set;

import io.fusionauth.http.Cookie.SameSite;
import org.primeframework.mvc.parameter.DefaultParameterParser;
import org.primeframework.mvc.parameter.el.ExpressionEvaluator;

/**
Expand All @@ -31,6 +32,13 @@
* @author Brian Pontarelli
*/
public interface MVCConfiguration {
/**
* In most cases you should disable this feature. While it may be useful, modifying the URI may have un-intended consequences.
*
* @return true if alternate actions can be specified by using the {@link DefaultParameterParser#ACTION_PREFIX} prefix.
*/
boolean allowActionParameterDuringActionMappingWorkflow();

/**
* @return true if unknown parameters should be allowed, false if they are not allowed.
*/
Expand Down Expand Up @@ -135,7 +143,7 @@ public interface MVCConfiguration {

/**
* @return The number of seconds to check for Freemarker template updates (max integer means never and 0 means
* always).
* always).
*/
int templateCheckSeconds();

Expand All @@ -146,7 +154,7 @@ public interface MVCConfiguration {

/**
* @return The annotations that identify a field to be un-wrapped - or be considered transparent by the
* {@link ExpressionEvaluator}.
* {@link ExpressionEvaluator}.
*/
List<Class<? extends Annotation>> unwrapAnnotations();
}
26 changes: 26 additions & 0 deletions src/test/java/org/primeframework/mvc/GlobalTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.FileAssert.fail;

/**
Expand Down Expand Up @@ -1176,6 +1177,31 @@ public void get_unknownParameters() throws Exception {
));
}

@Test
public void get_url_rewrite() {
simulator.test("/doesNotExist?__a_foo=/user/edit&foo=true")
.get()
.assertStatusCode(200)
.assertContainsNoFieldMessages()
.assertBodyContains("""
<head><title>Edit a user</title></head>
""");
assertTrue(EditAction.getCalled);

// Disabled
configuration.allowActionParameterDuringActionMappingWorkflow = false;

// Reset
EditAction.getCalled = false;

simulator.test("/doesNotExist?__a_foo=/user/edit&foo=true")
.get()
.assertStatusCode(404)
.assertContainsNoFieldMessages()
.assertBodyContains("The page is missing!");
assertFalse(EditAction.getCalled);
}

@Test
public void get_wellKnownDotPrefixed() throws Exception {
test.simulate(() -> simulator.test("/.well-known/openid-configuration")
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/org/primeframework/mvc/PrimeBaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,19 @@ public void beforeMethod() {
// Reset allowUnknownParameters
configuration.allowUnknownParameters = false;

// Reset to default
configuration.allowActionParameterDuringActionMappingWorkflow = true;

// Reset the call count on the invocation finalizer
MockMVCWorkflowFinalizer.Called.set(0);

// Reset accumulating logger, using TRACE for now to debug some tests
((TestAccumulatingLogger) TestAccumulatingLoggerFactory.FACTORY.getLogger(PrimeBaseTest.class)).reset();
TestAccumulatingLoggerFactory.FACTORY.getLogger(PrimeBaseTest.class).setLevel(Level.Trace);

// Reset
EditAction.getCalled = false;

TestUnhandledExceptionHandler.reset();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,25 @@ public void differentButtonClick() throws Exception {
run("/admin/user/cancel", "/admin/user/cancel", null);
}

@Test
public void differentButtonClick_notAllowed() throws Exception {
// Disable action mapping using action parameter __a_
configuration.allowActionParameterDuringActionMappingWorkflow = false;

request.setPath("/admin/user/edit");
request.setMethod(HTTPMethod.POST);
request.addURLParameter("__a_submit", "");
request.addURLParameter("__a_cancel", "/admin/user/cancel");
request.addURLParameter("cancel", "Cancel");

run("/admin/user/edit", "/admin/user/edit", null);
}

@Test
public void differentButtonClickRelativeURI() throws Exception {
// enable alternate form actions
configuration.allowActionParameterDuringActionMappingWorkflow = true;

request.setPath("/admin/user/edit");
request.setMethod(HTTPMethod.POST);
request.addURLParameter("__a_submit", "");
Expand Down Expand Up @@ -126,7 +143,7 @@ private void run(String fullURI, String uri, String extension) throws Exception
chain.continueWorkflow();
EasyMock.replay(chain);

DefaultActionMappingWorkflow workflow = new DefaultActionMappingWorkflow(request, response, store, new DefaultActionMapper(provider, injector));
DefaultActionMappingWorkflow workflow = new DefaultActionMappingWorkflow(request, response, store, new DefaultActionMapper(provider, injector), configuration);
workflow.perform(chain);

ActionInvocation ai = capture.getValue();
Expand Down

0 comments on commit 7030013

Please sign in to comment.