Merge branch 'v4-development' into v4-feature-ip

This commit is contained in:
Simon Sessingø
2021-03-31 03:00:16 +02:00
committed by GitHub
8 changed files with 239 additions and 17 deletions
+73 -7
View File
@@ -48,6 +48,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Namespaces](#namespaces) - [Namespaces](#namespaces)
- [Subdomain-routing](#subdomain-routing) - [Subdomain-routing](#subdomain-routing)
- [Route prefixes](#route-prefixes) - [Route prefixes](#route-prefixes)
- [Partial groups](#partial-groups)
- [Form Method Spoofing](#form-method-spoofing) - [Form Method Spoofing](#form-method-spoofing)
- [Accessing The Current Route](#accessing-the-current-route) - [Accessing The Current Route](#accessing-the-current-route)
- [Other examples](#other-examples) - [Other examples](#other-examples)
@@ -83,6 +84,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Advanced](#advanced) - [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering) - [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip) - [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting) - [Url rewriting](#url-rewriting)
- [Changing current route](#changing-current-route) - [Changing current route](#changing-current-route)
- [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically) - [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically)
@@ -467,7 +469,8 @@ SimpleRouter::get('/posts/{post}/comments/{comment}', function ($postId, $commen
}); });
``` ```
**Note:** Route parameters are always encased within {} braces and should consist of alphabetic characters. Route parameters may not contain a - character. Use an underscore (_) instead. **Note:** Route parameters are always encased within `{` `}` braces and should consist of alphabetic characters. Route parameters can only contain certain characters like `A-Z`, `a-z`, `0-9`, `-` and `_`.
If your route contain other characters, please see [Custom regex for matching parameters](#custom-regex-for-matching-parameters).
### Optional parameters ### Optional parameters
@@ -682,6 +685,27 @@ SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
}); });
``` ```
## Partial groups
Partial router groups has the same benefits as a normal group, but **are only rendered once the url has matched**
in contrast to a normal group which are always rendered in order to retrieve it's child routes.
Partial groups are therefore more like a hybrid of a traditional route with the benefits of a group.
This can be extremely useful in situations where you only want special routes to be added, but only when a certain criteria or logic has been met.
**NOTE:** Use partial groups with caution as routes added within are only rendered and available once the url of the partial-group has matched.
This can cause `url()` not to find urls for the routes added within before the partial-group has been matched and is rendered.
**Example:**
```php
SimpleRouter::partialGroup('/plugin/{name}', function ($plugin) {
// Add routes from plugin
});
```
## Form Method Spoofing ## Form Method Spoofing
HTML forms do not support `PUT`, `PATCH` or `DELETE` actions. So, when defining `PUT`, `PATCH` or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method: HTML forms do not support `PUT`, `PATCH` or `DELETE` actions. So, when defining `PUT`, `PATCH` or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method:
@@ -1243,7 +1267,7 @@ All event callbacks will retrieve a `EventArgument` object as parameter. This ob
| `EVENT_ALL` | - | Fires when a event is triggered. | | `EVENT_ALL` | - | Fires when a event is triggered. |
| `EVENT_INIT` | - | Fires when router is initializing and before routes are loaded. | | `EVENT_INIT` | - | Fires when router is initializing and before routes are loaded. |
| `EVENT_LOAD` | `loadedRoutes` | Fires when all routes has been loaded and rendered, just before the output is returned. | | `EVENT_LOAD` | `loadedRoutes` | Fires when all routes has been loaded and rendered, just before the output is returned. |
| `EVENT_ADD_ROUTE` | `route` | Fires when route is added to the router. | | `EVENT_ADD_ROUTE` | `route`<br>`isSubRoute` | Fires when route is added to the router. `isSubRoute` is true when sub-route is rendered. |
| `EVENT_REWRITE` | `rewriteUrl`<br>`rewriteRoute` | Fires when a url-rewrite is and just before the routes are re-initialized. | | `EVENT_REWRITE` | `rewriteUrl`<br>`rewriteRoute` | Fires when a url-rewrite is and just before the routes are re-initialized. |
| `EVENT_BOOT` | `bootmanagers` | Fires when the router is booting. This happens just before boot-managers are rendered and before any routes has been loaded. | | `EVENT_BOOT` | `bootmanagers` | Fires when the router is booting. This happens just before boot-managers are rendered and before any routes has been loaded. |
| `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. | | `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. |
@@ -1373,19 +1397,19 @@ This behavior can be easily disabled by setting `SimpleRouter::enableMultiRouteR
## Restrict access to IP ## Restrict access to IP
You can white- and blacklist access to IP's using the build in `IpRestrictAccess` middleware. You can white and/or blacklist access to IP's using the build in `IpRestrictAccess` middleware.
Create your own custom Middleware and extend the `IpRestrictAccess` class. Create your own custom Middleware and extend the `IpRestrictAccess` class.
The `IpRestrictAccess` class contains two properties `ipBlacklist` and `ipWhitelist` that can be added The `IpRestrictAccess` class contains two properties `ipBlacklist` and `ipWhitelist` that can be added to your middleware to change which IP's that have access to your routes.
to your middleware to change which IP's that have restricted access.
You can use `*` to restrict access to a range of ips. You can use `*` to restrict access to a range of ips.
```php ```php
use \Pecee\Http\Middleware\IpRestrictAccess; use \Pecee\Http\Middleware\IpRestrictAccess;
class IpBlockerMiddleware extends IpRestrictAccess { class IpBlockerMiddleware extends IpRestrictAccess
{
protected $ipBlacklist = [ protected $ipBlacklist = [
'5.5.5.5', '5.5.5.5',
@@ -1399,7 +1423,49 @@ class IpBlockerMiddleware extends IpRestrictAccess {
} }
``` ```
You can add the middleware to multiple routes by adding your [middleware to a groups](#middleware). You can add the middleware to multiple routes by adding your [middleware to a group](#middleware).
## Setting custom base path
Sometimes it can be useful to add a custom base path to all of the routes added.
This can easily be done by taking advantage of the [Event Handlers](#events) support of the project.
```php
$basePath = '/basepath/';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use($basePath) {
// Make sure url is alway correct
$basePath = rtrim($basePath, '/');
$route = $event->route;
// Skip routes added by group as these will inherit the url
if(!$event->isSubRoute) {
return;
}
switch (true) {
case $route instanceof ILoadableRoute:
$route->setUrl($basePath . $route->getUrl());
break;
case $route instanceof IGroupRoute:
$route->setPrefix($basePath . $route->getPrefix());
break;
}
});
$results = [];
TestRouter::addEventHandler($eventHandler);
```
In the example shown above, we create a new `EVENT_ADD_ROUTE` event that triggers, when a new route is added.
We skip all subroutes as these will inherit the url from their parent. Then, if the route is a group, we change the prefix
otherwise we change the url.
## Url rewriting ## Url rewriting
+29 -5
View File
@@ -12,7 +12,17 @@ class BaseCsrfVerifier implements IMiddleware
public const POST_KEY = 'csrf_token'; public const POST_KEY = 'csrf_token';
public const HEADER_KEY = 'X-CSRF-TOKEN'; public const HEADER_KEY = 'X-CSRF-TOKEN';
/**
* Urls to ignore. You can use * to exclude all sub-urls on a given path.
* For example: /admin/*
* @var array|null
*/
protected $except; protected $except;
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected $include;
protected $tokenProvider; protected $tokenProvider;
/** /**
@@ -34,11 +44,7 @@ class BaseCsrfVerifier implements IMiddleware
return false; return false;
} }
$max = count($this->except) - 1; foreach($this->except as $url) {
for ($i = $max; $i >= 0; $i--) {
$url = $this->except[$i];
$url = rtrim($url, '/'); $url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') { if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*'); $url = rtrim($url, '*');
@@ -48,6 +54,24 @@ class BaseCsrfVerifier implements IMiddleware
} }
if ($skip === true) { if ($skip === true) {
if($this->include !== null && count($this->include) > 0) {
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !($includeUrl === $request->getUrl()->getOriginalUrl());
}
}
if($skip === false) {
continue;
}
return true; return true;
} }
} }
+4
View File
@@ -391,6 +391,10 @@ class Request
if ($this->url->getHost() === null) { if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost()); $this->url->setHost((string)$this->getHost());
} }
if($this->isSecure() === true) {
$this->url->setScheme('https');
}
} }
/** /**
@@ -1,8 +1,4 @@
<?php <?php
/**
* @deprecated This class is deprecated and will be removed in future versions.
* @see \Pecee\SimpleRouter\Route\RouteGroup
*/
namespace Pecee\SimpleRouter\Route; namespace Pecee\SimpleRouter\Route;
class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute
+1 -1
View File
@@ -161,6 +161,7 @@ class Router
{ {
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [ $this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route, 'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]); ]);
/* /*
@@ -184,7 +185,6 @@ class Router
*/ */
protected function renderAndProcess(IRoute $route): void protected function renderAndProcess(IRoute $route): void
{ {
$this->isProcessingRoute = true; $this->isProcessingRoute = true;
$route->renderRoute($this->request, $this); $route->renderRoute($this->request, $this);
$this->isProcessingRoute = false; $this->isProcessingRoute = false;
@@ -0,0 +1,66 @@
<?php
require_once 'Dummy/CsrfVerifier/DummyCsrfVerifier.php';
require_once 'Dummy/Security/SilentTokenProvider.php';
class CsrfVerifierTest extends \PHPUnit\Framework\TestCase
{
public function testTokenPass()
{
global $_POST;
$tokenProvider = new SilentTokenProvider();
$_POST[DummyCsrfVerifier::POST_KEY] = $tokenProvider->getToken();
TestRouter::router()->reset();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
// If handle doesn't throw exception, the test has passed
$this->assertTrue(true);
}
public function testTokenFail()
{
$this->expectException(\Pecee\Http\Middleware\Exceptions\TokenMismatchException::class);
global $_POST;
$tokenProvider = new SilentTokenProvider();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
}
public function testExcludeInclude()
{
$router = TestRouter::router();
$csrf = new DummyCsrfVerifier();
$request = $router->getRequest();
$request->setUrl(new \Pecee\Http\Url('/exclude-page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
}
}
@@ -0,0 +1,18 @@
<?php
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [
'/exclude-page',
'/exclude-all/*',
];
protected $include = [
'/exclude-all/include-page',
];
public function testSkip(\Pecee\Http\Request $request) {
return $this->skip($request);
}
}
@@ -103,4 +103,52 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
} }
public function testCustomBasePath() {
$basePath = '/basepath/';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $data) use($basePath) {
// Add basepath
$basePath = rtrim($basePath, '/');
// Skip routes added by group
if($data->isSubRoute === false) {
switch (true) {
case $data->route instanceof \Pecee\SimpleRouter\Route\ILoadableRoute:
$data->route->setUrl($basePath . $data->route->getUrl());
break;
case $data->route instanceof \Pecee\SimpleRouter\Route\IGroupRoute:
$data->route->setPrefix($basePath . $data->route->getPrefix());
break;
}
}
});
$results = [];
TestRouter::addEventHandler($eventHandler);
TestRouter::get('/about', function() use(&$results) {
$results[] = 'about';
});
TestRouter::group(['prefix' => '/admin'], function() use(&$results) {
TestRouter::get('/', function() use(&$results) {
$results[] = 'admin';
});
});
TestRouter::router()->setRenderMultipleRoutes(false);
TestRouter::debugNoReset('/basepath/about');
TestRouter::debugNoReset('/basepath/admin');
$this->assertEquals(['about', 'admin'], $results);
}
} }