mirror of
https://github.com/Mibew/mibew.git
synced 2025-03-03 18:38:31 +03:00
Add routes-based access control
This commit is contained in:
parent
bac1404cf1
commit
b10408b631
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2005-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Mibew\AccessControl\Check;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CheckResolver
|
||||
{
|
||||
/**
|
||||
* Resolves access check callable by request.
|
||||
*
|
||||
* @param Request $request Incoming request.
|
||||
* @return callable
|
||||
* @throws \InvalidArgumentException If the access check cannot be resolved.
|
||||
*/
|
||||
public function getCheck(Request $request)
|
||||
{
|
||||
// Get access check name from the request
|
||||
$access_check = $request->attributes->get('_access_check');
|
||||
if (!$access_check) {
|
||||
// By default we do not need to restrict access
|
||||
return function () {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// Check if specified access check is something that can be called
|
||||
// directly
|
||||
if (strpos($access_check, ':') === false) {
|
||||
if (method_exists($access_check, '__invoke')) {
|
||||
return new $access_check();
|
||||
} elseif (function_exists($access_check)) {
|
||||
return $access_check;
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Unable to find access check "%s".',
|
||||
$access_check
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Build callable for specified access check
|
||||
$callable = $this->createCheck($access_check);
|
||||
|
||||
if (!is_callable($callable)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Access check "%s" for URI "%s" is not callable.',
|
||||
$access_check,
|
||||
$request->getPathInfo()
|
||||
));
|
||||
}
|
||||
|
||||
return $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds access check callable by its full name.
|
||||
*
|
||||
* @param string $access_check Full access check name in "<Class>::<method>"
|
||||
* format.
|
||||
* @return callable Access check callable
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function createCheck($access_check)
|
||||
{
|
||||
if (strpos($access_check, '::') === false) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Unable to find access check "%s".',
|
||||
$access_check
|
||||
));
|
||||
}
|
||||
|
||||
list($class, $method) = explode('::', $access_check, 2);
|
||||
if (!class_exists($class)) {
|
||||
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
|
||||
}
|
||||
|
||||
return array(new $class(), $method);
|
||||
}
|
||||
}
|
@ -17,12 +17,15 @@
|
||||
|
||||
namespace Mibew;
|
||||
|
||||
use Mibew\EventDispatcher;
|
||||
use Mibew\Routing\Router;
|
||||
use Mibew\Routing\RouteCollectionLoader;
|
||||
use Mibew\Routing\Exception\AccessDeniedException;
|
||||
use Mibew\Controller\ControllerResolver;
|
||||
use Mibew\AccessControl\Check\CheckResolver;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\Routing\RequestContext;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||
@ -56,6 +59,7 @@ class Application
|
||||
$this->fileLocator = new FileLocator(array(MIBEW_FS_ROOT));
|
||||
$this->router = new Router(new RouteCollectionLoader($this->fileLocator));
|
||||
$this->controllerResolver = new ControllerResolver($this->router);
|
||||
$this->accessCheckResolver = new CheckResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,17 +76,22 @@ class Application
|
||||
$this->router->setContext($context);
|
||||
|
||||
try {
|
||||
// Try to match route
|
||||
// Try to match a route and add extra data to the request.
|
||||
$parameters = $this->router->matchRequest($request);
|
||||
$request->attributes->add($parameters);
|
||||
$request->attributes->set('_operator', $this->extractOperator($request));
|
||||
|
||||
// Get controller
|
||||
// Check if the user can access the page
|
||||
$access_check = $this->accessCheckResolver->getCheck($request);
|
||||
if (!call_user_func($access_check, $request)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Get controller and perform its action to get a response.
|
||||
$controller = $this->controllerResolver->getController($request);
|
||||
|
||||
// Execute the controller's action and get response.
|
||||
$response = call_user_func($controller, $request);
|
||||
} catch (AccessDeniedException $e) {
|
||||
return new Response('Forbidden', 403);
|
||||
return $this->buildAccessDeniedResponse($request);
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
return new Response('Not Found', 404);
|
||||
} catch (MethodNotAllowedException $e) {
|
||||
@ -99,4 +108,83 @@ class Application
|
||||
return new Response((string)$response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts operator's data from the passed in request object.
|
||||
*
|
||||
* @param Request $request A request to extract operator from.
|
||||
* @return array|bool Associative array with operator's data or boolean
|
||||
* false if there is no operator related with the request.
|
||||
*
|
||||
* @todo Remove this method when Object Oriented wrapper for an operator
|
||||
* will be created.
|
||||
*/
|
||||
protected function extractOperator(Request $request)
|
||||
{
|
||||
// Try to get operator from session.
|
||||
if (isset($_SESSION[SESSION_PREFIX . "operator"])) {
|
||||
return $_SESSION[SESSION_PREFIX . "operator"];
|
||||
}
|
||||
|
||||
// Check if operator had used "remember me" feature.
|
||||
if ($request->cookies->has(REMEMBER_OPERATOR_COOKIE_NAME)) {
|
||||
$cookie_value = $request->cookies->get(REMEMBER_OPERATOR_COOKIE_NAME);
|
||||
list($login, $pwd) = preg_split('/\x0/', base64_decode($cookie_value), 2);
|
||||
$op = operator_by_login($login);
|
||||
$can_login = $op
|
||||
&& isset($pwd)
|
||||
&& isset($op['vcpassword'])
|
||||
&& calculate_password_hash($op['vclogin'], $op['vcpassword']) == $pwd
|
||||
&& !operator_is_disabled($op);
|
||||
if ($can_login) {
|
||||
$_SESSION[SESSION_PREFIX . "operator"] = $op;
|
||||
|
||||
return $op;
|
||||
}
|
||||
}
|
||||
|
||||
// Operator's data cannot be extracted from the request.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds response for pages with denied access
|
||||
*
|
||||
* Triggers "accessDenied' event to provide an ability for plugins to set custom response.
|
||||
* an associative array with folloing keys is passed to event listeners:
|
||||
* - 'request': {@link Symfony\Component\HttpFoundation\Request} object.
|
||||
*
|
||||
* An event listener can attach custom response to the arguments array
|
||||
* (using "response" key) to send it to the client.
|
||||
*
|
||||
* @param Request $request Incoming request
|
||||
* @return Response
|
||||
*/
|
||||
protected function buildAccessDeniedResponse(Request $request)
|
||||
{
|
||||
// Trigger fail
|
||||
$args = array(
|
||||
'request' => $request,
|
||||
'response' => false,
|
||||
);
|
||||
$dispatcher = EventDispatcher::getInstance();
|
||||
$dispatcher->triggerEvent('accessDenied', $args);
|
||||
|
||||
if ($args['response'] && ($args['response'] instanceof Response)) {
|
||||
// If one of event listeners returned the response object send it
|
||||
// to the client.
|
||||
return $args['response'];
|
||||
}
|
||||
|
||||
if ($request->attributes->get('_operator')) {
|
||||
// If the operator already logged in, display 403 page.
|
||||
return new Response('Forbidden', 403);
|
||||
}
|
||||
|
||||
// Operator is not logged in. Redirect him to the login page.
|
||||
$_SESSION['backpath'] = $request->getUri();
|
||||
$response = new RedirectResponse($request->getUriForPath('/operator/login.php'));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user