/** * {@inheritdoc} */ public function isAllowed($role, $resource, $privilege) { if ($role instanceof IRole) { $role = $role->getRoleId(); } if (!$resource instanceof PresenterResource) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Resource must be instance of %s, %s given.', PresenterResource::class, gettype($resource))); } $request = $resource->getRequest(); $presenterName = $request->getPresenterName(); list($signal, $signalReceiver) = $this->getSignal($request); if (!$signal) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('No signal sent.')); } $refClass = new PresenterComponentReflection($class = $this->presenterFactory->getPresenterClass($presenterName)); while ($name = array_shift($signalReceiver)) { $name = 'createComponent' . ucfirst($name); if (!$refClass->hasMethod($name)) { throw new \Nette\InvalidStateException(sprintf('Method %s::%s is not implemented.', $refClass->getName(), $name)); } $refMethod = $refClass->getMethod($name); if (!$refMethod->hasAnnotation('return')) { throw new \Nette\InvalidStateException(sprintf('Method %s::%s must have fully qualified return annotation.', $refClass->getName(), $name)); } $refClass = new ClassType($refMethod->getAnnotation('return')); } if (!$refClass->hasMethod($name = Presenter::formatSignalMethod($signal))) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Method %s::%s is not implemented.', $refClass->getName(), $name)); } $refMethod = $refClass->getMethod($name); if (!$refMethod->hasAnnotation($privilege)) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Method %s::%s does not have annotation %s.', $refClass->getName(), $name, $privilege)); } return in_array($role, preg_split('#\\s+#', trim((string) $refMethod->getAnnotation($privilege)))); }
/** * @param string */ public function signalReceived($signal) { $methodName = sprintf('handle%s', \Nette\Utils\Strings::firstUpper($signal)); if (!method_exists($this, $methodName)) { throw new \Nette\Application\UI\BadSignalException(sprintf('Method %s does not exist', $methodName)); } $presenterComponentReflection = new PresenterComponentReflection(get_called_class()); $methodReflection = $presenterComponentReflection->getMethod($methodName); $args = $presenterComponentReflection->combineArgs($methodReflection, $this->params); $methodReflection->invokeArgs($this, $args); }
/** * @param App\Request $request * @return App\IResponse */ public function run(App\Request $request) { $this->request = $request; $this->startup(); if (!$this->startupCheck) { $class = (new \ReflectionClass($this))->getMethod('startup')->getDeclaringClass()->getName(); throw new Nette\InvalidStateException("'{$class}::startup()' or its descendant does not call parent method"); } try { $rm = new \ReflectionMethod($this, $this->getAction()); } catch (\ReflectionException $e) { } if (isset($e) || $rm->isAbstract() || $rm->isStatic() || !$rm->isPublic()) { throw new App\BadRequestException("Method '{$request->getMethod()}' not allowed", 405); } $params = $this->getParameters(); $args = App\UI\PresenterComponentReflection::combineArgs($rm, $params); $response = $rm->invokeArgs($this, $args); if ($response === null) { $response = new Responses\NullResponse(); } elseif (!$response instanceof App\IResponse) { throw new Nette\InvalidStateException("Action '{$this->getAction(true)}' does not return instance of Nette\\Application\\IResponse"); } return $response; }
/** * Saves state informations for next request. * * @param array * @param PresenterComponentReflection (internal, used by Presenter) * * @return void */ public function saveState(array &$params, $reflection = null) { $reflection = $reflection === null ? $this->getReflection() : $reflection; foreach ($reflection->getPersistentParams() as $name => $meta) { if (isset($params[$name])) { // injected value } elseif (array_key_exists($name, $params)) { // NULLs are skipped continue; } elseif (!isset($meta['since']) || $this instanceof $meta['since']) { $params[$name] = $this->{$name}; // object property value } else { continue; // ignored parameter } $type = gettype($meta['def'] === null ? $params[$name] : $meta['def']); // compatible with 2.0.x if (!PresenterComponentReflection::convertType($params[$name], $type)) { throw new InvalidLinkException("Invalid value for persistent parameter '{$name}' in '{$this->getName()}', expected " . ($type === 'NULL' ? 'scalar' : $type) . "."); } if ($params[$name] === $meta['def'] || $meta['def'] === null && is_scalar($params[$name]) && (string) $params[$name] === '') { $params[$name] = null; // value transmit is unnecessary } } }
/** * @return Nette\Application\IResponse */ public function run(Application\Request $request) { $this->request = $request; $httpRequest = $this->context->getByType('Nette\\Http\\IRequest'); if (!$httpRequest->isAjax() && ($request->isMethod('get') || $request->isMethod('head'))) { $refUrl = clone $httpRequest->getUrl(); $url = $this->context->getService('router')->constructUrl($request, $refUrl->setPath($refUrl->getScriptPath())); if ($url !== NULL && !$httpRequest->getUrl()->isEqual($url)) { return new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY); } } $params = $request->getParameters(); if (!isset($params['callback'])) { throw new Application\BadRequestException("Parameter callback is missing."); } $params['presenter'] = $this; $callback = new Nette\Callback($params['callback']); $response = $callback->invokeArgs(Application\UI\PresenterComponentReflection::combineArgs($callback->toReflection(), $params)); if (is_string($response)) { $response = array($response, array()); } if (is_array($response)) { if ($response[0] instanceof \SplFileInfo) { $response = $this->createTemplate('Nette\\Templating\\FileTemplate')->setParameters($response[1])->setFile($response[0]); } else { $response = $this->createTemplate('Nette\\Templating\\Template')->setParameters($response[1])->setSource($response[0]); } } if ($response instanceof Nette\Templating\ITemplate) { return new Responses\TextResponse($response); } else { return $response; } }
/** * This allows me to implement a basic access control for presenters. * * This method is called for every presenter run, * once it's created before the presenter startup, * and for every other lifecycle methods, like render, action and signals. */ public function checkRequirements($element) { $user = PresenterComponentReflection::parseAnnotation($element, 'User'); if ($user === FALSE) { return; // not protected } if (!$this->getUser()->isLoggedIn()) { $this->forbiddenAccess(); } }
/** * {@inheritdoc} */ public function isAllowed($role, $resource, $privilege) { if ($role instanceof IRole) { $role = $role->getRoleId(); } if (!$resource instanceof PresenterResource) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Resource must be instance of %s, %s given.', PresenterResource::class, gettype($resource))); } $request = $resource->getRequest(); $presenterName = $request->getPresenterName(); $refClass = new PresenterComponentReflection($class = $this->presenterFactory->getPresenterClass($presenterName)); if (!$refClass->hasMethod($name = Presenter::formatRenderMethod($this->getRenderName($request)))) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Method %s::%s is not implemented.', $class, $name)); } $refMethod = $refClass->getMethod($name); if (!$refMethod->hasAnnotation($privilege)) { throw new \Ark8\Security\Exceptions\SkipException(sprintf('Method %s::%s does not have annotation %s.', $class, $name, $privilege)); } return in_array($role, preg_split('#\\s+#', trim((string) $refMethod->getAnnotation($privilege)))); }
/** * Check whenever current user is allowed to use given link * * @param string $element etc "this", ":Admin:Show:default" * * @return bool */ public function isAllowed($element) { list($presenter, $action) = $this->formatLink($element); $presenterReflection = UI\PresenterComponentReflection::from($this->presenterFactory->getPresenterClass($presenter)); if (!$this->requirementsChecker->isAllowed($presenterReflection)) { return FALSE; } $actionKey = UI\Presenter::ACTION_KEY . ucfirst($action); if ($presenterReflection->hasMethod($actionKey) && !$this->requirementsChecker->isAllowed($presenterReflection->getMethod($actionKey))) { return FALSE; } return TRUE; }
/** * Check whenever current user is allowed to use given link * @param string $link etc "this", ":Admin:Show:default" * @return bool */ public function isAllowed($link) { list($presenter, $action) = $this->formatLink($link); $presenterReflection = PresenterComponentReflection::from($this->presenterFactory->getPresenterClass($presenter)); if (!$this->permissionChecker->isAllowed($presenterReflection)) { return false; } $actionKey = Presenter::ACTION_KEY . ucfirst($action); if ($presenterReflection->hasMethod($actionKey) && !$this->permissionChecker->isAllowed($presenterReflection->getMethod($actionKey))) { return false; } return true; }
/** * @return Nette\Application\IResponse */ public function run(Application\Request $request) { $this->request = $request; if ($this->httpRequest && $this->router && !$this->httpRequest->isAjax() && ($request->isMethod('get') || $request->isMethod('head'))) { $refUrl = clone $this->httpRequest->getUrl(); $url = $this->router->constructUrl($request, $refUrl->setPath($refUrl->getScriptPath())); if ($url !== NULL && !$this->httpRequest->getUrl()->isEqual($url)) { return new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY); } } $params = $request->getParameters(); if (!isset($params['callback'])) { throw new Application\BadRequestException('Parameter callback is missing.'); } $params['presenter'] = $this; $callback = $params['callback']; $reflection = Nette\Utils\Callback::toReflection(Nette\Utils\Callback::check($callback)); $params = Application\UI\PresenterComponentReflection::combineArgs($reflection, $params); if ($this->context) { foreach ($reflection->getParameters() as $param) { if ($param->getClassName()) { unset($params[$param->getPosition()]); } } $params = Nette\DI\Helpers::autowireArguments($reflection, $params, $this->context); $params['presenter'] = $this; } $response = call_user_func_array($callback, $params); if (is_string($response)) { $response = array($response, array()); } if (is_array($response)) { list($templateSource, $templateParams) = $response; $response = $this->createTemplate()->setParameters($templateParams); if (!$templateSource instanceof \SplFileInfo) { $response->getLatte()->setLoader(new Latte\Loaders\StringLoader); } $response->setFile($templateSource); } if ($response instanceof Application\UI\ITemplate) { return new Responses\TextResponse($response); } else { return $response; } }
/** * @param \Nette\Application\UI\PresenterComponentReflection $element * @return bool */ protected function isPresenterAllowed(\Nette\Application\UI\PresenterComponentReflection $element) { $ref = ClassType::from($element->name); // is not secured if (!$ref->hasAnnotation('secured')) { return TRUE; } // resource & privilege $secured = $ref->getAnnotation('secured'); $resource = isset($secured['resource']) ? $secured['resource'] : $ref->getNamespaceName(); $privilege = isset($secured['privilege']) ? $secured['privilege'] : NULL; if (!parent::isAllowed($resource, $privilege)) { return FALSE; } // roles if (isset($secured['roles'])) { $userRoles = $this->getRoles(); $roles = explode(',', $secured['roles']); array_walk($roles, function (&$val) { $val = trim($val); }); if (count(array_intersect($userRoles, $roles)) == 0) { return FALSE; } } // users if (isset($secured['users'])) { $users = explode(',', $secured['users']); array_walk($users, function (&$val) { $val = trim($val); }); $users = (array) $element->getAnnotation('User'); if (in_array($this->getId(), $users)) { return FALSE; } } return TRUE; }
/** * Converts list of arguments to named parameters. * @param string class name * @param string method name * @param array arguments * @param array supplemental arguments * @return void * @throws InvalidLinkException */ private static function argsToParams($class, $method, &$args, $supplemental = array()) { $i = 0; $rm = new \ReflectionMethod($class, $method); foreach ($rm->getParameters() as $param) { $name = $param->getName(); if (array_key_exists($i, $args)) { $args[$name] = $args[$i]; unset($args[$i]); $i++; } elseif (array_key_exists($name, $args)) { // continue with process } elseif (array_key_exists($name, $supplemental)) { $args[$name] = $supplemental[$name]; } else { continue; } if ($args[$name] === NULL) { continue; } $def = $param->isDefaultValueAvailable() && $param->isOptional() ? $param->getDefaultValue() : NULL; // see PHP bug #62988 $type = $param->isArray() ? 'array' : gettype($def); if (!PresenterComponentReflection::convertType($args[$name], $type)) { throw new InvalidLinkException("Invalid value for parameter '{$name}' in method {$class}::{$method}(), expected " . ($type === 'NULL' ? 'scalar' : $type) . "."); } if ($args[$name] === $def || $def === NULL && is_scalar($args[$name]) && (string) $args[$name] === '') { $args[$name] = NULL; // value transmit is unnecessary } } if (array_key_exists($i, $args)) { $method = $rm->getName(); throw new InvalidLinkException("Passed more parameters than method {$class}::{$method}() expects."); } }
/** * Calls public method if exists. * @param string * @param array * @return bool does method exist? */ protected function tryCall($method, array $params, $class = null, $dryRun = false) { if (func_num_args() == 2) { $class = $this; } //$rc = $class->getReflection(); $rc = new Nette\Application\UI\PresenterComponentReflection(get_class($class)); if ($rc->hasMethod($method)) { $rm = $rc->getMethod($method); if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) { $this->checkRequirements($rm); if (!$dryRun) { $rm->invokeArgs($class, $rc->combineArgs($rm, $params)); } return TRUE; } } return FALSE; }
/** * Returns array of persistent components. * This default implementation detects components by class-level annotation @persistent(cmp1, cmp2). * @return array */ public static function getPersistentComponents() { return (array) PresenterComponentReflection::parseAnnotation(new \ReflectionClass(get_called_class()), 'persistent'); }
/** * Saves state informations for next request. * @param array * @param PresenterComponentReflection (internal, used by Presenter) * @return void */ public function saveState(array & $params, $reflection = NULL) { $reflection = $reflection === NULL ? $this->getReflection() : $reflection; foreach ($reflection->getPersistentParams() as $name => $meta) { if (isset($params[$name])) { // injected value } elseif (array_key_exists($name, $params)) { // NULLs are skipped continue; } elseif (!isset($meta['since']) || $this instanceof $meta['since']) { $params[$name] = $this->$name; // object property value } else { continue; // ignored parameter } $type = gettype($meta['def']); if (!PresenterComponentReflection::convertType($params[$name], $type)) { throw new InvalidLinkException(sprintf("Invalid value for persistent parameter '%s' in '%s', expected %s.", $name, $this->getName(), $type === 'NULL' ? 'scalar' : $type)); } if ($params[$name] === $meta['def'] || ($meta['def'] === NULL && is_scalar($params[$name]) && (string) $params[$name] === '')) { $params[$name] = NULL; // value transmit is unnecessary } } }
/** * Calls public method if exists * * @param string * @param array * * @return bool does method exist? */ protected function tryCall($method, array $params) { $rc = $this->getReflection(); if ($rc->hasMethod($method)) { $rm = $rc->getMethod($method); if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) { return $rm->invokeArgs($this, Application\UI\PresenterComponentReflection::combineArgs($rm, $params)); } } return FALSE; }
/** * Request/URL factory. * @param PresenterComponent base * @param string destination in format "[[module:]presenter:]action" or "signal!" or "this" * @param array array of arguments * @param string forward|redirect|link * @return string URL * @throws InvalidLinkException * @internal */ protected final function createRequest($component, $destination, array $args, $mode) { // note: createRequest supposes that saveState(), run() & tryCall() behaviour is final // cached services for better performance static $presenterFactory, $router, $refUrl; if ($presenterFactory === NULL) { $presenterFactory = $this->getApplication()->getPresenterFactory(); $router = $this->getApplication()->getRouter(); $refUrl = new Http\Url($this->getHttpRequest()->getUrl()); $refUrl->setPath($this->getHttpRequest()->getUrl()->getScriptPath()); } $this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL; // PARSE DESTINATION // 1) fragment $a = strpos($destination, '#'); if ($a === FALSE) { $fragment = ''; } else { $fragment = substr($destination, $a); $destination = substr($destination, 0, $a); } // 2) ?query syntax $a = strpos($destination, '?'); if ($a !== FALSE) { parse_str(substr($destination, $a + 1), $args); // requires disabled magic quotes $destination = substr($destination, 0, $a); } // 3) URL scheme $a = strpos($destination, '//'); if ($a === FALSE) { $scheme = FALSE; } else { $scheme = substr($destination, 0, $a); $destination = substr($destination, $a + 2); } // 4) signal or empty if (!$component instanceof Presenter || substr($destination, -1) === '!') { $signal = rtrim($destination, '!'); $a = strrpos($signal, ':'); if ($a !== FALSE) { $component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-')); $signal = (string) substr($signal, $a + 1); } if ($signal == NULL) { // intentionally == throw new InvalidLinkException("Signal must be non-empty string."); } $destination = 'this'; } if ($destination == NULL) { // intentionally == throw new InvalidLinkException("Destination must be non-empty string."); } // 5) presenter: action $current = FALSE; $a = strrpos($destination, ':'); if ($a === FALSE) { $action = $destination === 'this' ? $this->action : $destination; $presenter = $this->getName(); $presenterClass = get_class($this); } else { $action = (string) substr($destination, $a + 1); if ($destination[0] === ':') { // absolute if ($a < 2) { throw new InvalidLinkException("Missing presenter name in '{$destination}'."); } $presenter = substr($destination, 1, $a - 1); } else { // relative $presenter = $this->getName(); $b = strrpos($presenter, ':'); if ($b === FALSE) { // no module $presenter = substr($destination, 0, $a); } else { // with module $presenter = substr($presenter, 0, $b + 1) . substr($destination, 0, $a); } } try { $presenterClass = $presenterFactory->getPresenterClass($presenter); } catch (Application\InvalidPresenterException $e) { throw new InvalidLinkException($e->getMessage(), NULL, $e); } } // PROCESS SIGNAL ARGUMENTS if (isset($signal)) { // $component must be IStatePersistent $reflection = new PresenterComponentReflection(get_class($component)); if ($signal === 'this') { // means "no signal" $signal = ''; if (array_key_exists(0, $args)) { throw new InvalidLinkException("Unable to pass parameters to 'this!' signal."); } } elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) { // TODO: AppForm exception // counterpart of signalReceived() & tryCall() $method = $component->formatSignalMethod($signal); if (!$reflection->hasCallableMethod($method)) { throw new InvalidLinkException("Unknown signal '{$signal}', missing handler {$reflection->name}::{$method}()"); } if ($args) { // convert indexed parameters to named self::argsToParams(get_class($component), $method, $args); } } // counterpart of IStatePersistent if ($args && array_intersect_key($args, $reflection->getPersistentParams())) { $component->saveState($args); } if ($args && $component !== $this) { $prefix = $component->getUniqueId() . self::NAME_SEPARATOR; foreach ($args as $key => $val) { unset($args[$key]); $args[$prefix . $key] = $val; } } } // PROCESS ARGUMENTS if (is_subclass_of($presenterClass, __CLASS__)) { if ($action === '') { $action = self::DEFAULT_ACTION; } $current = ($action === '*' || $action === $this->action) && $presenterClass === get_class($this); // TODO $reflection = new PresenterComponentReflection($presenterClass); if ($args || $destination === 'this') { // counterpart of run() & tryCall() /**/ $method = $presenterClass::formatActionMethod($action); /**/ /*5.2* $method = call_user_func(array($presenterClass, 'formatActionMethod'), $action);*/ if (!$reflection->hasCallableMethod($method)) { /**/ $method = $presenterClass::formatRenderMethod($action); /**/ /*5.2* $method = call_user_func(array($presenterClass, 'formatRenderMethod'), $action);*/ if (!$reflection->hasCallableMethod($method)) { $method = NULL; } } // convert indexed parameters to named if ($method === NULL) { if (array_key_exists(0, $args)) { throw new InvalidLinkException("Unable to pass parameters to action '{$presenter}:{$action}', missing corresponding method."); } } elseif ($destination === 'this') { self::argsToParams($presenterClass, $method, $args, $this->params); } else { self::argsToParams($presenterClass, $method, $args); } } // counterpart of IStatePersistent if ($args && array_intersect_key($args, $reflection->getPersistentParams())) { $this->saveState($args, $reflection); } $globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass); if ($current && $args) { $tmp = $globalState + $this->params; foreach ($args as $key => $val) { if ((string) $val !== (isset($tmp[$key]) ? (string) $tmp[$key] : '')) { $current = FALSE; break; } } } $args += $globalState; } // ADD ACTION & SIGNAL & FLASH $args[self::ACTION_KEY] = $action; if (!empty($signal)) { $args[self::SIGNAL_KEY] = $component->getParamId($signal); $current = $current && $args[self::SIGNAL_KEY] === $this->getParam(self::SIGNAL_KEY); } if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) { $args[self::FLASH_KEY] = $this->getParam(self::FLASH_KEY); } $this->lastCreatedRequest = new Application\Request($presenter, Application\Request::FORWARD, $args, array(), array()); $this->lastCreatedRequestFlag = array('current' => $current); if ($mode === 'forward') { return; } // CONSTRUCT URL $url = $router->constructUrl($this->lastCreatedRequest, $refUrl); if ($url === NULL) { unset($args[self::ACTION_KEY]); $params = urldecode(http_build_query($args, NULL, ', ')); throw new InvalidLinkException("No route for {$presenter}:{$action}({$params})"); } // make URL relative if possible if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) { $hostUrl = $refUrl->getHostUrl(); if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) { $url = substr($url, strlen($hostUrl)); } } return $url . $fragment; }
/** * Returns array of classes persistent parameters. They have public visibility and are non-static. * This default implementation detects persistent parameters by annotation @persistent. * @return array */ public static function getPersistentParams() { $rc = new \ReflectionClass(get_called_class()); $params = array(); foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $rp) { if (!$rp->isStatic() && PresenterComponentReflection::parseAnnotation($rp, 'persistent')) { $params[] = $rp->getName(); } } return $params; }