/** * Handle a form submission. GET and POST requests behave identically. * Populates the form with {@link loadDataFrom()}, calls {@link validate()}, * and only triggers the requested form action/method * if the form is valid. * * @param SS_HTTPRequest $request * @throws SS_HTTPResponse_Exception */ public function httpSubmission($request) { // Strict method check if ($this->strictFormMethodCheck) { // Throws an error if the method is bad... if ($this->formMethod != $request->httpMethod()) { $response = Controller::curr()->getResponse(); $response->addHeader('Allow', $this->formMethod); $this->httpError(405, _t("Form.BAD_METHOD", "This form requires a " . $this->formMethod . " submission")); } // ...and only uses the variables corresponding to that method type $vars = $this->formMethod == 'GET' ? $request->getVars() : $request->postVars(); } else { $vars = $request->requestVars(); } // Populate the form $this->loadDataFrom($vars, true); // Protection against CSRF attacks $token = $this->getSecurityToken(); if (!$token->checkRequest($request)) { $securityID = $token->getName(); if (empty($vars[$securityID])) { $this->httpError(400, _t("Form.CSRF_FAILED_MESSAGE", "There seems to have been a technical problem. Please click the back button, " . "refresh your browser, and try again.")); } else { // Clear invalid token on refresh $data = $this->getData(); unset($data[$securityID]); Session::set("FormInfo.{$this->FormName()}.data", $data); Session::set("FormInfo.{$this->FormName()}.errors", array()); $this->sessionMessage(_t("Form.CSRF_EXPIRED_MESSAGE", "Your session has expired. Please re-submit the form."), "warning"); return $this->controller->redirectBack(); } } // Determine the action button clicked $funcName = null; foreach ($vars as $paramName => $paramVal) { if (substr($paramName, 0, 7) == 'action_') { // Break off querystring arguments included in the action if (strpos($paramName, '?') !== false) { list($paramName, $paramVars) = explode('?', $paramName, 2); $newRequestParams = array(); parse_str($paramVars, $newRequestParams); $vars = array_merge((array) $vars, (array) $newRequestParams); } // Cleanup action_, _x and _y from image fields $funcName = preg_replace(array('/^action_/', '/_x$|_y$/'), '', $paramName); break; } } // If the action wasn't set, choose the default on the form. if (!isset($funcName) && ($defaultAction = $this->defaultAction())) { $funcName = $defaultAction->actionName(); } if (isset($funcName)) { Form::set_current_action($funcName); $this->setButtonClicked($funcName); } // Permission checks (first on controller, then falling back to form) if ($this->controller->hasMethod($funcName) && !$this->controller->checkAccessAction($funcName) && !$this->actions->dataFieldByName('action_' . $funcName)) { return $this->httpError(403, sprintf('Action "%s" not allowed on controller (Class: %s)', $funcName, get_class($this->controller))); } elseif ($this->hasMethod($funcName) && !$this->checkAccessAction($funcName)) { return $this->httpError(403, sprintf('Action "%s" not allowed on form (Name: "%s")', $funcName, $this->name)); } // TODO : Once we switch to a stricter policy regarding allowed_actions (meaning actions must be set // explicitly in allowed_actions in order to run) // Uncomment the following for checking security against running actions on form fields /* else { // Try to find a field that has the action, and allows it $fieldsHaveMethod = false; foreach ($this->Fields() as $field){ if ($field->hasMethod($funcName) && $field->checkAccessAction($funcName)) { $fieldsHaveMethod = true; } } if (!$fieldsHaveMethod) { return $this->httpError( 403, sprintf('Action "%s" not allowed on any fields of form (Name: "%s")', $funcName, $this->Name()) ); } }*/ // Validate the form if (!$this->validate()) { return $this->getValidationErrorResponse(); } // First, try a handler method on the controller (has been checked for allowed_actions above already) if ($this->controller->hasMethod($funcName)) { return $this->controller->{$funcName}($vars, $this, $request); // Otherwise, try a handler method on the form object. } elseif ($this->hasMethod($funcName)) { return $this->{$funcName}($vars, $this, $request); } elseif ($field = $this->checkFieldsForAction($this->Fields(), $funcName)) { return $field->{$funcName}($vars, $this, $request); } return $this->httpError(404); }
/** * Checks method and action for request * RequestHandler.php -> handleRequest() * @param string $action * @return boolean */ public function checkAccessAction($action) { $method = $this->request->httpMethod(); $apiActions = Config::inst()->get(get_class($this), 'api_allowed_actions'); $isAllowed = false; if ($apiActions === null) { // all actions + methods are allowed, use default check return parent::checkAccessAction($action); } else { if ($apiActions === true) { return true; } } if (is_array($apiActions)) { foreach ($apiActions as $apiAction => $permission) { preg_match("/^(.+?):(.+)\$/", $apiAction, $matches); if (!isset($matches[1]) || !isset($matches[2])) { return user_error("Ensure that `api_allowed_actions` fulfills the following pattern: `\$method:\$action` => `\$permission`"); } $allowedMethod = strtoupper($matches[1]); $allowedAction = $matches[2]; if (($allowedMethod === $method || $allowedMethod === "*") && $allowedAction === $action) { if ($permission === true) { // wildcard $isAllowed = true; } else { if ($permission === '->') { $isAllowed = true; } else { if (substr($permission, 0, 2) === '->') { // use method $permissionMethod = substr($permission, 2); if (!$this->hasMethod($permissionMethod)) { return user_error("Permission method `{$permissionMethod}` doesn't exists on `{$this->class}`"); } else { $isAllowed = $this->{$permissionMethod}(); } } else { $isAllowed = Permission::check($permission); } } } } } } return $isAllowed; }