Property changes on: . ___________________________________________________________________ Name: svn:ignore - dist target classes test-classes etc + dist target classes test-classes etc doc tmp Index: test/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClassTests.java =================================================================== --- test/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClassTests.java (revision 1582) +++ test/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClassTests.java (working copy) @@ -121,4 +121,31 @@ assertFalse(grailsClass.isInterceptedAfter(controller,"list")); assertTrue(grailsClass.isInterceptedAfter(controller,"save")); } + + public void testHttpMethodRestrictions() throws Exception { + GroovyClassLoader cl = new GroovyClassLoader(); + Class clazz = cl.parseClass("class TestController { \n" + + "def httpMethodRestrictions = [actionTwo:'POST', actionThree:['POST', 'PUT']]\n" + + "def actionOne = { return 'test' }\n " + + "def actionTwo = { return 'test' }\n " + + "def actionThree = { return 'test' }\n " + + "} "); + GrailsControllerClass grailsClass = new DefaultGrailsControllerClass(clazz); + GroovyObject controller = (GroovyObject)grailsClass.newInstance(); + + assertFalse("actionTwo should not have accepted a GET", grailsClass.isHttpMethodAllowedForAction(controller, "GET", "actionTwo")); + assertFalse("actionTwo should not have accepted a PUT", grailsClass.isHttpMethodAllowedForAction(controller, "PUT", "actionTwo")); + assertFalse("actionTwo should not have accepted a DELETE", grailsClass.isHttpMethodAllowedForAction(controller, "DELETE", "actionTwo")); + assertTrue("actionTwo should have accepted a POST", grailsClass.isHttpMethodAllowedForAction(controller, "POST", "actionTwo")); + + assertFalse("actionThree should not have accepted a GET", grailsClass.isHttpMethodAllowedForAction(controller, "GET", "actionThree")); + assertTrue("actionThree should have accepted a PUT", grailsClass.isHttpMethodAllowedForAction(controller, "PUT", "actionThree")); + assertFalse("actionThree should not have accepted a DELETE", grailsClass.isHttpMethodAllowedForAction(controller, "DELETE", "actionThree")); + assertTrue("actionThree should have accepted a POST", grailsClass.isHttpMethodAllowedForAction(controller, "POST", "actionThree")); + + assertTrue("actionOne should have accepted a GET", grailsClass.isHttpMethodAllowedForAction(controller, "GET", "actionOne")); + assertTrue("actionOne should have accepted a PUT", grailsClass.isHttpMethodAllowedForAction(controller, "PUT", "actionOne")); + assertTrue("actionOne should have accepted a DELETE", grailsClass.isHttpMethodAllowedForAction(controller, "DELETE", "actionOne")); + assertTrue("actionOne should have accepted a POST", grailsClass.isHttpMethodAllowedForAction(controller, "POST", "actionOne")); + } } Index: test/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerTests.java =================================================================== --- test/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerTests.java (revision 1582) +++ test/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerTests.java (working copy) @@ -20,6 +20,8 @@ import java.util.Iterator; import java.util.Properties; +import javax.servlet.http.HttpServletResponse; + import junit.framework.TestCase; import org.codehaus.groovy.grails.commons.DefaultGrailsApplication; @@ -80,6 +82,13 @@ "}\n" + "}"); + Class c4 = cl.parseClass("class RestrictedController {\n"+ + "def httpMethodRestrictions=[action1:'POST', action3:['PUT', 'DELETE']]\n" + + "def action1 = {}\n" + + "def action2 = {}\n" + + "def action3 = {}\n" + + "}"); + // this.grailsApplication = new DefaultGrailsApplication(new Class[]{c1,c2,c3},cl); // this.controller = new SimpleGrailsController(); // this.controller.setGrailsApplication(grailsApplication); @@ -90,7 +99,7 @@ this.localContext = new GenericApplicationContext(); ConstructorArgumentValues args = new ConstructorArgumentValues(); - args.addGenericArgumentValue(new Class[]{c1,c2,c3}); + args.addGenericArgumentValue(new Class[]{c1,c2,c3,c4}); args.addGenericArgumentValue(cl); MutablePropertyValues propValues = new MutablePropertyValues(); @@ -115,9 +124,12 @@ super.setUp(); } - private ModelAndView execute(String uri, Properties parameters) throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", uri); + return execute(uri, parameters, "GET", new MockHttpServletResponse()); + } + + private ModelAndView execute(String uri, Properties parameters, String requestMethod, HttpServletResponse response) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(requestMethod, uri); request.setContextPath("/simple"); if (parameters != null) { @@ -127,14 +139,34 @@ request.addParameter(paramName, paramValue); } } - MockHttpServletResponse response = new MockHttpServletResponse(); return controller.handleRequest(request, response); } - public void testSimpleControllerSuccess() throws Exception { ModelAndView modelAndView = execute("/test/test", null); assertNotNull(modelAndView); } + + public void testRestrictedControllerSuccess() throws Exception { + assertResponseStatusCode("/restricted/action1", "GET", HttpServletResponse.SC_FORBIDDEN); + assertResponseStatusCode("/restricted/action1", "PUT", HttpServletResponse.SC_FORBIDDEN); + assertResponseStatusCode("/restricted/action1", "POST", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action1", "DELETE", HttpServletResponse.SC_FORBIDDEN); + + assertResponseStatusCode("/restricted/action2", "GET", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action2", "PUT", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action2", "POST", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action2", "DELETE", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action3", "GET", HttpServletResponse.SC_FORBIDDEN); + assertResponseStatusCode("/restricted/action3", "PUT", HttpServletResponse.SC_OK); + assertResponseStatusCode("/restricted/action3", "POST", HttpServletResponse.SC_FORBIDDEN); + assertResponseStatusCode("/restricted/action3", "DELETE", HttpServletResponse.SC_OK); + } + + private void assertResponseStatusCode(String uri, String httpMethod, int expectedStatusCode) throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + execute(uri, null, httpMethod, response); + assertEquals(expectedStatusCode, response.getStatus()); + } } Index: src/commons/org/codehaus/groovy/grails/commons/GrailsControllerClass.java =================================================================== --- src/commons/org/codehaus/groovy/grails/commons/GrailsControllerClass.java (revision 1582) +++ src/commons/org/codehaus/groovy/grails/commons/GrailsControllerClass.java (working copy) @@ -42,6 +42,17 @@ public String AFTER_INTERCEPTOR = "afterInterceptor"; /** + * Checks to see if an action is accessible via a particular + * http method + * + * @param controller The instance of the controller + * @param httpMethod The http request method + * @param actionName The action to check + * @return true if the action is accessible via the specified http method + */ + boolean isHttpMethodAllowedForAction(GroovyObject controller, String httpMethod, String actionName); + + /** * Checks whether the specified action is intercepted for the * specified controller instance * Index: src/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClass.java =================================================================== --- src/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClass.java (revision 1582) +++ src/commons/org/codehaus/groovy/grails/commons/DefaultGrailsControllerClass.java (working copy) @@ -41,6 +41,7 @@ private static final String VIEW = "View"; private static final String DEFAULT_CLOSURE_PROPERTY = "defaultAction"; private static final String SCAFFOLDING_PROPERTY = "scaffold"; + private static final String METHOD_RESTRICTIONS_PROPERTY = "httpMethodRestrictions"; private static final String EXCEPT = "except"; private static final String ONLY = "only"; @@ -238,6 +239,27 @@ return false; } + public boolean isHttpMethodAllowedForAction(GroovyObject controller, String httpMethod, String actionName) { + boolean isAllowed = true; + try { + Object methodRestrictionsProperty = controller.getProperty(METHOD_RESTRICTIONS_PROPERTY); + if(methodRestrictionsProperty instanceof Map) { + Map map = (Map)methodRestrictionsProperty; + if(map.containsKey(actionName)) { + Object value = map.get(actionName); + if(value instanceof List) { + List listOfMethods = (List) value; + isAllowed = listOfMethods.contains(httpMethod); + } else if(value instanceof String) { + isAllowed = value.equals(httpMethod); + } + } + } + } catch (MissingPropertyException mpe) { + } + return isAllowed; + } + public boolean isInterceptedAfter(GroovyObject controller, String action) { try { return isIntercepted(controller.getProperty(AFTER_INTERCEPTOR),action); Index: src/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerHelper.java =================================================================== --- src/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerHelper.java (revision 1582) +++ src/web/org/codehaus/groovy/grails/web/servlet/mvc/SimpleGrailsControllerHelper.java (working copy) @@ -220,6 +220,15 @@ // Step 3: load controller from application context. GroovyObject controller = getControllerInstance(controllerClass); + if(!controllerClass.isHttpMethodAllowedForAction(controller, request.getMethod(), actionName)) { + try { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return null; + } catch (IOException e) { + throw new ControllerExecutionException("I/O error sending 403 error",e); + } + } + request.setAttribute( GrailsApplicationAttributes.CONTROLLER, controller ); // Step 3a: Configure a proxy interceptor for controller dynamic methods for this request