mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge pull request #7052 from sminnee/director-middleware
Allow application of HTTPMiddleware to Director.
This commit is contained in:
commit
cf758ddd4f
@ -261,7 +261,7 @@ mappings:
|
|||||||
Cookie_Backend: SilverStripe\Control\Cookie_Backend
|
Cookie_Backend: SilverStripe\Control\Cookie_Backend
|
||||||
CookieJar: SilverStripe\Control\CookieJar
|
CookieJar: SilverStripe\Control\CookieJar
|
||||||
Director: SilverStripe\Control\Director
|
Director: SilverStripe\Control\Director
|
||||||
FlushRequestFilter: SilverStripe\Control\FlushRequestFilter
|
FlushRequestFilter: SilverStripe\Control\Middleware\FlushMiddleware
|
||||||
HTTP: SilverStripe\Control\HTTP
|
HTTP: SilverStripe\Control\HTTP
|
||||||
SS_HTTPRequest: SilverStripe\Control\HTTPRequest
|
SS_HTTPRequest: SilverStripe\Control\HTTPRequest
|
||||||
SS_HTTPResponse: SilverStripe\Control\HTTPResponse
|
SS_HTTPResponse: SilverStripe\Control\HTTPResponse
|
||||||
@ -496,8 +496,8 @@ mappings:
|
|||||||
TestRequestFilter: SilverStripe\Control\Tests\DirectorTest\TestRequestFilter
|
TestRequestFilter: SilverStripe\Control\Tests\DirectorTest\TestRequestFilter
|
||||||
DirectorTestRequest_Controller: SilverStripe\Control\Tests\DirectorTest\TestController
|
DirectorTestRequest_Controller: SilverStripe\Control\Tests\DirectorTest\TestController
|
||||||
FakeController: SilverStripe\Control\Tests\FakeController
|
FakeController: SilverStripe\Control\Tests\FakeController
|
||||||
FlushRequestFilterTest: SilverStripe\Control\Tests\FlushRequestFilterTest
|
FlushRequestFilterTest: SilverStripe\Control\Tests\FlushMiddlewareTest
|
||||||
FlushRequestFilterTest_Flushable: SilverStripe\Control\Tests\FlushRequestFilterTest\TestFlushable
|
FlushRequestFilterTest_Flushable: SilverStripe\Control\Tests\FlushMiddlewareTest\TestFlushable
|
||||||
HTTPRequestTest: SilverStripe\Control\Tests\HTTPRequestTest
|
HTTPRequestTest: SilverStripe\Control\Tests\HTTPRequestTest
|
||||||
HTTPResponseTest: SilverStripe\Control\Tests\HTTPResponseTest
|
HTTPResponseTest: SilverStripe\Control\Tests\HTTPResponseTest
|
||||||
HTTPTest: SilverStripe\Control\Tests\HTTPTest
|
HTTPTest: SilverStripe\Control\Tests\HTTPTest
|
||||||
|
@ -2,10 +2,17 @@
|
|||||||
Name: requestprocessors
|
Name: requestprocessors
|
||||||
---
|
---
|
||||||
SilverStripe\Core\Injector\Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
FlushRequestFilter:
|
SilverStripe\Control\Director:
|
||||||
class: SilverStripe\Control\FlushRequestFilter
|
|
||||||
SilverStripe\Control\RequestProcessor:
|
|
||||||
properties:
|
properties:
|
||||||
filters:
|
Middlewares:
|
||||||
- '%$FlushRequestFilter'
|
TrustedProxyMiddleware: %$SilverStripe\Control\Middleware\TrustedProxyMiddleware
|
||||||
|
AllowedHostsMiddleware: %$SilverStripe\Control\Middleware\AllowedHostsMiddleware
|
||||||
|
SessionMiddleware: %$SilverStripe\Control\Middleware\SessionMiddleware
|
||||||
|
RequestProcessor: %$SilverStripe\Control\RequestProcessor
|
||||||
|
FlushMiddleware: %$SilverStripe\Control\Middleware\FlushMiddleware
|
||||||
|
SilverStripe\Control\AllowedHostsMiddleware:
|
||||||
|
properties:
|
||||||
|
AllowedHosts: "`SS_ALLOWED_HOSTS`"
|
||||||
|
SilverStripe\Control\TrustedProxyMiddleware:
|
||||||
|
properties:
|
||||||
|
TrustedProxyIPs: "`SS_TRUSTED_PROXY_IPS`"
|
||||||
|
@ -18,15 +18,17 @@ SilverStripe\Core\Injector\Injector:
|
|||||||
alc: %$SilverStripe\Security\MemberAuthenticator\CookieAuthenticationHandler
|
alc: %$SilverStripe\Security\MemberAuthenticator\CookieAuthenticationHandler
|
||||||
---
|
---
|
||||||
Name: coresecurity
|
Name: coresecurity
|
||||||
|
After:
|
||||||
|
- requestprocessors
|
||||||
---
|
---
|
||||||
SilverStripe\Core\Injector\Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
SilverStripe\Security\AuthenticationRequestFilter:
|
SilverStripe\Control\Director:
|
||||||
|
properties:
|
||||||
|
Middlewares:
|
||||||
|
AuthenticationMiddleware: %$SilverStripe\Security\AuthenticationMiddleware
|
||||||
|
SilverStripe\Security\AuthenticationMiddleware:
|
||||||
properties:
|
properties:
|
||||||
AuthenticationHandler: %$SilverStripe\Security\AuthenticationHandler
|
AuthenticationHandler: %$SilverStripe\Security\AuthenticationHandler
|
||||||
SilverStripe\Control\RequestProcessor:
|
|
||||||
properties:
|
|
||||||
filters:
|
|
||||||
- %$SilverStripe\Security\AuthenticationRequestFilter
|
|
||||||
SilverStripe\Security\Security:
|
SilverStripe\Security\Security:
|
||||||
properties:
|
properties:
|
||||||
Authenticators:
|
Authenticators:
|
||||||
|
@ -80,9 +80,6 @@ SilverStripe core environment variables are listed here, though you're free to d
|
|||||||
| `SS_ERROR_LOG` | Relative path to the log file. |
|
| `SS_ERROR_LOG` | Relative path to the log file. |
|
||||||
| `SS_PROTECTED_ASSETS_PATH` | Path to secured assets - defaults to ASSET_PATH/.protected |
|
| `SS_PROTECTED_ASSETS_PATH` | Path to secured assets - defaults to ASSET_PATH/.protected |
|
||||||
| `SS_DATABASE_MEMORY` | Used for SQLite3 DBs |
|
| `SS_DATABASE_MEMORY` | Used for SQLite3 DBs |
|
||||||
| `SS_TRUSTED_PROXY_PROTOCOL_HEADER` | Used to define the proxy header to be used to determine HTTPS status |
|
|
||||||
| `SS_TRUSTED_PROXY_IP_HEADER` | Used to define the proxy header to be used to determine request IPs |
|
|
||||||
| `SS_TRUSTED_PROXY_HOST_HEADER` | Used to define the proxy header to be used to determine the requested host name |
|
|
||||||
| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from. If left blank no proxy headers are trusted. Can be set to 'none' (trust none) or '*' (trust all) |
|
| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from. If left blank no proxy headers are trusted. Can be set to 'none' (trust none) or '*' (trust all) |
|
||||||
| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to |
|
| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to |
|
||||||
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
|
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
|
||||||
|
118
docs/en/02_Developer_Guides/02_Controllers/05_Middlewares.md
Normal file
118
docs/en/02_Developer_Guides/02_Controllers/05_Middlewares.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
title: HTTP Middlewares
|
||||||
|
summary: Create objects for modifying request and response objects across controllers.
|
||||||
|
|
||||||
|
# HTTP Middlewares
|
||||||
|
|
||||||
|
HTTP Middlewares allow you to put code that will run before or after. These might be used for
|
||||||
|
authentication, logging, caching, request processing, and many other purposes. Note this interface
|
||||||
|
replaces the SilverStripe 3 interface, [api:RequestFilter], which still works but is deprecated.
|
||||||
|
|
||||||
|
To create a middleware class, implement `SilverStripe\Control\HTTPMiddleware` and define the
|
||||||
|
`process(HTTPRequest $request, callbale $delegate)` method. You can do anything you like in this
|
||||||
|
method, but to continue normal execution, you should call `$response = $delegate($request)`
|
||||||
|
at some point in this method.
|
||||||
|
|
||||||
|
In addition, you should return an HTTPResponse object. In normal cases, this should be the
|
||||||
|
$response object returned by `$delegate`, perhaps with some modification. However, sometimes you
|
||||||
|
will deliberately return a different response, e.g. an error response or a redirection.
|
||||||
|
|
||||||
|
**mysite/code/CustomMiddleware.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddleware
|
||||||
|
|
||||||
|
class CustomMiddleware implements HTTPMiddleware {
|
||||||
|
|
||||||
|
public $Secret = 'SECRET';
|
||||||
|
|
||||||
|
public function process(HTTPRequest $request, callable $delegate) {
|
||||||
|
|
||||||
|
// You can break execution by not calling $delegate.
|
||||||
|
if ($request->getHeader('X-Special-Header') !== $this->Secret) {
|
||||||
|
return new HTTPResponse('You missed the special header', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can modify the request before
|
||||||
|
// For example, this might force JSON responses
|
||||||
|
$request->addHeader('Accept', 'application/json');
|
||||||
|
|
||||||
|
// If you want normal behaviour to occur, make sure you call $delegate($request)
|
||||||
|
$response = $delegate($request);
|
||||||
|
|
||||||
|
// You can modify the response after it has been generated
|
||||||
|
$response->addHeader('X-Middleware-Applied', 'CustomMiddleware')
|
||||||
|
|
||||||
|
// Don't forget to the return the response!
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Once you have created your middleware class, you must attach it to the Director config to make
|
||||||
|
use of it.
|
||||||
|
|
||||||
|
## Global middleware
|
||||||
|
|
||||||
|
By adding the service or class name to the Director::Middlewares property via injector,
|
||||||
|
array, a middleware will be executed on every request:
|
||||||
|
|
||||||
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
|
|
||||||
|
:::yml
|
||||||
|
---
|
||||||
|
Name: myrequestprocessors
|
||||||
|
After:
|
||||||
|
- requestprocessors
|
||||||
|
---
|
||||||
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
SilverStripe\Control\Director:
|
||||||
|
properties:
|
||||||
|
Middlewares:
|
||||||
|
CustomMiddleware: %$CustomMiddleware
|
||||||
|
|
||||||
|
|
||||||
|
Because these are service names, you can configure properties into a custom service if you would
|
||||||
|
like:
|
||||||
|
|
||||||
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
|
:::yml
|
||||||
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
SilverStripe\Control\Director:
|
||||||
|
properties:
|
||||||
|
Middlewares:
|
||||||
|
CustomMiddleware: %$ConfiguredMiddleware
|
||||||
|
ConfiguredMiddleware:
|
||||||
|
class: 'CustomMiddleware'
|
||||||
|
properties:
|
||||||
|
Secret: "DIFFERENT-ONE"
|
||||||
|
|
||||||
|
## Route-specific middleware
|
||||||
|
|
||||||
|
Alternatively, you can apply middlewares to a specific route. These will be processed after the
|
||||||
|
global middlewares. You can do this by using the `RequestHandlerMiddlewareAdapter` class
|
||||||
|
as a replacement for your controller, and register it as a service with a `Middlewares`
|
||||||
|
property. The controller which does the work should be registered under the
|
||||||
|
`RequestHandler` property.
|
||||||
|
|
||||||
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
|
:::yml
|
||||||
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
SpecialRouteMiddleware:
|
||||||
|
class: SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter
|
||||||
|
properties
|
||||||
|
RequestHandler: %$MyController
|
||||||
|
Middlewares:
|
||||||
|
- %$CustomMiddleware
|
||||||
|
- %$AnotherMiddleware
|
||||||
|
SilverStripe\Control\Director:
|
||||||
|
rules:
|
||||||
|
special\section:
|
||||||
|
Controller: %$SpecialRouteMiddleware
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:SilverStripe\Control\HTTPMiddleware]
|
@ -1,57 +0,0 @@
|
|||||||
title: Request Filters
|
|
||||||
summary: Create objects for modifying request and response objects across controllers.
|
|
||||||
|
|
||||||
# Request Filters
|
|
||||||
|
|
||||||
[api:RequestFilter] is an interface that provides two key methods. `preRequest` and `postRequest`. These methods are
|
|
||||||
executed before and after a request occurs to give developers a hook to modify any global state, add request tracking or
|
|
||||||
perform operations wrapped around responses and request objects. A `RequestFilter` is defined as:
|
|
||||||
|
|
||||||
**mysite/code/CustomRequestFilter.php**
|
|
||||||
|
|
||||||
:::php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
class CustomRequestFilter implements RequestFilter {
|
|
||||||
|
|
||||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model) {
|
|
||||||
|
|
||||||
// if(!something) {
|
|
||||||
// By returning 'false' from the preRequest method, request execution will be stopped from continuing.
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// we can also set any properties onto the request that we need or add any tracking
|
|
||||||
// Foo::bar();
|
|
||||||
|
|
||||||
// return true to continue processing.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model) {
|
|
||||||
// response is about to be sent.
|
|
||||||
// any modifications or tracking to be done?
|
|
||||||
// Foo::unbar();
|
|
||||||
|
|
||||||
// return true to send the response.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
After defining the `RequestFilter`, add it as an allowed `filter` through the [Configuration API](../configuration)
|
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
|
||||||
|
|
||||||
:::yml
|
|
||||||
Injector:
|
|
||||||
RequestProcessor:
|
|
||||||
properties:
|
|
||||||
filters:
|
|
||||||
- '%$CustomRequestFilter'
|
|
||||||
|
|
||||||
## API Documentation
|
|
||||||
|
|
||||||
* [api:RequestFilter]
|
|
||||||
* [api:RequestProcessor]
|
|
||||||
|
|
||||||
|
|
@ -556,6 +556,18 @@ In order to prevent this kind of attack, it's necessary to whitelist trusted pro
|
|||||||
server IPs using the SS_TRUSTED_PROXY_IPS define in your `.env`.
|
server IPs using the SS_TRUSTED_PROXY_IPS define in your `.env`.
|
||||||
|
|
||||||
SS_TRUSTED_PROXY_IPS="127.0.0.1,192.168.0.1"
|
SS_TRUSTED_PROXY_IPS="127.0.0.1,192.168.0.1"
|
||||||
|
|
||||||
|
If you wish to change the headers that are used to find the proxy information, you should reconfigure the
|
||||||
|
TrustedProxyMiddleware service:
|
||||||
|
|
||||||
|
:::yml
|
||||||
|
SilverStripe\Control\TrustedProxyMiddleware:
|
||||||
|
properties:
|
||||||
|
ProxyHostHeaders: X-Forwarded-Host
|
||||||
|
ProxySchemeHeaders: X-Forwarded-Protocol
|
||||||
|
ProxyIPHeaders: X-Forwarded-Ip
|
||||||
|
|
||||||
|
|
||||||
SS_TRUSTED_PROXY_HOST_HEADER="HTTP_X_FORWARDED_HOST"
|
SS_TRUSTED_PROXY_HOST_HEADER="HTTP_X_FORWARDED_HOST"
|
||||||
SS_TRUSTED_PROXY_IP_HEADER="HTTP_X_FORWARDED_FOR"
|
SS_TRUSTED_PROXY_IP_HEADER="HTTP_X_FORWARDED_FOR"
|
||||||
SS_TRUSTED_PROXY_PROTOCOL_HEADER="HTTP_X_FORWARDED_PROTOCOL"
|
SS_TRUSTED_PROXY_PROTOCOL_HEADER="HTTP_X_FORWARDED_PROTOCOL"
|
||||||
|
@ -6,7 +6,7 @@ summary: Allows a class to define it's own flush functionality.
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Allows a class to define it's own flush functionality, which is triggered when `flush=1` is requested in the URL.
|
Allows a class to define it's own flush functionality, which is triggered when `flush=1` is requested in the URL.
|
||||||
[api:FlushRequestFilter] is run before a request is made, calling `flush()` statically on all
|
[api:FlushMiddleware] is run before a request is made, calling `flush()` statically on all
|
||||||
implementors of [api:Flushable].
|
implementors of [api:Flushable].
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -1327,6 +1327,9 @@ After (`mysite/_config/config.yml`):
|
|||||||
* 'BlockUntrustedIPS' env setting has been removed.
|
* 'BlockUntrustedIPS' env setting has been removed.
|
||||||
All IPs are untrusted unless `SS_TRUSTED_PROXY_IPS` is set to '*'
|
All IPs are untrusted unless `SS_TRUSTED_PROXY_IPS` is set to '*'
|
||||||
See [Environment Management docs](/getting-started/environment_management/) for full details.
|
See [Environment Management docs](/getting-started/environment_management/) for full details.
|
||||||
|
* `SS_TRUSTED_PROXY_HOST_HEADER`, `SS_TRUSTED_PROXY_PROTOCOL_HEADER`, and `SS_TRUSTED_PROXY_IP_HEADER`
|
||||||
|
are no longer supported. These settings should go into the Injector service configuration for
|
||||||
|
TrustedProxyMiddleware instead.
|
||||||
* `MODULES_PATH` removed
|
* `MODULES_PATH` removed
|
||||||
* `MODULES_DIR` removed
|
* `MODULES_DIR` removed
|
||||||
* `SS_HOST` removed. Use `SS_BASE_URL` instead.
|
* `SS_HOST` removed. Use `SS_BASE_URL` instead.
|
||||||
@ -1341,7 +1344,9 @@ After (`mysite/_config/config.yml`):
|
|||||||
* `findAnAdministrator` use `DefaultAdminService::findOrCreateDefaultAdmin()` instead
|
* `findAnAdministrator` use `DefaultAdminService::findOrCreateDefaultAdmin()` instead
|
||||||
* `Member` methods deprecated:
|
* `Member` methods deprecated:
|
||||||
* `checkPassword`. Use Authenticator::checkPassword() instead
|
* `checkPassword`. Use Authenticator::checkPassword() instead
|
||||||
* `RequestFilter` changed. $session and $dataModel variables removed from preRequest / postRequest
|
* `RequestFilter` has been deprecated in favour of
|
||||||
|
[HTTPMiddleware](/developer_guides/controllers/middlewares). Also the legacy RequestFilter
|
||||||
|
API has changed: $session and $dataModel variables removed from preRequest / postRequest.
|
||||||
|
|
||||||
#### <a name="overview-general-removed"></a>General and Core Removed API
|
#### <a name="overview-general-removed"></a>General and Core Removed API
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class ContentNegotiator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::config()->enabled) {
|
if (static::config()->get('enabled')) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return (substr($response->getBody(), 0, 5) == '<' . '?xml');
|
return (substr($response->getBody(), 0, 5) == '<' . '?xml');
|
||||||
@ -106,7 +106,7 @@ class ContentNegotiator
|
|||||||
);
|
);
|
||||||
$q = array();
|
$q = array();
|
||||||
if (headers_sent()) {
|
if (headers_sent()) {
|
||||||
$chosenFormat = static::config()->default_format;
|
$chosenFormat = static::config()->get('default_format');
|
||||||
} elseif (isset($_GET['forceFormat'])) {
|
} elseif (isset($_GET['forceFormat'])) {
|
||||||
$chosenFormat = $_GET['forceFormat'];
|
$chosenFormat = $_GET['forceFormat'];
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control;
|
||||||
|
|
||||||
use SilverStripe\CMS\Model\SiteTree;
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddlewareAware;
|
||||||
use SilverStripe\Core\Config\Configurable;
|
use SilverStripe\Core\Config\Configurable;
|
||||||
use SilverStripe\Core\Environment;
|
use SilverStripe\Core\Environment;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Core\Kernel;
|
use SilverStripe\Core\Kernel;
|
||||||
use SilverStripe\Dev\Deprecation;
|
use SilverStripe\Dev\Deprecation;
|
||||||
@ -16,19 +18,21 @@ use SilverStripe\View\TemplateGlobalProvider;
|
|||||||
/**
|
/**
|
||||||
* Director is responsible for processing URLs, and providing environment information.
|
* Director is responsible for processing URLs, and providing environment information.
|
||||||
*
|
*
|
||||||
* The most important part of director is {@link Director::direct()}, which is passed a URL and will
|
* The most important part of director is {@link Director::handleRequest()}, which is passed an HTTPRequest and will
|
||||||
* execute the appropriate controller.
|
* execute the appropriate controller.
|
||||||
*
|
*
|
||||||
* Director also has a number of static methods that provide information about the environment, such as
|
* Director also has a number of static methods that provide information about the environment, such as
|
||||||
* {@link Director::$environment_type}.
|
* {@link Director::$environment_type}.
|
||||||
*
|
*
|
||||||
* @see Director::direct()
|
* @see Director::handleRequest()
|
||||||
* @see Director::$rules
|
* @see Director::$rules
|
||||||
* @see Director::$environment_type
|
* @see Director::$environment_type
|
||||||
*/
|
*/
|
||||||
class Director implements TemplateGlobalProvider
|
class Director implements TemplateGlobalProvider
|
||||||
{
|
{
|
||||||
use Configurable;
|
use Configurable;
|
||||||
|
use Injectable;
|
||||||
|
use HTTPMiddlewareAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies this url is relative to the base.
|
* Specifies this url is relative to the base.
|
||||||
@ -100,66 +104,10 @@ class Director implements TemplateGlobalProvider
|
|||||||
protected static $environment_type;
|
protected static $environment_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the given URL, creating the appropriate controller and executing it.
|
* Test a URL request, returning a response object. This method is a wrapper around
|
||||||
*
|
* Director::handleRequest() to assist with functional testing. It will execute the URL given, and
|
||||||
* Request processing is handled as follows:
|
|
||||||
* - Director::direct() creates a new HTTPResponse object and passes this to
|
|
||||||
* Director::handleRequest().
|
|
||||||
* - Director::handleRequest($request) checks each of the Director rules and identifies a controller
|
|
||||||
* to handle this request.
|
|
||||||
* - Controller::handleRequest($request) is then called. This will find a rule to handle the URL,
|
|
||||||
* and call the rule handling method.
|
|
||||||
* - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
|
|
||||||
* returns a RequestHandler object.
|
|
||||||
*
|
|
||||||
* In addition to request processing, Director will manage the session, and perform the output of
|
|
||||||
* the actual response to the browser.
|
|
||||||
*
|
|
||||||
* @uses handleRequest() rule-lookup logic is handled by this.
|
|
||||||
* @uses TestController::handleRequest() This handles the page logic for a Director::direct() call.
|
|
||||||
* @param HTTPRequest $request
|
|
||||||
* @return HTTPResponse
|
|
||||||
* @throws HTTPResponse_Exception
|
|
||||||
*/
|
|
||||||
public static function direct(HTTPRequest $request)
|
|
||||||
{
|
|
||||||
// check allowed hosts
|
|
||||||
if (getenv('SS_ALLOWED_HOSTS') && !static::is_cli()) {
|
|
||||||
$allowedHosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
|
|
||||||
if (!in_array(static::host(), $allowedHosts)) {
|
|
||||||
return new HTTPResponse('Invalid Host', 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-request
|
|
||||||
$output = RequestProcessor::singleton()->preRequest($request);
|
|
||||||
if ($output === false) {
|
|
||||||
return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate output
|
|
||||||
$result = static::handleRequest($request);
|
|
||||||
|
|
||||||
// Save session data. Note that save() will start/resume the session if required.
|
|
||||||
$request->getSession()->save();
|
|
||||||
|
|
||||||
// Post-request handling
|
|
||||||
$postRequest = RequestProcessor::singleton()->postRequest($request, $result);
|
|
||||||
if ($postRequest === false) {
|
|
||||||
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test a URL request, returning a response object. This method is the counterpart of
|
|
||||||
* Director::direct() that is used in functional testing. It will execute the URL given, and
|
|
||||||
* return the result as an HTTPResponse object.
|
* return the result as an HTTPResponse object.
|
||||||
*
|
*
|
||||||
* @uses TestController::handleRequest() Handles the page logic for a Director::direct() call.
|
|
||||||
*
|
|
||||||
* @param string $url The URL to visit.
|
* @param string $url The URL to visit.
|
||||||
* @param array $postVars The $_POST & $_FILES variables.
|
* @param array $postVars The $_POST & $_FILES variables.
|
||||||
* @param array|Session $session The {@link Session} object representing the current session.
|
* @param array|Session $session The {@link Session} object representing the current session.
|
||||||
@ -188,7 +136,7 @@ class Director implements TemplateGlobalProvider
|
|||||||
) {
|
) {
|
||||||
return static::mockRequest(
|
return static::mockRequest(
|
||||||
function (HTTPRequest $request) {
|
function (HTTPRequest $request) {
|
||||||
return static::direct($request);
|
return Director::singleton()->handleRequest($request);
|
||||||
},
|
},
|
||||||
$url,
|
$url,
|
||||||
$postVars,
|
$postVars,
|
||||||
@ -341,17 +289,41 @@ class Director implements TemplateGlobalProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an HTTP request, defined with a HTTPRequest object.
|
* Process the given URL, creating the appropriate controller and executing it.
|
||||||
|
*
|
||||||
|
* Request processing is handled as follows:
|
||||||
|
* - Director::handleRequest($request) checks each of the Director rules and identifies a controller
|
||||||
|
* to handle this request.
|
||||||
|
* - Controller::handleRequest($request) is then called. This will find a rule to handle the URL,
|
||||||
|
* and call the rule handling method.
|
||||||
|
* - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
|
||||||
|
* returns a RequestHandler object.
|
||||||
|
*
|
||||||
|
* In addition to request processing, Director will manage the session, and perform the output of
|
||||||
|
* the actual response to the browser.
|
||||||
*
|
*
|
||||||
* @skipUpgrade
|
|
||||||
* @param HTTPRequest $request
|
* @param HTTPRequest $request
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
|
* @throws HTTPResponse_Exception
|
||||||
*/
|
*/
|
||||||
protected static function handleRequest(HTTPRequest $request)
|
public function handleRequest(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
|
Injector::inst()->registerService($request, HTTPRequest::class);
|
||||||
|
|
||||||
$rules = Director::config()->uninherited('rules');
|
$rules = Director::config()->uninherited('rules');
|
||||||
|
|
||||||
|
// Default handler - mo URL rules matched, so return a 404 error.
|
||||||
|
$handler = function () {
|
||||||
|
return new HTTPResponse('No URL rule was matched', 404);
|
||||||
|
};
|
||||||
|
|
||||||
foreach ($rules as $pattern => $controllerOptions) {
|
foreach ($rules as $pattern => $controllerOptions) {
|
||||||
|
// Match pattern
|
||||||
|
$arguments = $request->match($pattern, true);
|
||||||
|
if ($arguments == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Normalise route rule
|
// Normalise route rule
|
||||||
if (is_string($controllerOptions)) {
|
if (is_string($controllerOptions)) {
|
||||||
if (substr($controllerOptions, 0, 2) == '->') {
|
if (substr($controllerOptions, 0, 2) == '->') {
|
||||||
@ -360,41 +332,49 @@ class Director implements TemplateGlobalProvider
|
|||||||
$controllerOptions = array('Controller' => $controllerOptions);
|
$controllerOptions = array('Controller' => $controllerOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$request->setRouteParams($controllerOptions);
|
||||||
|
|
||||||
// Match pattern
|
// controllerOptions provide some default arguments
|
||||||
$arguments = $request->match($pattern, true);
|
$arguments = array_merge($controllerOptions, $arguments);
|
||||||
if ($arguments !== false) {
|
|
||||||
$request->setRouteParams($controllerOptions);
|
|
||||||
// controllerOptions provide some default arguments
|
|
||||||
$arguments = array_merge($controllerOptions, $arguments);
|
|
||||||
|
|
||||||
// Pop additional tokens from the tokenizer if necessary
|
// Pop additional tokens from the tokenizer if necessary
|
||||||
if (isset($controllerOptions['_PopTokeniser'])) {
|
if (isset($controllerOptions['_PopTokeniser'])) {
|
||||||
$request->shift($controllerOptions['_PopTokeniser']);
|
$request->shift($controllerOptions['_PopTokeniser']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle redirection
|
// Handler for redirection
|
||||||
if (isset($arguments['Redirect'])) {
|
if (isset($arguments['Redirect'])) {
|
||||||
|
$handler = function () use ($arguments) {
|
||||||
// Redirection
|
// Redirection
|
||||||
$response = new HTTPResponse();
|
$response = new HTTPResponse();
|
||||||
$response->redirect(static::absoluteURL($arguments['Redirect']));
|
$response->redirect(static::absoluteURL($arguments['Redirect']));
|
||||||
return $response;
|
return $response;
|
||||||
}
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the controller name
|
/** @var RequestHandler $controllerObj */
|
||||||
$controller = $arguments['Controller'];
|
$controllerObj = Injector::inst()->create($arguments['Controller']);
|
||||||
$controllerObj = Injector::inst()->create($controller);
|
|
||||||
|
|
||||||
|
// Handler for calling a controller
|
||||||
|
$handler = function (HTTPRequest $request) use ($controllerObj) {
|
||||||
try {
|
try {
|
||||||
return $controllerObj->handleRequest($request);
|
return $controllerObj->handleRequest($request);
|
||||||
} catch (HTTPResponse_Exception $responseException) {
|
} catch (HTTPResponse_Exception $responseException) {
|
||||||
return $responseException->getResponse();
|
return $responseException->getResponse();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No URL rules matched, so return a 404 error.
|
// Call the handler with the configured middlewares
|
||||||
return new HTTPResponse('No URL rule was matched', 404);
|
$response = $this->callMiddleware($request, $handler);
|
||||||
|
|
||||||
|
// Note that if a different request was previously registered, this will now be lost
|
||||||
|
// In these cases it's better to use Kernel::nest() prior to kicking off a nested request
|
||||||
|
Injector::inst()->unregisterNamedObject(HTTPRequest::class);
|
||||||
|
|
||||||
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -481,9 +461,10 @@ class Director implements TemplateGlobalProvider
|
|||||||
* - SERVER_NAME
|
* - SERVER_NAME
|
||||||
* - gethostname()
|
* - gethostname()
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function host()
|
public static function host(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
// Check if overridden by alternate_base_url
|
// Check if overridden by alternate_base_url
|
||||||
if ($baseURL = self::config()->get('alternate_base_url')) {
|
if ($baseURL = self::config()->get('alternate_base_url')) {
|
||||||
@ -494,18 +475,9 @@ class Director implements TemplateGlobalProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate proxy-specific headers
|
$request = static::currentRequest($request);
|
||||||
if (TRUSTED_PROXY) {
|
if ($request && ($host = $request->getHeader('Host'))) {
|
||||||
// Check headers to validate
|
return $host;
|
||||||
$headers = getenv('SS_TRUSTED_PROXY_HOST_HEADER')
|
|
||||||
? explode(',', getenv('SS_TRUSTED_PROXY_HOST_HEADER'))
|
|
||||||
: ['HTTP_X_FORWARDED_HOST']; // Backwards compatible defaults
|
|
||||||
foreach ($headers as $header) {
|
|
||||||
if (!empty($_SERVER[$header])) {
|
|
||||||
// Get the first host, in case there's multiple separated through commas
|
|
||||||
return strtok($_SERVER[$header], ',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check given header
|
// Check given header
|
||||||
@ -530,29 +502,32 @@ class Director implements TemplateGlobalProvider
|
|||||||
* Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
|
* Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
|
||||||
* variable isn't set.
|
* variable isn't set.
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return bool|string
|
* @return bool|string
|
||||||
*/
|
*/
|
||||||
public static function protocolAndHost()
|
public static function protocolAndHost(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
return static::protocol() . static::host();
|
return static::protocol($request) . static::host($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current protocol that the site is running under.
|
* Return the current protocol that the site is running under.
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function protocol()
|
public static function protocol(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
return (self::is_https()) ? 'https://' : 'http://';
|
return (self::is_https($request)) ? 'https://' : 'http://';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the site is running as under HTTPS.
|
* Return whether the site is running as under HTTPS.
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function is_https()
|
public static function is_https(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
// Check override from alternate_base_url
|
// Check override from alternate_base_url
|
||||||
if ($baseURL = self::config()->uninherited('alternate_base_url')) {
|
if ($baseURL = self::config()->uninherited('alternate_base_url')) {
|
||||||
@ -563,26 +538,10 @@ class Director implements TemplateGlobalProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
|
// Check the current request
|
||||||
// See https://support.microsoft.com/en-us/kb/307347
|
$request = static::currentRequest($request);
|
||||||
if (TRUSTED_PROXY) {
|
if ($request && ($scheme = $request->getScheme())) {
|
||||||
$headers = getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')
|
return $scheme === 'https';
|
||||||
? explode(',', getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER'))
|
|
||||||
: ['HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS'];
|
|
||||||
foreach ($headers as $header) {
|
|
||||||
$headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
|
|
||||||
if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check common $_SERVER
|
|
||||||
if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isset($_SERVER['SSL'])) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check default_base_url
|
// Check default_base_url
|
||||||
@ -842,9 +801,10 @@ class Director implements TemplateGlobalProvider
|
|||||||
* Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
|
* Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
|
||||||
* the URL.
|
* the URL.
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest|null $request
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function absoluteBaseURLWithAuth()
|
public static function absoluteBaseURLWithAuth(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
$login = "";
|
$login = "";
|
||||||
|
|
||||||
@ -852,7 +812,7 @@ class Director implements TemplateGlobalProvider
|
|||||||
$login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
|
$login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Director::protocol() . $login . static::host() . Director::baseURL();
|
return Director::protocol($request) . $login . static::host($request) . Director::baseURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -960,12 +920,14 @@ class Director implements TemplateGlobalProvider
|
|||||||
* Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
|
* Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
|
||||||
* jQuery or whether a manually set request-parameter 'ajax' is present.
|
* jQuery or whether a manually set request-parameter 'ajax' is present.
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function is_ajax()
|
public static function is_ajax(HTTPRequest $request = null)
|
||||||
{
|
{
|
||||||
if (Controller::has_curr()) {
|
$request = self::currentRequest($request);
|
||||||
return Controller::curr()->getRequest()->isAjax();
|
if ($request) {
|
||||||
|
return $request->isAjax();
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
isset($_REQUEST['ajax']) ||
|
isset($_REQUEST['ajax']) ||
|
||||||
@ -1046,4 +1008,20 @@ class Director implements TemplateGlobalProvider
|
|||||||
'BaseHref' => 'absoluteBaseURL', //@deprecated 3.0
|
'BaseHref' => 'absoluteBaseURL', //@deprecated 3.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to validate or check the current request object
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return HTTPRequest Request object if one is both current and valid
|
||||||
|
*/
|
||||||
|
protected static function currentRequest(HTTPRequest $request = null)
|
||||||
|
{
|
||||||
|
// Ensure we only use a registered HTTPRequest and don't
|
||||||
|
// incidentally construct a singleton
|
||||||
|
if (!$request && Injector::inst()->has(HTTPRequest::class)) {
|
||||||
|
$request = Injector::inst()->get(HTTPRequest::class);
|
||||||
|
}
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddlewareAware;
|
||||||
use SilverStripe\Core\Application;
|
use SilverStripe\Core\Application;
|
||||||
use SilverStripe\Control\HTTPMiddleware;
|
|
||||||
use SilverStripe\Core\Kernel;
|
use SilverStripe\Core\Kernel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,10 +11,7 @@ use SilverStripe\Core\Kernel;
|
|||||||
*/
|
*/
|
||||||
class HTTPApplication implements Application
|
class HTTPApplication implements Application
|
||||||
{
|
{
|
||||||
/**
|
use HTTPMiddlewareAware;
|
||||||
* @var HTTPMiddleware[]
|
|
||||||
*/
|
|
||||||
protected $middlewares = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Kernel
|
* @var Kernel
|
||||||
@ -26,54 +23,6 @@ class HTTPApplication implements Application
|
|||||||
$this->kernel = $kernel;
|
$this->kernel = $kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return HTTPMiddleware[]
|
|
||||||
*/
|
|
||||||
public function getMiddlewares()
|
|
||||||
{
|
|
||||||
return $this->middlewares;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param HTTPMiddleware[] $middlewares
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setMiddlewares($middlewares)
|
|
||||||
{
|
|
||||||
$this->middlewares = $middlewares;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param HTTPMiddleware $middleware
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function addMiddleware(HTTPMiddleware $middleware)
|
|
||||||
{
|
|
||||||
$this->middlewares[] = $middleware;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call middleware
|
|
||||||
*
|
|
||||||
* @param HTTPRequest $request
|
|
||||||
* @param callable $last Last config to call
|
|
||||||
* @return HTTPResponse
|
|
||||||
*/
|
|
||||||
protected function callMiddleware(HTTPRequest $request, $last)
|
|
||||||
{
|
|
||||||
// Reverse middlewares
|
|
||||||
$next = $last;
|
|
||||||
/** @var HTTPMiddleware $middleware */
|
|
||||||
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
|
|
||||||
$next = function ($request) use ($middleware, $next) {
|
|
||||||
return $middleware->process($request, $next);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return call_user_func($next, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the kernel for this application
|
* Get the kernel for this application
|
||||||
*
|
*
|
||||||
@ -96,9 +45,7 @@ class HTTPApplication implements Application
|
|||||||
|
|
||||||
// Ensure boot is invoked
|
// Ensure boot is invoked
|
||||||
return $this->execute($request, function (HTTPRequest $request) {
|
return $this->execute($request, function (HTTPRequest $request) {
|
||||||
// Start session and execute
|
return Director::singleton()->handleRequest($request);
|
||||||
$request->getSession()->init();
|
|
||||||
return Director::direct($request);
|
|
||||||
}, $flush);
|
}, $flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Control;
|
|||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
|
use InvalidArgumentException;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\ORM\ArrayLib;
|
use SilverStripe\ORM\ArrayLib;
|
||||||
|
|
||||||
@ -52,6 +53,20 @@ class HTTPRequest implements ArrayAccess
|
|||||||
*/
|
*/
|
||||||
protected $httpMethod;
|
protected $httpMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL scheme in lowercase: http or https
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $scheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client IP address
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains alls HTTP GET parameters passed into this request.
|
* Contains alls HTTP GET parameters passed into this request.
|
||||||
*
|
*
|
||||||
@ -146,6 +161,7 @@ class HTTPRequest implements ArrayAccess
|
|||||||
$this->getVars = (array) $getVars;
|
$this->getVars = (array) $getVars;
|
||||||
$this->postVars = (array) $postVars;
|
$this->postVars = (array) $postVars;
|
||||||
$this->body = $body;
|
$this->body = $body;
|
||||||
|
$this->scheme = "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -757,58 +773,29 @@ class HTTPRequest implements ArrayAccess
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the client IP address which
|
* Returns the client IP address which originated this request.
|
||||||
* originated this request.
|
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIP()
|
public function getIP()
|
||||||
{
|
{
|
||||||
$headerOverrideIP = null;
|
return $this->ip;
|
||||||
if (TRUSTED_PROXY) {
|
|
||||||
$headers = (getenv('SS_TRUSTED_PROXY_IP_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_IP_HEADER')) : null;
|
|
||||||
if (!$headers) {
|
|
||||||
// Backwards compatible defaults
|
|
||||||
$headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR');
|
|
||||||
}
|
|
||||||
foreach ($headers as $header) {
|
|
||||||
if (!empty($_SERVER[$header])) {
|
|
||||||
$headerOverrideIP = $_SERVER[$header];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($headerOverrideIP && filter_var($headerOverrideIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
||||||
return $this->getIPFromHeaderValue($headerOverrideIP);
|
|
||||||
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
return $_SERVER['REMOTE_ADDR'];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract an IP address from a header value that has been obtained. Accepts single IP or comma separated string of
|
* Sets the client IP address which originated this request.
|
||||||
* IPs
|
* Use setIPFromHeaderValue if assigning from header value.
|
||||||
*
|
*
|
||||||
* @param string $headerValue The value from a trusted header
|
* @param $ip string
|
||||||
* @return string The IP address
|
* @return $this
|
||||||
*/
|
*/
|
||||||
protected function getIPFromHeaderValue($headerValue)
|
public function setIP($ip)
|
||||||
{
|
{
|
||||||
if (strpos($headerValue, ',') !== false) {
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||||
//sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
|
throw new InvalidArgumentException("Invalid ip $ip");
|
||||||
// likely candidate
|
|
||||||
$ips = explode(',', $headerValue);
|
|
||||||
foreach ($ips as $ip) {
|
|
||||||
$ip = trim($ip);
|
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
|
|
||||||
return $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $headerValue;
|
$this->ip = $ip;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -837,6 +824,30 @@ class HTTPRequest implements ArrayAccess
|
|||||||
return $this->httpMethod;
|
return $this->httpMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the URL scheme (e.g. "http" or "https").
|
||||||
|
* Equivalent to PSR-7 getUri()->getScheme()
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getScheme()
|
||||||
|
{
|
||||||
|
return $this->scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the URL scheme (e.g. "http" or "https").
|
||||||
|
* Equivalent to PSR-7 getUri()->getScheme(),
|
||||||
|
*
|
||||||
|
* @param string $scheme
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setScheme($scheme)
|
||||||
|
{
|
||||||
|
$this->scheme = $scheme;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the "real" HTTP method for a request.
|
* Gets the "real" HTTP method for a request.
|
||||||
*
|
*
|
||||||
@ -846,7 +857,7 @@ class HTTPRequest implements ArrayAccess
|
|||||||
* Using GET for the "_method" override is not supported,
|
* Using GET for the "_method" override is not supported,
|
||||||
* as GET should never carry out state changes.
|
* as GET should never carry out state changes.
|
||||||
* Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
|
* Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
|
||||||
* to override the original method in {@link Director::direct()}.
|
* to override the original method.
|
||||||
* The '_method' POST parameter overrules the custom HTTP header.
|
* The '_method' POST parameter overrules the custom HTTP header.
|
||||||
*
|
*
|
||||||
* @param string $origMethod Original HTTP method from the browser request
|
* @param string $origMethod Original HTTP method from the browser request
|
||||||
@ -857,7 +868,7 @@ class HTTPRequest implements ArrayAccess
|
|||||||
{
|
{
|
||||||
if (isset($postVars['_method'])) {
|
if (isset($postVars['_method'])) {
|
||||||
if (!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
|
if (!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
|
||||||
user_error('Director::direct(): Invalid "_method" parameter', E_USER_ERROR);
|
user_error('HTTPRequest::detect_method(): Invalid "_method" parameter', E_USER_ERROR);
|
||||||
}
|
}
|
||||||
return strtoupper($postVars['_method']);
|
return strtoupper($postVars['_method']);
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,6 +45,17 @@ class HTTPRequestBuilder
|
|||||||
$input
|
$input
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set the scheme to HTTPS if needed
|
||||||
|
if ((!empty($variables['_SERVER']['HTTPS']) && $variables['_SERVER']['HTTPS'] != 'off')
|
||||||
|
|| isset($variables['_SERVER']['SSL'])) {
|
||||||
|
$request->setScheme('https');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the client IP
|
||||||
|
if (!empty($variables['_SERVER']['REMOTE_ADDR'])) {
|
||||||
|
$request->setIP($variables['_SERVER']['REMOTE_ADDR']);
|
||||||
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
$headers = static::extractRequestHeaders($variables['_SERVER']);
|
$headers = static::extractRequestHeaders($variables['_SERVER']);
|
||||||
foreach ($headers as $header => $value) {
|
foreach ($headers as $header => $value) {
|
||||||
|
62
src/Control/Middleware/AllowedHostsMiddleware.php
Normal file
62
src/Control/Middleware/AllowedHostsMiddleware.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\HTTPResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secures requests by only allowing a whitelist of Host values
|
||||||
|
*/
|
||||||
|
class AllowedHostsMiddleware implements HTTPMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* List of allowed hosts
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $allowedHosts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array List of allowed Host header values
|
||||||
|
*/
|
||||||
|
public function getAllowedHosts()
|
||||||
|
{
|
||||||
|
return $this->allowedHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of allowed Host header values
|
||||||
|
* Can also specify a comma separated list
|
||||||
|
*
|
||||||
|
* @param array|string $allowedHosts
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setAllowedHosts($allowedHosts)
|
||||||
|
{
|
||||||
|
if (is_string($allowedHosts)) {
|
||||||
|
$allowedHosts = preg_split('/ *, */', $allowedHosts);
|
||||||
|
}
|
||||||
|
$this->allowedHosts = $allowedHosts;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
|
{
|
||||||
|
$allowedHosts = $this->getAllowedHosts();
|
||||||
|
|
||||||
|
// check allowed hosts
|
||||||
|
if ($allowedHosts
|
||||||
|
&& !Director::is_cli()
|
||||||
|
&& !in_array($request->getHeader('Host'), $allowedHosts)
|
||||||
|
) {
|
||||||
|
return new HTTPResponse('Invalid Host', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $delegate($request);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
use SilverStripe\Core\Flushable;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
|
use SilverStripe\Core\Flushable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a call to flush() on all implementors of Flushable.
|
* Triggers a call to flush() on all implementors of Flushable.
|
||||||
*/
|
*/
|
||||||
class FlushRequestFilter implements RequestFilter
|
class FlushMiddleware implements HTTPMiddleware
|
||||||
{
|
{
|
||||||
public function preRequest(HTTPRequest $request)
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
{
|
{
|
||||||
if (array_key_exists('flush', $request->getVars())) {
|
if (array_key_exists('flush', $request->getVars())) {
|
||||||
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
|
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
|
||||||
|
/** @var Flushable|string $class */
|
||||||
$class::flush();
|
$class::flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
return $delegate($request);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\HTTPResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP Request middleware
|
* HTTP Request middleware
|
67
src/Control/Middleware/HTTPMiddlewareAware.php
Normal file
67
src/Control/Middleware/HTTPMiddlewareAware.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\HTTPResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds middleware support to an object.
|
||||||
|
* Provides a Middlewares property and a callMiddleware() callback
|
||||||
|
*/
|
||||||
|
trait HTTPMiddlewareAware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var HTTPMiddleware[]
|
||||||
|
*/
|
||||||
|
protected $middlewares = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HTTPMiddleware[]
|
||||||
|
*/
|
||||||
|
public function getMiddlewares()
|
||||||
|
{
|
||||||
|
return $this->middlewares;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param HTTPMiddleware[] $middlewares
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMiddlewares($middlewares)
|
||||||
|
{
|
||||||
|
// Allow nulls in the middlewares array to deal with limitations of yml config
|
||||||
|
$this->middlewares = array_filter((array)$middlewares);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param HTTPMiddleware $middleware
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addMiddleware(HTTPMiddleware $middleware)
|
||||||
|
{
|
||||||
|
$this->middlewares[] = $middleware;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call middleware
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request The request to pass to the middlewares and callback
|
||||||
|
* @param callable $last The callback to call after all middlewares
|
||||||
|
* @return HTTPResponse
|
||||||
|
*/
|
||||||
|
protected function callMiddleware(HTTPRequest $request, callable $last)
|
||||||
|
{
|
||||||
|
// Reverse middlewares
|
||||||
|
$next = $last;
|
||||||
|
/** @var HTTPMiddleware $middleware */
|
||||||
|
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
|
||||||
|
$next = function ($request) use ($middleware, $next) {
|
||||||
|
return $middleware->process($request, $next);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
59
src/Control/Middleware/RequestHandlerMiddlewareAdapter.php
Normal file
59
src/Control/Middleware/RequestHandlerMiddlewareAdapter.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\RequestHandler;
|
||||||
|
use SilverStripe\Dev\Debug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates a request handler with the HTTP Middleware pattern
|
||||||
|
*/
|
||||||
|
class RequestHandlerMiddlewareAdapter extends RequestHandler
|
||||||
|
{
|
||||||
|
use HTTPMiddlewareAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RequestHandler
|
||||||
|
*/
|
||||||
|
protected $requestHandler = null;
|
||||||
|
|
||||||
|
public function __construct(RequestHandler $handler = null)
|
||||||
|
{
|
||||||
|
if ($handler) {
|
||||||
|
$this->setRequestHandler($handler);
|
||||||
|
}
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Link($action = null)
|
||||||
|
{
|
||||||
|
return $this->getRequestHandler()->Link($action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RequestHandler
|
||||||
|
*/
|
||||||
|
public function getRequestHandler()
|
||||||
|
{
|
||||||
|
return $this->requestHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param RequestHandler $requestHandler
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setRequestHandler(RequestHandler $requestHandler)
|
||||||
|
{
|
||||||
|
$this->requestHandler = $requestHandler;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRequest(HTTPRequest $request)
|
||||||
|
{
|
||||||
|
return $this->callMiddleware($request, function (HTTPRequest $request) {
|
||||||
|
$this->setRequest($request);
|
||||||
|
return $this->getRequestHandler()->handleRequest($request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
30
src/Control/Middleware/SessionMiddleware.php
Normal file
30
src/Control/Middleware/SessionMiddleware.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
|
||||||
|
class SessionMiddleware implements HTTPMiddleware
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Start session and execute
|
||||||
|
$request->getSession()->init($request);
|
||||||
|
|
||||||
|
// Generate output
|
||||||
|
$response = $delegate($request);
|
||||||
|
|
||||||
|
// Save session data, even if there was an exception.
|
||||||
|
// Note that save() will start/resume the session if required.
|
||||||
|
} finally {
|
||||||
|
$request->getSession()->save($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
237
src/Control/Middleware/TrustedProxyMiddleware.php
Normal file
237
src/Control/Middleware/TrustedProxyMiddleware.php
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Middleware;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\Util\IPUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This middleware will rewrite headers that provide IP and host details from an upstream proxy.
|
||||||
|
*/
|
||||||
|
class TrustedProxyMiddleware implements HTTPMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Comma-separated list of IP ranges that are trusted to provide proxy headers.
|
||||||
|
* Can also be 'none' or '*' (all)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $trustedProxyIPs = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of headers from which to lookup the hostname
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $proxyHostHeaders = [
|
||||||
|
'X-Forwarded-Host'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of headers from which to lookup the client IP
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $proxyIPHeaders = [
|
||||||
|
'Client-IP',
|
||||||
|
'X-Forwarded-For'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of headers from which to lookup the client scheme (http/https)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $proxySchemeHeaders = [
|
||||||
|
'X-Forwarded-Protocol',
|
||||||
|
'X-Forwarded-Proto',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the comma-separated list of IP ranges that are trusted to provide proxy headers
|
||||||
|
* Can also be 'none' or '*' (all)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTrustedProxyIPs()
|
||||||
|
{
|
||||||
|
return $this->trustedProxyIPs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the comma-separated list of IP ranges that are trusted to provide proxy headers
|
||||||
|
* Can also be 'none' or '*' (all)
|
||||||
|
*
|
||||||
|
* @param string $trustedProxyIPs
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTrustedProxyIPs($trustedProxyIPs)
|
||||||
|
{
|
||||||
|
$this->trustedProxyIPs = $trustedProxyIPs;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the array of headers from which to lookup the hostname
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getProxyHostHeaders()
|
||||||
|
{
|
||||||
|
return $this->proxyHostHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the array of headers from which to lookup the hostname.
|
||||||
|
*
|
||||||
|
* @param array $proxyHostHeaders
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setProxyHostHeaders($proxyHostHeaders)
|
||||||
|
{
|
||||||
|
$this->proxyHostHeaders = $proxyHostHeaders ?: [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the array of headers from which to lookup the client IP
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getProxyIPHeaders()
|
||||||
|
{
|
||||||
|
return $this->proxyIPHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the array of headers from which to lookup the client IP.
|
||||||
|
*
|
||||||
|
* @param array $proxyIPHeaders
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setProxyIPHeaders($proxyIPHeaders)
|
||||||
|
{
|
||||||
|
$this->proxyIPHeaders = $proxyIPHeaders ?: [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the array of headers from which to lookup the client scheme (http/https)
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getProxySchemeHeaders()
|
||||||
|
{
|
||||||
|
return $this->proxySchemeHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set array of headers from which to lookup the client scheme (http/https)
|
||||||
|
* Can also specify comma-separated list as a single string.
|
||||||
|
*
|
||||||
|
* @param array $proxySchemeHeaders
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setProxySchemeHeaders($proxySchemeHeaders)
|
||||||
|
{
|
||||||
|
$this->proxySchemeHeaders = $proxySchemeHeaders ?: [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
|
{
|
||||||
|
// If this is a trust proxy
|
||||||
|
if ($this->isTrustedProxy($request)) {
|
||||||
|
// Replace host
|
||||||
|
foreach ($this->getProxyHostHeaders() as $header) {
|
||||||
|
$hostList = $request->getHeader($header);
|
||||||
|
if ($hostList) {
|
||||||
|
$request->addHeader('Host', strtok($hostList, ','));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace scheme
|
||||||
|
foreach ($this->getProxySchemeHeaders() as $header) {
|
||||||
|
$headerValue = $request->getHeader($header);
|
||||||
|
if ($headerValue) {
|
||||||
|
$request->setScheme(strtolower($headerValue));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace IP
|
||||||
|
foreach ($this->proxyIPHeaders as $header) {
|
||||||
|
$headerValue = $request->getHeader($header);
|
||||||
|
if ($headerValue) {
|
||||||
|
$ipHeader = $this->getIPFromHeaderValue($headerValue);
|
||||||
|
if ($ipHeader) {
|
||||||
|
$request->setIP($ipHeader);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $delegate($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the current request is coming from a trusted proxy
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return bool True if the request's source IP is a trusted proxy
|
||||||
|
*/
|
||||||
|
protected function isTrustedProxy(HTTPRequest $request)
|
||||||
|
{
|
||||||
|
$trustedIPs = $this->getTrustedProxyIPs();
|
||||||
|
|
||||||
|
// Disabled
|
||||||
|
if (empty($trustedIPs) || $trustedIPs === 'none') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow all
|
||||||
|
if ($trustedIPs === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate IP address
|
||||||
|
$ip = $request->getIP();
|
||||||
|
if ($ip) {
|
||||||
|
return IPUtils::checkIP($ip, preg_split('/\s*,\s*/', $trustedIPs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract an IP address from a header value that has been obtained.
|
||||||
|
* Accepts single IP or comma separated string of IPs
|
||||||
|
*
|
||||||
|
* @param string $headerValue The value from a trusted header
|
||||||
|
* @return string The IP address
|
||||||
|
*/
|
||||||
|
protected function getIPFromHeaderValue($headerValue)
|
||||||
|
{
|
||||||
|
// Sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z"
|
||||||
|
// so we need to find the most likely candidate
|
||||||
|
$ips = preg_split('/\s*,\s*/', $headerValue);
|
||||||
|
|
||||||
|
// Prioritise filters
|
||||||
|
$filters = [
|
||||||
|
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
|
||||||
|
FILTER_FLAG_NO_PRIV_RANGE,
|
||||||
|
null
|
||||||
|
];
|
||||||
|
foreach ($filters as $filter) {
|
||||||
|
// Find best IP
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, $filter)) {
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ namespace SilverStripe\Control;
|
|||||||
*
|
*
|
||||||
* @author marcus@silverstripe.com.au
|
* @author marcus@silverstripe.com.au
|
||||||
* @license BSD License http://silverstripe.org/bsd-license/
|
* @license BSD License http://silverstripe.org/bsd-license/
|
||||||
|
* @deprecated 4.0..5.0 Use HTTPMiddleware instead
|
||||||
*/
|
*/
|
||||||
interface RequestFilter
|
interface RequestFilter
|
||||||
{
|
{
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use ReflectionClass;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Core\Config\Config;
|
use SilverStripe\Core\Config\Config;
|
||||||
use SilverStripe\Dev\Debug;
|
use SilverStripe\Dev\Debug;
|
||||||
use SilverStripe\Security\Security;
|
|
||||||
use SilverStripe\Security\PermissionFailureException;
|
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
|
use SilverStripe\Security\PermissionFailureException;
|
||||||
|
use SilverStripe\Security\Security;
|
||||||
use SilverStripe\View\ViewableData;
|
use SilverStripe\View\ViewableData;
|
||||||
use ReflectionClass;
|
|
||||||
use Exception;
|
|
||||||
use BadMethodCallException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the base class of any SilverStripe object that can be used to handle HTTP requests.
|
* This class is the base class of any SilverStripe object that can be used to handle HTTP requests.
|
||||||
@ -46,6 +46,7 @@ use BadMethodCallException;
|
|||||||
*/
|
*/
|
||||||
class RequestHandler extends ViewableData
|
class RequestHandler extends ViewableData
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional url_segment for this request handler
|
* Optional url_segment for this request handler
|
||||||
*
|
*
|
||||||
|
@ -2,22 +2,31 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddleware;
|
||||||
use SilverStripe\Core\Injector\Injectable;
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a request processer that delegates pre and post request handling to nested request filters
|
* Middleware that provides back-support for the deprecated RequestFilter API.
|
||||||
|
*
|
||||||
|
* @deprecated 4.0..5.0 Use HTTPMiddleware directly instead.
|
||||||
*/
|
*/
|
||||||
class RequestProcessor implements RequestFilter
|
class RequestProcessor implements HTTPMiddleware
|
||||||
{
|
{
|
||||||
use Injectable;
|
use Injectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of currently assigned request filters
|
* List of currently assigned request filters
|
||||||
*
|
*
|
||||||
* @var array
|
* @var RequestFilter[]
|
||||||
*/
|
*/
|
||||||
private $filters = array();
|
private $filters = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct new RequestFilter with a list of filter objects
|
||||||
|
*
|
||||||
|
* @param RequestFilter[] $filters
|
||||||
|
*/
|
||||||
public function __construct($filters = array())
|
public function __construct($filters = array())
|
||||||
{
|
{
|
||||||
$this->filters = $filters;
|
$this->filters = $filters;
|
||||||
@ -26,32 +35,43 @@ class RequestProcessor implements RequestFilter
|
|||||||
/**
|
/**
|
||||||
* Assign a list of request filters
|
* Assign a list of request filters
|
||||||
*
|
*
|
||||||
* @param array $filters
|
* @param RequestFilter[] $filters
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setFilters($filters)
|
public function setFilters($filters)
|
||||||
{
|
{
|
||||||
$this->filters = $filters;
|
$this->filters = $filters;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function preRequest(HTTPRequest $request)
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
{
|
{
|
||||||
|
if ($this->filters) {
|
||||||
|
Deprecation::notice(
|
||||||
|
'5.0',
|
||||||
|
'Deprecated RequestFilters are in use. Apply HTTPMiddleware to Director.middlewares instead.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->filters as $filter) {
|
foreach ($this->filters as $filter) {
|
||||||
$res = $filter->preRequest($request);
|
$res = $filter->preRequest($request);
|
||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
return false;
|
return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
$response = $delegate($request);
|
||||||
{
|
|
||||||
foreach ($this->filters as $filter) {
|
foreach ($this->filters as $filter) {
|
||||||
$res = $filter->postRequest($request, $response);
|
$res = $filter->postRequest($request, $response);
|
||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
return false;
|
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return $response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,15 +143,12 @@ class Session
|
|||||||
/**
|
/**
|
||||||
* Get user agent for this request
|
* Get user agent for this request
|
||||||
*
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function userAgent()
|
protected function userAgent(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
return $request->getHeader('User-Agent');
|
||||||
return $_SERVER['HTTP_USER_AGENT'];
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,30 +168,34 @@ class Session
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Init this session instance before usage
|
* Init this session instance before usage
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
*/
|
*/
|
||||||
public function init()
|
public function init(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
if (!$this->isStarted()) {
|
if (!$this->isStarted()) {
|
||||||
$this->start();
|
$this->start($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funny business detected!
|
// Funny business detected!
|
||||||
if (isset($this->data['HTTP_USER_AGENT'])) {
|
if (isset($this->data['HTTP_USER_AGENT'])) {
|
||||||
if ($this->data['HTTP_USER_AGENT'] !== $this->userAgent()) {
|
if ($this->data['HTTP_USER_AGENT'] !== $this->userAgent($request)) {
|
||||||
$this->clearAll();
|
$this->clearAll();
|
||||||
$this->destroy();
|
$this->destroy();
|
||||||
$this->start();
|
$this->start($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy existing session and restart
|
* Destroy existing session and restart
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
*/
|
*/
|
||||||
public function restart()
|
public function restart(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
$this->destroy();
|
$this->destroy();
|
||||||
$this->init();
|
$this->init($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -210,9 +211,9 @@ class Session
|
|||||||
/**
|
/**
|
||||||
* Begin session
|
* Begin session
|
||||||
*
|
*
|
||||||
* @param string $sid
|
* @param HTTPRequest $request The request for which to start a session
|
||||||
*/
|
*/
|
||||||
public function start($sid = null)
|
public function start(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
if ($this->isStarted()) {
|
if ($this->isStarted()) {
|
||||||
throw new BadMethodCallException("Session has already started");
|
throw new BadMethodCallException("Session has already started");
|
||||||
@ -223,7 +224,7 @@ class Session
|
|||||||
$path = Director::baseURL();
|
$path = Director::baseURL();
|
||||||
}
|
}
|
||||||
$domain = $this->config()->get('cookie_domain');
|
$domain = $this->config()->get('cookie_domain');
|
||||||
$secure = Director::is_https() && $this->config()->get('cookie_secure');
|
$secure = Director::is_https($request) && $this->config()->get('cookie_secure');
|
||||||
$session_path = $this->config()->get('session_store_path');
|
$session_path = $this->config()->get('session_store_path');
|
||||||
$timeout = $this->config()->get('timeout');
|
$timeout = $this->config()->get('timeout');
|
||||||
|
|
||||||
@ -255,9 +256,6 @@ class Session
|
|||||||
session_name('SECSESSID');
|
session_name('SECSESSID');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sid) {
|
|
||||||
session_id($sid);
|
|
||||||
}
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$this->data = isset($_SESSION) ? $_SESSION : array();
|
$this->data = isset($_SESSION) ? $_SESSION : array();
|
||||||
@ -470,23 +468,27 @@ class Session
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set user agent key
|
* Set user agent key
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
*/
|
*/
|
||||||
public function finalize()
|
public function finalize(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
$this->set('HTTP_USER_AGENT', $this->userAgent());
|
$this->set('HTTP_USER_AGENT', $this->userAgent($request));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save data to session
|
* Save data to session
|
||||||
* Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned.
|
* Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned.
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
*/
|
*/
|
||||||
public function save()
|
public function save(HTTPRequest $request)
|
||||||
{
|
{
|
||||||
if ($this->changedData) {
|
if ($this->changedData) {
|
||||||
$this->finalize();
|
$this->finalize($request);
|
||||||
|
|
||||||
if (!$this->isStarted()) {
|
if (!$this->isStarted()) {
|
||||||
$this->start();
|
$this->start($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->recursivelyApply($this->changedData, $_SESSION);
|
$this->recursivelyApply($this->changedData, $_SESSION);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Core;
|
namespace SilverStripe\Core;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Middleware\FlushMiddleware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an interface for classes to implement their own flushing functionality
|
* Provides an interface for classes to implement their own flushing functionality
|
||||||
* whenever flush=1 is requested.
|
* whenever flush=1 is requested.
|
||||||
@ -14,7 +16,7 @@ interface Flushable
|
|||||||
* parameter has been set. Each class that implements Flushable implements
|
* parameter has been set. Each class that implements Flushable implements
|
||||||
* this function which looks after it's own specific flushing functionality.
|
* this function which looks after it's own specific flushing functionality.
|
||||||
*
|
*
|
||||||
* @see FlushRequestFilter
|
* @see FlushMiddleware
|
||||||
*/
|
*/
|
||||||
public static function flush();
|
public static function flush();
|
||||||
}
|
}
|
||||||
|
@ -856,6 +856,7 @@ class Injector implements ContainerInterface
|
|||||||
public function unregisterNamedObject($name)
|
public function unregisterNamedObject($name)
|
||||||
{
|
{
|
||||||
unset($this->serviceCache[$name]);
|
unset($this->serviceCache[$name]);
|
||||||
|
unset($this->specs[$name]);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,7 +880,7 @@ class Injector implements ContainerInterface
|
|||||||
throw new InvalidArgumentException("Global unregistration is not allowed");
|
throw new InvalidArgumentException("Global unregistration is not allowed");
|
||||||
}
|
}
|
||||||
if ($object instanceof $filterClass) {
|
if ($object instanceof $filterClass) {
|
||||||
unset($this->serviceCache[$key]);
|
$this->unregisterNamedObject($key);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -929,6 +930,11 @@ class Injector implements ContainerInterface
|
|||||||
*/
|
*/
|
||||||
protected function getNamedService($name, $asSingleton = true, $constructorArgs = [])
|
protected function getNamedService($name, $asSingleton = true, $constructorArgs = [])
|
||||||
{
|
{
|
||||||
|
// Allow service names of the form "%$ServiceName"
|
||||||
|
if (substr($name, 0, 2) == '%$') {
|
||||||
|
$name = substr($name, 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Normalise service / args
|
// Normalise service / args
|
||||||
list($name, $constructorArgs) = $this->normaliseArguments($name, $constructorArgs);
|
list($name, $constructorArgs) = $this->normaliseArguments($name, $constructorArgs);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use SilverStripe\Control\HTTPRequest;
|
|||||||
use SilverStripe\Control\HTTPResponse;
|
use SilverStripe\Control\HTTPResponse;
|
||||||
use SilverStripe\Control\HTTPResponse_Exception;
|
use SilverStripe\Control\HTTPResponse_Exception;
|
||||||
use SilverStripe\Core\Application;
|
use SilverStripe\Core\Application;
|
||||||
use SilverStripe\Control\HTTPMiddleware;
|
use SilverStripe\Control\Middleware\HTTPMiddleware;
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
use SilverStripe\Security\Security;
|
use SilverStripe\Security\Security;
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
|
|||||||
$this->getApplication()->getKernel()->boot(false);
|
$this->getApplication()->getKernel()->boot(false);
|
||||||
|
|
||||||
// Ensure session is started
|
// Ensure session is started
|
||||||
$request->getSession()->init();
|
$request->getSession()->init($request);
|
||||||
|
|
||||||
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
|
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
|
||||||
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
|
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
|
||||||
|
@ -911,7 +911,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
|
|||||||
// Custom application
|
// Custom application
|
||||||
$app->execute($request, function (HTTPRequest $request) {
|
$app->execute($request, function (HTTPRequest $request) {
|
||||||
// Start session and execute
|
// Start session and execute
|
||||||
$request->getSession()->init();
|
$request->getSession()->init($request);
|
||||||
|
|
||||||
// Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
|
// Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
|
||||||
// (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
|
// (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
|
||||||
|
@ -1032,7 +1032,7 @@ class Form extends ViewableData implements HasRequestHandler
|
|||||||
* As most browsers only support GET and POST in
|
* As most browsers only support GET and POST in
|
||||||
* form submissions, all other HTTP methods are
|
* form submissions, all other HTTP methods are
|
||||||
* added as a hidden field "_method" that
|
* added as a hidden field "_method" that
|
||||||
* gets evaluated in {@link Director::direct()}.
|
* gets evaluated in {@link HTTPRequest::detect_method()}.
|
||||||
* See {@link FormMethod()} to get a HTTP method
|
* See {@link FormMethod()} to get a HTTP method
|
||||||
* for safe insertion into a <form> tag.
|
* for safe insertion into a <form> tag.
|
||||||
*
|
*
|
||||||
|
@ -4,12 +4,11 @@ namespace SilverStripe\Security;
|
|||||||
|
|
||||||
use SilverStripe\Control\HTTPRequest;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use SilverStripe\Control\HTTPResponse;
|
use SilverStripe\Control\HTTPResponse;
|
||||||
use SilverStripe\Control\HTTPResponse_Exception;
|
use SilverStripe\Control\Middleware\HTTPMiddleware;
|
||||||
use SilverStripe\Control\RequestFilter;
|
|
||||||
use SilverStripe\Core\Config\Configurable;
|
use SilverStripe\Core\Config\Configurable;
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
|
|
||||||
class AuthenticationRequestFilter implements RequestFilter
|
class AuthenticationMiddleware implements HTTPMiddleware
|
||||||
{
|
{
|
||||||
use Configurable;
|
use Configurable;
|
||||||
|
|
||||||
@ -40,35 +39,24 @@ class AuthenticationRequestFilter implements RequestFilter
|
|||||||
* Identify the current user from the request
|
* Identify the current user from the request
|
||||||
*
|
*
|
||||||
* @param HTTPRequest $request
|
* @param HTTPRequest $request
|
||||||
* @return bool|void
|
* @param callable $delegate
|
||||||
* @throws HTTPResponse_Exception
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
public function preRequest(HTTPRequest $request)
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
{
|
{
|
||||||
if (!Security::database_is_ready()) {
|
if (Security::database_is_ready()) {
|
||||||
return;
|
try {
|
||||||
|
$this
|
||||||
|
->getAuthenticationHandler()
|
||||||
|
->authenticateRequest($request);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return new HTTPResponse(
|
||||||
|
"Bad log-in details: " . $e->getMessage(),
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return $delegate($request);
|
||||||
$this
|
|
||||||
->getAuthenticationHandler()
|
|
||||||
->authenticateRequest($request);
|
|
||||||
} catch (ValidationException $e) {
|
|
||||||
throw new HTTPResponse_Exception(
|
|
||||||
"Bad log-in details: " . $e->getMessage(),
|
|
||||||
400
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No-op
|
|
||||||
*
|
|
||||||
* @param HTTPRequest $request
|
|
||||||
* @param HTTPResponse $response
|
|
||||||
* @return bool|void
|
|
||||||
*/
|
|
||||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -415,7 +415,7 @@ class Member extends DataObject
|
|||||||
*/
|
*/
|
||||||
public function beforeMemberLoggedIn()
|
public function beforeMemberLoggedIn()
|
||||||
{
|
{
|
||||||
// @todo Move to middleware on the AuthenticationRequestFilter IdentityStore
|
// @todo Move to middleware on the AuthenticationMiddleware IdentityStore
|
||||||
$this->extend('beforeMemberLoggedIn');
|
$this->extend('beforeMemberLoggedIn');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,27 +73,6 @@ if (!getenv('SS_IGNORE_DOT_ENV')) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether the request comes directly from a trusted server or not
|
|
||||||
* This is necessary to validate whether or not the values of X-Forwarded-
|
|
||||||
* or Client-IP HTTP headers can be trusted
|
|
||||||
*/
|
|
||||||
if (!defined('TRUSTED_PROXY')) {
|
|
||||||
define('TRUSTED_PROXY', call_user_func(function () {
|
|
||||||
$trustedIPs = getenv('SS_TRUSTED_PROXY_IPS');
|
|
||||||
if (empty($trustedIPs) || $trustedIPs === 'none') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($trustedIPs === '*') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Validate IP address
|
|
||||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
return IPUtils::checkIP($_SERVER['REMOTE_ADDR'], explode(',', $trustedIPs));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!defined('BASE_URL')) {
|
if (!defined('BASE_URL')) {
|
||||||
define('BASE_URL', call_user_func(function () {
|
define('BASE_URL', call_user_func(function () {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SilverStripe\Control\Tests;
|
namespace SilverStripe\Control\Tests;
|
||||||
|
|
||||||
use SilverStripe\Control\Cookie_Backend;
|
use SilverStripe\Control\Cookie_Backend;
|
||||||
@ -8,8 +7,12 @@ use SilverStripe\Control\HTTPRequest;
|
|||||||
use SilverStripe\Control\HTTPRequestBuilder;
|
use SilverStripe\Control\HTTPRequestBuilder;
|
||||||
use SilverStripe\Control\HTTPResponse;
|
use SilverStripe\Control\HTTPResponse;
|
||||||
use SilverStripe\Control\HTTPResponse_Exception;
|
use SilverStripe\Control\HTTPResponse_Exception;
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddleware;
|
||||||
|
use SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter;
|
||||||
|
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
|
||||||
use SilverStripe\Control\RequestProcessor;
|
use SilverStripe\Control\RequestProcessor;
|
||||||
use SilverStripe\Control\Tests\DirectorTest\TestController;
|
use SilverStripe\Control\Tests\DirectorTest\TestController;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Core\Kernel;
|
use SilverStripe\Core\Kernel;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
@ -535,63 +538,91 @@ class DirectorTest extends SapphireTest
|
|||||||
|
|
||||||
public function testIsHttps()
|
public function testIsHttps()
|
||||||
{
|
{
|
||||||
if (!TRUSTED_PROXY) {
|
// Trust all IPs for this test
|
||||||
$this->markTestSkipped('Test cannot be run without trusted proxy');
|
/** @var TrustedProxyMiddleware $trustedProxyMiddleware */
|
||||||
}
|
$trustedProxyMiddleware
|
||||||
|
= Injector::inst()->get(TrustedProxyMiddleware::class);
|
||||||
|
$trustedProxyMiddleware->setTrustedProxyIPs('*');
|
||||||
|
|
||||||
|
// Clear alternate_base_url for this test
|
||||||
|
Director::config()->remove('alternate_base_url');
|
||||||
|
|
||||||
// nothing available
|
// nothing available
|
||||||
$headers = array(
|
$headers = array(
|
||||||
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
|
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
|
||||||
);
|
);
|
||||||
|
|
||||||
$origServer = $_SERVER;
|
|
||||||
|
|
||||||
foreach ($headers as $header) {
|
foreach ($headers as $header) {
|
||||||
if (isset($_SERVER[$header])) {
|
if (isset($_SERVER[$header])) {
|
||||||
unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
|
unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertFalse(Director::is_https());
|
$this->assertEquals(
|
||||||
|
'no',
|
||||||
|
Director::test('TestController/returnIsSSL')->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
|
$this->assertEquals(
|
||||||
$this->assertTrue(Director::is_https());
|
'yes',
|
||||||
|
Director::test(
|
||||||
|
'TestController/returnIsSSL',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
[ 'X-Forwarded-Protocol' => 'https' ]
|
||||||
|
)->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
|
$this->assertEquals(
|
||||||
$this->assertFalse(Director::is_https());
|
'no',
|
||||||
|
Director::test(
|
||||||
|
'TestController/returnIsSSL',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
[ 'X-Forwarded-Protocol' => 'http' ]
|
||||||
|
)->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'ftp';
|
$this->assertEquals(
|
||||||
$this->assertFalse(Director::is_https());
|
'no',
|
||||||
|
Director::test(
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
|
'TestController/returnIsSSL',
|
||||||
$this->assertTrue(Director::is_https());
|
null,
|
||||||
|
null,
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
|
null,
|
||||||
$this->assertFalse(Director::is_https());
|
null,
|
||||||
|
[ 'X-Forwarded-Protocol' => 'ftp' ]
|
||||||
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'ftp';
|
)->getBody()
|
||||||
$this->assertFalse(Director::is_https());
|
);
|
||||||
|
|
||||||
$_SERVER['HTTP_FRONT_END_HTTPS'] = 'On';
|
|
||||||
$this->assertTrue(Director::is_https());
|
|
||||||
|
|
||||||
$_SERVER['HTTP_FRONT_END_HTTPS'] = 'Off';
|
|
||||||
$this->assertFalse(Director::is_https());
|
|
||||||
|
|
||||||
// https via HTTPS
|
// https via HTTPS
|
||||||
$_SERVER['HTTPS'] = 'true';
|
$_SERVER['HTTPS'] = 'true';
|
||||||
$this->assertTrue(Director::is_https());
|
$this->assertEquals(
|
||||||
|
'yes',
|
||||||
|
Director::test('TestController/returnIsSSL')->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
$_SERVER['HTTPS'] = '1';
|
$_SERVER['HTTPS'] = '1';
|
||||||
$this->assertTrue(Director::is_https());
|
$this->assertEquals(
|
||||||
|
'yes',
|
||||||
|
Director::test('TestController/returnIsSSL')->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
$_SERVER['HTTPS'] = 'off';
|
$_SERVER['HTTPS'] = 'off';
|
||||||
$this->assertFalse(Director::is_https());
|
$this->assertEquals(
|
||||||
|
'no',
|
||||||
|
Director::test('TestController/returnIsSSL')->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
// https via SSL
|
// https via SSL
|
||||||
$_SERVER['SSL'] = '';
|
$_SERVER['SSL'] = '';
|
||||||
$this->assertTrue(Director::is_https());
|
$this->assertEquals(
|
||||||
|
'yes',
|
||||||
$_SERVER = $origServer;
|
Director::test('TestController/returnIsSSL')->getBody()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTestIgnoresHashes()
|
public function testTestIgnoresHashes()
|
||||||
@ -646,4 +677,88 @@ class DirectorTest extends SapphireTest
|
|||||||
// preCall 'true' will trigger an exception and prevent post call execution
|
// preCall 'true' will trigger an exception and prevent post call execution
|
||||||
$this->assertEquals(2, $filter->postCalls);
|
$this->assertEquals(2, $filter->postCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGlobalMiddleware()
|
||||||
|
{
|
||||||
|
$middleware = new DirectorTest\TestMiddleware;
|
||||||
|
Director::singleton()->setMiddlewares([$middleware]);
|
||||||
|
|
||||||
|
$response = Director::test('some-dummy-url');
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
|
||||||
|
// Both triggered
|
||||||
|
$this->assertEquals(1, $middleware->preCalls);
|
||||||
|
$this->assertEquals(1, $middleware->postCalls);
|
||||||
|
|
||||||
|
$middleware->failPost = true;
|
||||||
|
|
||||||
|
$response = Director::test('some-dummy-url');
|
||||||
|
$this->assertEquals(500, $response->getStatusCode());
|
||||||
|
|
||||||
|
// Both triggered
|
||||||
|
$this->assertEquals(2, $middleware->preCalls);
|
||||||
|
$this->assertEquals(2, $middleware->postCalls);
|
||||||
|
|
||||||
|
$middleware->failPre = true;
|
||||||
|
|
||||||
|
$response = Director::test('some-dummy-url');
|
||||||
|
$this->assertEquals(400, $response->getStatusCode());
|
||||||
|
|
||||||
|
// Pre triggered, post not
|
||||||
|
$this->assertEquals(3, $middleware->preCalls);
|
||||||
|
$this->assertEquals(2, $middleware->postCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRouteSpecificMiddleware()
|
||||||
|
{
|
||||||
|
// Inject adapter in place of controller
|
||||||
|
$specificMiddleware = new DirectorTest\TestMiddleware;
|
||||||
|
Injector::inst()->registerService($specificMiddleware, 'SpecificMiddleware');
|
||||||
|
|
||||||
|
// Register adapter as factory for creating this controller
|
||||||
|
Config::modify()->merge(
|
||||||
|
Injector::class,
|
||||||
|
'ControllerWithMiddleware',
|
||||||
|
[
|
||||||
|
'class' => RequestHandlerMiddlewareAdapter::class,
|
||||||
|
'constructor' => [
|
||||||
|
'%$' . TestController::class
|
||||||
|
],
|
||||||
|
'properties' => [
|
||||||
|
'Middlewares' => [
|
||||||
|
'%$SpecificMiddleware',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Global middleware
|
||||||
|
$middleware = new DirectorTest\TestMiddleware;
|
||||||
|
Director::singleton()->setMiddlewares([ $middleware ]);
|
||||||
|
|
||||||
|
// URL rules, one of which has a specific middleware
|
||||||
|
Config::modify()->set(
|
||||||
|
Director::class,
|
||||||
|
'rules',
|
||||||
|
[
|
||||||
|
'url-one' => TestController::class,
|
||||||
|
'url-two' => [
|
||||||
|
'Controller' => 'ControllerWithMiddleware',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// URL without a route-specific middleware
|
||||||
|
Director::test('url-one');
|
||||||
|
|
||||||
|
// Only the global middleware triggered
|
||||||
|
$this->assertEquals(1, $middleware->preCalls);
|
||||||
|
$this->assertEquals(0, $specificMiddleware->postCalls);
|
||||||
|
|
||||||
|
Director::test('url-two');
|
||||||
|
|
||||||
|
// Both triggered on the url with the specific middleware applied
|
||||||
|
$this->assertEquals(2, $middleware->preCalls);
|
||||||
|
$this->assertEquals(1, $specificMiddleware->postCalls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace SilverStripe\Control\Tests\DirectorTest;
|
namespace SilverStripe\Control\Tests\DirectorTest;
|
||||||
|
|
||||||
use SilverStripe\Control\Controller;
|
use SilverStripe\Control\Controller;
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
use SilverStripe\Dev\TestOnly;
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
class TestController extends Controller implements TestOnly
|
class TestController extends Controller implements TestOnly
|
||||||
@ -22,6 +23,7 @@ class TestController extends Controller implements TestOnly
|
|||||||
'returnPostValue',
|
'returnPostValue',
|
||||||
'returnRequestValue',
|
'returnRequestValue',
|
||||||
'returnCookieValue',
|
'returnCookieValue',
|
||||||
|
'returnIsSSL',
|
||||||
);
|
);
|
||||||
|
|
||||||
public function returnGetValue($request)
|
public function returnGetValue($request)
|
||||||
@ -55,4 +57,9 @@ class TestController extends Controller implements TestOnly
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function returnIsSSL()
|
||||||
|
{
|
||||||
|
return Director::is_https() ? 'yes': 'no';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
40
tests/php/Control/DirectorTest/TestMiddleware.php
Normal file
40
tests/php/Control/DirectorTest/TestMiddleware.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Control\Tests\DirectorTest;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\HTTPResponse;
|
||||||
|
use SilverStripe\Control\Middleware\HTTPMiddleware;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
|
||||||
|
class TestMiddleware implements HTTPMiddleware, TestOnly
|
||||||
|
{
|
||||||
|
public $preCalls = 0;
|
||||||
|
public $postCalls = 0;
|
||||||
|
|
||||||
|
public $failPre = false;
|
||||||
|
public $failPost = false;
|
||||||
|
|
||||||
|
public function process(HTTPRequest $request, callable $delegate)
|
||||||
|
{
|
||||||
|
$this->preCalls++;
|
||||||
|
if ($this->failPre) {
|
||||||
|
return new HTTPResponse('Fail pre', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $delegate($request);
|
||||||
|
|
||||||
|
$this->postCalls++;
|
||||||
|
if ($this->failPost) {
|
||||||
|
return new HTTPResponse('Fail post', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset()
|
||||||
|
{
|
||||||
|
$this->preCalls = 0;
|
||||||
|
$this->postCalls = 0;
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Control\Tests;
|
namespace SilverStripe\Control\Tests;
|
||||||
|
|
||||||
use SilverStripe\Control\Tests\FlushRequestFilterTest\TestFlushable;
|
use SilverStripe\Control\Tests\FlushMiddlewareTest\TestFlushable;
|
||||||
use SilverStripe\Dev\FunctionalTest;
|
use SilverStripe\Dev\FunctionalTest;
|
||||||
|
|
||||||
class FlushRequestFilterTest extends FunctionalTest
|
class FlushMiddlewareTest extends FunctionalTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Assert that classes that implement flushable are called
|
* Assert that classes that implement flushable are called
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SilverStripe\Control\Tests\FlushRequestFilterTest;
|
namespace SilverStripe\Control\Tests\FlushMiddlewareTest;
|
||||||
|
|
||||||
use SilverStripe\Core\Flushable;
|
use SilverStripe\Core\Flushable;
|
||||||
use SilverStripe\Dev\TestOnly;
|
use SilverStripe\Dev\TestOnly;
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Control\Tests;
|
namespace SilverStripe\Control\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\Control\HTTPRequest;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
@ -267,9 +268,9 @@ class HTTPRequestTest extends SapphireTest
|
|||||||
$this->assertEquals('home', $req->getURL());
|
$this->assertEquals('home', $req->getURL());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetIPFromHeaderValue()
|
public function testSetIPFromHeaderValue()
|
||||||
{
|
{
|
||||||
$req = new HTTPRequest('GET', '/');
|
$req = new TrustedProxyMiddleware();
|
||||||
$reflectionMethod = new ReflectionMethod($req, 'getIPFromHeaderValue');
|
$reflectionMethod = new ReflectionMethod($req, 'getIPFromHeaderValue');
|
||||||
$reflectionMethod->setAccessible(true);
|
$reflectionMethod->setAccessible(true);
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Control\Tests;
|
|||||||
|
|
||||||
use SilverStripe\Control\Session;
|
use SilverStripe\Control\Session;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests to cover the {@link Session} class
|
* Tests to cover the {@link Session} class
|
||||||
@ -107,20 +108,22 @@ class SessionTest extends SapphireTest
|
|||||||
public function testUserAgentLockout()
|
public function testUserAgentLockout()
|
||||||
{
|
{
|
||||||
// Set a user agent
|
// Set a user agent
|
||||||
$_SERVER['HTTP_USER_AGENT'] = 'Test Agent';
|
$req1 = new HTTPRequest('GET', '/');
|
||||||
|
$req1->addHeader('User-Agent', 'Test Agent');
|
||||||
|
|
||||||
// Generate our session
|
// Generate our session
|
||||||
$s = new Session(array());
|
$s = new Session(array());
|
||||||
$s->init();
|
$s->init($req1);
|
||||||
$s->set('val', 123);
|
$s->set('val', 123);
|
||||||
$s->finalize();
|
$s->finalize($req1);
|
||||||
|
|
||||||
// Change our UA
|
// Change our UA
|
||||||
$_SERVER['HTTP_USER_AGENT'] = 'Fake Agent';
|
$req2 = new HTTPRequest('GET', '/');
|
||||||
|
$req2->addHeader('User-Agent', 'Fake Agent');
|
||||||
|
|
||||||
// Verify the new session reset our values
|
// Verify the new session reset our values
|
||||||
$s2 = new Session($s);
|
$s2 = new Session($s);
|
||||||
$s2->init();
|
$s2->init($req2);
|
||||||
$this->assertNotEquals($s2->get('val'), 123);
|
$this->assertNotEquals($s2->get('val'), 123);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ use SilverStripe\Core\Tests\Injector\InjectorTest\TestObject;
|
|||||||
use SilverStripe\Core\Tests\Injector\InjectorTest\TestSetterInjections;
|
use SilverStripe\Core\Tests\Injector\InjectorTest\TestSetterInjections;
|
||||||
use SilverStripe\Core\Tests\Injector\InjectorTest\TestStaticInjections;
|
use SilverStripe\Core\Tests\Injector\InjectorTest\TestStaticInjections;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Dev\TestOnly;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
define('TEST_SERVICES', __DIR__ . '/AopProxyServiceTest');
|
define('TEST_SERVICES', __DIR__ . '/AopProxyServiceTest');
|
||||||
@ -802,10 +803,26 @@ class InjectorTest extends SapphireTest
|
|||||||
public function testNamedServices()
|
public function testNamedServices()
|
||||||
{
|
{
|
||||||
$injector = new Injector();
|
$injector = new Injector();
|
||||||
$service = new stdClass();
|
$service = new TestObject();
|
||||||
|
$service->setSomething('injected');
|
||||||
|
|
||||||
|
// Test registering with non-class name
|
||||||
$injector->registerService($service, 'NamedService');
|
$injector->registerService($service, 'NamedService');
|
||||||
|
$this->assertTrue($injector->has('NamedService'));
|
||||||
$this->assertEquals($service, $injector->get('NamedService'));
|
$this->assertEquals($service, $injector->get('NamedService'));
|
||||||
|
|
||||||
|
// Unregister service by name
|
||||||
|
$injector->unregisterNamedObject('NamedService');
|
||||||
|
$this->assertFalse($injector->has('NamedService'));
|
||||||
|
|
||||||
|
// Test registered with class name
|
||||||
|
$injector->registerService($service);
|
||||||
|
$this->assertTrue($injector->has(TestObject::class));
|
||||||
|
$this->assertEquals($service, $injector->get(TestObject::class));
|
||||||
|
|
||||||
|
// Unregister service by class
|
||||||
|
$injector->unregisterNamedObject(TestObject::class);
|
||||||
|
$this->assertFalse($injector->has(TestObject::class));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateConfiggedObjectWithCustomConstructorArgs()
|
public function testCreateConfiggedObjectWithCustomConstructorArgs()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user