mirror of
https://github.com/skipperbent/simple-php-router.git
synced 2026-06-17 16:57:53 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbb4294f58 | |||
| d6bdcbe70c | |||
| 25f569384f | |||
| b37c73d5dd | |||
| f5597c24ce | |||
| b8061f2aa7 | |||
| 6c7ac2b250 | |||
| d2de22e5e0 | |||
| 252fb16326 | |||
| 63dfbb24af | |||
| 3ccfac9422 | |||
| 8f2d49fb73 | |||
| bdb5b2dead | |||
| 5d643d842a | |||
| d6cf5c9b68 | |||
| 03cac14e8e | |||
| f49fa5dca2 | |||
| 9d6a3c328f | |||
| 1ba05b923c | |||
| c221381c02 | |||
| b173659657 | |||
| 961d73a13f | |||
| 54ae628f4e | |||
| 7a23ac0b2e | |||
| b555eb07a6 | |||
| 8959a237f9 |
@@ -17,13 +17,23 @@ Add the latest version pf Simple PHP Router to your ```composer.json```
|
||||
|
||||
## Notes
|
||||
|
||||
### Features
|
||||
|
||||
- Basic routing (get, post, put, delete) with support for custom multiple verbs.
|
||||
- Regular Expression Constraints for parameters.
|
||||
- Named routes.
|
||||
- Generating url to routes.
|
||||
- Route groups.
|
||||
- Middleware (classes that intercepts before the route is rendered).
|
||||
- Namespaces.
|
||||
- Route prefixes.
|
||||
- CSRF protection.
|
||||
|
||||
### Features currently "in-the-works"
|
||||
|
||||
- Global Constraints
|
||||
- Named Routes
|
||||
- Sub-Domain Routing
|
||||
- CSRF Protection
|
||||
- Optional/required parameters
|
||||
- Required parameters
|
||||
|
||||
## Initialising the router
|
||||
|
||||
@@ -138,8 +148,11 @@ class Router extends SimpleRouter {
|
||||
// Init locale settings
|
||||
Locale::getInstance();
|
||||
|
||||
// Set default namespace
|
||||
// Set default namespace for routes
|
||||
$defaultNamespace = '\\'.Registry::getInstance()->get('AppName') . '\\Controller';
|
||||
|
||||
// Add custom csrf verifier (must extend BaseCsrfVerifier)
|
||||
parent::csrfVerifier('MyProject\Middleware\CustomCsrfVerifier');
|
||||
|
||||
// Handle exceptions
|
||||
try {
|
||||
@@ -171,17 +184,62 @@ function url($controller, $parameters = null, $getParams = null) {
|
||||
}
|
||||
```
|
||||
|
||||
In ```routes.php``` we have added this route:
|
||||
This is a basic example for getting the current csrf token
|
||||
|
||||
```SimpleRouter::get('/item/{id}', 'myController@show');```
|
||||
```php
|
||||
/**
|
||||
* Get current csrf-token
|
||||
* @return null|string
|
||||
*/
|
||||
function csrf_token() {
|
||||
$token = new \Pecee\CsrfToken();
|
||||
return $token->getToken();
|
||||
}
|
||||
```
|
||||
|
||||
In the template we then call:
|
||||
## Getting urls
|
||||
|
||||
```url('myController@show', ['id' => 22], ['category' => 'shoes']);```
|
||||
**In ```routes.php``` we have added this route:**
|
||||
|
||||
Result url is:
|
||||
```php
|
||||
SimpleRouter::get('/item/{id}', 'myController@show', ['as' => 'item']);
|
||||
```
|
||||
|
||||
```/item/22?category=shoes ```
|
||||
**In the template we then call:**
|
||||
|
||||
```php
|
||||
url('item', ['id' => 22], ['category' => 'shoes']);
|
||||
```
|
||||
|
||||
**Result url is:**
|
||||
|
||||
```php
|
||||
/item/22/?category=shoes
|
||||
```
|
||||
|
||||
## Custom CSRF verifier
|
||||
|
||||
Create a new class and extend the ```BaseCsrfVerifier``` middleware class provided with simple-php-router.
|
||||
|
||||
Add the property ```except``` with an array of the urls to the routes you would like to exclude from the CSRF validation. Using ```*``` at the end for the url will match the entire url.
|
||||
|
||||
Querystrings are ignored.
|
||||
|
||||
```php
|
||||
use Pecee\Http\Middleware\BaseCsrfVerifier;
|
||||
|
||||
class CsrfVerifier extends BaseCsrfVerifier {
|
||||
|
||||
protected $except = ['/companies/*', '/user/save'];
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Register the new class in your ```routes.php```, custom ```Router``` class or wherever you register your routes.
|
||||
|
||||
```php
|
||||
SimpleRouter::csrfVerifier(new \Demo\Middleware\CsrfVerifier());
|
||||
```
|
||||
|
||||
## Documentation
|
||||
While I work on a better documentation, please refer to the Laravel 5 routing documentation here:
|
||||
@@ -211,4 +269,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
+24
-38
@@ -3,37 +3,21 @@ namespace Pecee;
|
||||
|
||||
class CsrfToken {
|
||||
|
||||
const CSRF_KEY = 'csrf_token';
|
||||
const CSRF_KEY = 'XSRF-TOKEN';
|
||||
|
||||
protected static $instance;
|
||||
|
||||
protected $lastToken;
|
||||
protected $currentToken;
|
||||
|
||||
public static function getInstance() {
|
||||
if(self::$instance === null) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
protected $token;
|
||||
|
||||
public function __construct() {
|
||||
$this->lastToken = isset($_SESSION[self::CSRF_KEY]) ? $_SESSION[self::CSRF_KEY] : null;
|
||||
$this->currentToken = $this->generate();
|
||||
|
||||
// Initialise session, if it hasn't been initialised.
|
||||
if(!isset($_SESSION)) {
|
||||
session_start();
|
||||
if($this->getToken() === null) {
|
||||
$this->setToken($this->generateToken());
|
||||
}
|
||||
|
||||
$_SESSION['csrf_token'] = $this->currentToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random identifier for CSRF token
|
||||
* @return string
|
||||
*/
|
||||
public static function generate() {
|
||||
public static function generateToken() {
|
||||
if (function_exists('mcrypt_create_iv')) {
|
||||
return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
|
||||
}
|
||||
@@ -47,28 +31,30 @@ class CsrfToken {
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($token) {
|
||||
return hash_equals($token, $_SESSION[self::CSRF_KEY]);
|
||||
if($token !== null && $this->getToken() !== null) {
|
||||
return hash_equals($token, $this->getToken());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set csrf token cookie
|
||||
*
|
||||
* @param $token
|
||||
*/
|
||||
public function setToken($token) {
|
||||
setcookie(self::CSRF_KEY, $token, time() + 60 * 120, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get csrf token
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLastToken(){
|
||||
return $this->lastToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $lastToken
|
||||
*/
|
||||
public function setLastToken($lastToken){
|
||||
$this->lastToken = $lastToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCurrentToken(){
|
||||
return $this->currentToken;
|
||||
public function getToken(){
|
||||
if(isset($_COOKIE[self::CSRF_KEY])) {
|
||||
return $_COOKIE[self::CSRF_KEY];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
namespace Pecee\Exception;
|
||||
|
||||
class TokenMismatchException extends \Exception {}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Pecee\Http\Middleware;
|
||||
|
||||
use Pecee\CsrfToken;
|
||||
use Pecee\Exception\TokenMismatchException;
|
||||
use Pecee\Http\Request;
|
||||
|
||||
class BaseCsrfVerifier extends Middleware {
|
||||
|
||||
const POST_KEY = 'csrf-token';
|
||||
const HEADER_KEY = 'X-CSRF-TOKEN';
|
||||
|
||||
protected $except;
|
||||
protected $csrfToken;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
$this->csrfToken = new CsrfToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the url matches the urls in the except property
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function skip(Request $request) {
|
||||
|
||||
if($this->except === null || !is_array($this->except)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($this->except as $url) {
|
||||
$url = rtrim($url, '/');
|
||||
if($url[strlen($url)-1] === '*') {
|
||||
$url = rtrim($url, '*');
|
||||
$skip = (stripos($request->getUri(), $url) === 0);
|
||||
} else {
|
||||
$skip = ($url === rtrim($request->getUri(), '/'));
|
||||
}
|
||||
|
||||
if($skip) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handle(Request $request) {
|
||||
|
||||
if($request->getMethod() != 'get' && !$this->skip($request)) {
|
||||
|
||||
$token = (isset($_POST[self::POST_KEY])) ? $_POST[self::POST_KEY] : null;
|
||||
|
||||
// If the token is not posted, check headers for valid x-csrf-token
|
||||
if($token === null) {
|
||||
$token = $request->getHeader(self::HEADER_KEY);
|
||||
}
|
||||
|
||||
if( !$this->csrfToken->validate( $token ) ) {
|
||||
throw new TokenMismatchException('Invalid csrf-token.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,5 @@ use Pecee\SimpleRouter\RouterEntry;
|
||||
|
||||
abstract class Middleware
|
||||
{
|
||||
public function handle(Request $request) {
|
||||
return true;
|
||||
}
|
||||
abstract function handle(Request $request);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Pecee\Http\Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -6,11 +6,13 @@ class Request {
|
||||
protected $uri;
|
||||
protected $host;
|
||||
protected $method;
|
||||
protected $headers;
|
||||
|
||||
public function __construct() {
|
||||
$this->host = $_SERVER['HTTP_HOST'];
|
||||
$this->uri = rtrim($_SERVER['REQUEST_URI'], '/') . '/';
|
||||
$this->method = (isset($_POST['_method'])) ? strtolower($_POST['_method']) : strtolower($_SERVER['REQUEST_METHOD']);
|
||||
$this->headers = getallheaders();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,8 +41,66 @@ class Request {
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUser() {
|
||||
$data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
|
||||
return (isset($data['username'])) ? $data['username'] : null;
|
||||
return (isset($_SERVER['PHP_AUTH_USER'])) ? $_SERVER['PHP_AUTH_USER']: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get http basic auth password
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPassword() {
|
||||
return (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW']: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders() {
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id address
|
||||
* @return string
|
||||
*/
|
||||
public function getIp() {
|
||||
return isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get referer
|
||||
* @return string
|
||||
*/
|
||||
public function getReferer() {
|
||||
return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user agent
|
||||
* @return string
|
||||
*/
|
||||
public function getUserAgent() {
|
||||
return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header value by name
|
||||
* @param string $name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHeader($name) {
|
||||
return (isset($this->headers[$name])) ? $this->headers[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request input or default value
|
||||
* @param string $name
|
||||
* @param string $defaultValue
|
||||
* @return mixed
|
||||
*/
|
||||
public function getInput($name, $defaultValue) {
|
||||
return (isset($_REQUEST[$name]) ? $_REQUEST[$name] : $defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class Response {
|
||||
* @param string $url
|
||||
*/
|
||||
public function redirect($url) {
|
||||
header('location: ' . $url);
|
||||
$this->header('Location: ' . $url);
|
||||
die();
|
||||
}
|
||||
|
||||
@@ -29,4 +29,59 @@ class Response {
|
||||
$this->redirect(url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add http authorisation
|
||||
* @param string $name
|
||||
* @return self $this
|
||||
*/
|
||||
public function auth($name = '') {
|
||||
$this->headers([
|
||||
'WWW-Authenticate: Basic realm="' . $name . '"',
|
||||
'HTTP/1.0 401 Unauthorized'
|
||||
]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cache($duration = 2592000) {
|
||||
$this->headers([
|
||||
'Cache-Control: public,max-age='.$duration.',must-revalidate',
|
||||
'Expires: '.gmdate('D, d M Y H:i:s',(time()+$duration)).' GMT',
|
||||
'Last-modified: '.gmdate('D, d M Y H:i:s',time()).' GMT'
|
||||
]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Json encode array
|
||||
* @param array $value
|
||||
* @return self $this
|
||||
*/
|
||||
public function json(array $value) {
|
||||
$this->header('Content-type: application/json');
|
||||
echo json_encode($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add header to response
|
||||
* @param string $value
|
||||
* @return self $this
|
||||
*/
|
||||
public function header($value) {
|
||||
header($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple headers to response
|
||||
* @param array $headers
|
||||
* @return self $this
|
||||
*/
|
||||
public function headers(array $headers) {
|
||||
foreach($headers as $header) {
|
||||
header($header);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Pecee\SimpleRouter;
|
||||
|
||||
use Pecee\ArrayUtil;
|
||||
use Pecee\Http\Middleware\BaseCsrfVerifier;
|
||||
use Pecee\Http\Request;
|
||||
use Pecee\Url;
|
||||
|
||||
@@ -17,6 +18,7 @@ class RouterBase {
|
||||
protected $backstack;
|
||||
protected $loadedRoute;
|
||||
protected $defaultNamespace;
|
||||
protected $baseCsrfVerifier;
|
||||
|
||||
// TODO: make interface for controller routers, so they can be easily detected
|
||||
// TODO: clean up - cut some of the methods down to smaller pieces
|
||||
@@ -26,6 +28,7 @@ class RouterBase {
|
||||
$this->backstack = array();
|
||||
$this->controllerUrlMap = array();
|
||||
$this->request = new Request();
|
||||
$this->baseCsrfVerifier = new BaseCsrfVerifier();
|
||||
}
|
||||
|
||||
public function addRoute(RouterEntry $route) {
|
||||
@@ -85,8 +88,16 @@ class RouterBase {
|
||||
}
|
||||
|
||||
public function routeRequest() {
|
||||
// Loop through each route-request
|
||||
|
||||
// Verify csrf token for request
|
||||
if($this->baseCsrfVerifier !== null) {
|
||||
/* @var $csrfVerifier BaseCsrfVerifier */
|
||||
$csrfVerifier = $this->baseCsrfVerifier;
|
||||
$csrfVerifier = new $csrfVerifier();
|
||||
$csrfVerifier->handle($this->request);
|
||||
}
|
||||
|
||||
// Loop through each route-request
|
||||
$this->processRoutes($this->routes);
|
||||
|
||||
// Make sure the urls is in the right order when comparing
|
||||
@@ -100,7 +111,6 @@ class RouterBase {
|
||||
foreach($this->controllerUrlMap as $route) {
|
||||
$routeMatch = $route->matchRoute($this->request);
|
||||
|
||||
|
||||
if($routeMatch && !($routeMatch instanceof RouterGroup)) {
|
||||
|
||||
if(count($route->getRequestMethods()) && !in_array($this->request->getMethod(), $route->getRequestMethods())) {
|
||||
@@ -179,9 +189,28 @@ class RouterBase {
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base csrf verifier class
|
||||
* @return BaseCsrfVerifier
|
||||
*/
|
||||
public function getBaseCsrfVerifier() {
|
||||
return $this->baseCsrfVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set base csrf verifier class
|
||||
*
|
||||
* @param BaseCsrfVerifier $baseCsrfVerifier
|
||||
* @return self
|
||||
*/
|
||||
public function setBaseCsrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) {
|
||||
$this->baseCsrfVerifier = $baseCsrfVerifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function processUrl($route, $method = null, $parameters = null, $getParams = null) {
|
||||
|
||||
$url = $route->getUrl();
|
||||
$url = '/' . trim($route->getUrl(), '/');
|
||||
|
||||
if(($route instanceof RouterController || $route instanceof RouterResource) && $method !== null) {
|
||||
$url .= $method;
|
||||
@@ -277,7 +306,7 @@ class RouterBase {
|
||||
ArrayUtil::append($url, $parameters);
|
||||
}
|
||||
|
||||
return join('/', $url);
|
||||
return '/' . join('/', $url);
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
|
||||
@@ -245,13 +245,13 @@ abstract class RouterEntry {
|
||||
|
||||
protected function loadMiddleware(Request $request) {
|
||||
if($this->getMiddleware()) {
|
||||
if (!($this->getMiddleware() instanceof Middleware)) {
|
||||
$middleware = $this->loadClass($this->getMiddleware());
|
||||
if (!($middleware instanceof Middleware)) {
|
||||
throw new RouterException($this->getMiddleware() . ' must be instance of Middleware');
|
||||
}
|
||||
|
||||
/* @var $class Middleware */
|
||||
$class = $this->loadClass($this->getMiddleware());
|
||||
$class->handle($request);
|
||||
$middleware->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ abstract class RouterEntry {
|
||||
$className = $this->getNamespace() . '\\' . $controller[0];
|
||||
|
||||
$class = $this->loadClass($className);
|
||||
$method = $request->getMethod() . ucfirst($controller[1]);
|
||||
$method = $controller[1];
|
||||
|
||||
if (!method_exists($class, $method)) {
|
||||
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
|
||||
|
||||
@@ -9,14 +9,29 @@
|
||||
|
||||
namespace Pecee\SimpleRouter;
|
||||
|
||||
use Pecee\Http\Middleware\BaseCsrfVerifier;
|
||||
|
||||
class SimpleRouter {
|
||||
|
||||
/**
|
||||
* Start/route request
|
||||
* @param null $defaultNamespace
|
||||
* @throws RouterException
|
||||
*/
|
||||
public static function start($defaultNamespace = null) {
|
||||
$router = RouterBase::GetInstance();
|
||||
$router->setDefaultNamespace($defaultNamespace);
|
||||
$router->routeRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set base csrf verifier
|
||||
* @param BaseCsrfVerifier $baseCsrfVerifier
|
||||
*/
|
||||
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) {
|
||||
RouterBase::getInstance()->setBaseCsrfVerifier($baseCsrfVerifier);
|
||||
}
|
||||
|
||||
public static function get($url, $callback, array $settings = null) {
|
||||
$route = new RouterRoute($url, $callback);
|
||||
$route->addSettings($settings);
|
||||
@@ -113,7 +128,7 @@ class SimpleRouter {
|
||||
return $route;
|
||||
}
|
||||
|
||||
public function getRoute($controller = null, $parameters = null, $getParams = null) {
|
||||
public static function getRoute($controller = null, $parameters = null, $getParams = null) {
|
||||
return RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user