Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSR-15 Support #105

Merged
merged 31 commits into from
Sep 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2ea8665
Minimum PHP 7.1
thomas-lb Sep 4, 2019
b1c32cb
Slim 4 PSR-15 supported
thomas-lb Sep 4, 2019
9452abc
Corrected namespace
thomas-lb Sep 4, 2019
fe8053c
Fix test PHPUnit
thomas-lb Sep 5, 2019
ceb3523
Update README.md
thomas-lb Sep 5, 2019
afa062b
Remove HHVM Travis
thomas-lb Sep 5, 2019
74c82d8
README.md using PHP-DI
thomas-lb Sep 6, 2019
bdf3626
update CI configs and dependencies
l0gicgate Sep 9, 2019
2c308de
update bootstrapping and rename test class
l0gicgate Sep 9, 2019
b04aa7b
add initial draft for PSR-15 support
l0gicgate Sep 9, 2019
d5b9320
update examples
l0gicgate Sep 9, 2019
18ddba4
add php codesniffer
l0gicgate Sep 9, 2019
1931427
refactor everything
l0gicgate Sep 9, 2019
732fe65
rewrite all tests
l0gicgate Sep 9, 2019
bad6da0
add coverage badge
l0gicgate Sep 9, 2019
dee100b
add nullable type hint to failure handler
l0gicgate Sep 10, 2019
3e4a28a
add return self to setStorage method
l0gicgate Sep 10, 2019
5a5dc7e
remove unused import
l0gicgate Sep 10, 2019
9564be3
replace token keys and values to use helper for one source of truth
l0gicgate Sep 10, 2019
0b4af3b
add AppFactory::setContainer($container) to examples
l0gicgate Sep 10, 2019
f865fce
fix docblock for setStorage method
l0gicgate Sep 10, 2019
cbf1a31
fix appendTokenToRequest() docbloc $pair variable type hinting
l0gicgate Sep 10, 2019
fee65e2
fix comment in validateToken() method docblock
l0gicgate Sep 10, 2019
211bade
typehint validateToken to require parameters
l0gicgate Sep 10, 2019
1d21df1
optimize getLastKeyPair
l0gicgate Sep 10, 2019
e12acc6
optimize loadLastKeyPair()
l0gicgate Sep 10, 2019
6facb50
re-order imports
l0gicgate Sep 10, 2019
2feed8e
fix $failureHandler type hint
l0gicgate Sep 10, 2019
7ee477c
fix always true elseif statement
l0gicgate Sep 10, 2019
3e48e79
change setStorage logic to use session_status()
l0gicgate Sep 13, 2019
7afb074
fix readme to use container interface `get()` method instead of pimpl…
l0gicgate Sep 13, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
json_path: coveralls-upload.json
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true

[*]
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
26 changes: 19 additions & 7 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
/tests/ export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/CONTRIBUTING.md export-ignore
/README.md export-ignore
/phpunit.xml.dist export-ignore
# Enforce Unix newlines
* text=lf

# Exclude unused files
# see: https://redd.it/2jzp6k
/.coveralls.yml export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/CODE_OF_CONDUCT.md export-ignore
/CONTRIBUTING.md export-ignore
/README.md export-ignore
/UPGRADING.md export-ignore
/phpcs.xml export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.idea
composer.lock
phpunit.xml
vendor
.idea
coverage
29 changes: 18 additions & 11 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
language: php

php:
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
- hhvm
dist: trusty

matrix:
include:
- php: 7.1
- php: 7.2
- php: 7.3
env: ANALYSIS='true'
- php: nightly

allow_failures:
- php: hhvm
- php: nightly

before_script:
- if [[ "$ANALYSIS" == 'true' ]]; then composer require php-coveralls/php-coveralls:^2.1.0 ; fi
- composer install -n

before_script: composer install
script:
- if [[ "$ANALYSIS" != 'true' ]]; then vendor/bin/phpunit ; fi
- if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi

script: phpunit --coverage-text
after_success:
- if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/php-coveralls --coverage_clover=clover.xml -v ; fi
130 changes: 76 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Slim Framework CSRF Protection

[![Build Status](https://travis-ci.org/slimphp/Slim-Csrf.svg?branch=master)](https://travis-ci.org/slimphp/Slim-Csrf)
[![Coverage Status](https://coveralls.io/repos/github/slimphp/Slim-Csrf/badge.svg?branch=master)](https://coveralls.io/github/slimphp/Slim-Csrf?branch=master)

This repository contains a Slim Framework CSRF protection middleware. CSRF protection applies to all unsafe HTTP requests (POST, PUT, DELETE, PATCH).
This repository contains a Slim Framework CSRF protection PSR-15 middleware. CSRF protection applies to all unsafe HTTP requests (POST, PUT, DELETE, PATCH).

You can fetch the latest CSRF token's name and value from the Request object with its `getAttribute()` method. By default, the CSRF token's name is stored in the `csrf_name` attribute, and the CSRF token's value is stored in the `csrf_value` attribute.

Expand All @@ -14,36 +15,45 @@ Via Composer
$ composer require slim/csrf
```

Requires Slim 3.0.0 or newer.
Requires Slim 4.0.0 or newer.

## Usage

In most cases you want to register Slim\Csrf for all routes, however,
as it is middleware, you can also register it for a subset of routes.

In most cases you want to register Slim\Csrf for all routes, however, as it is middleware, you can also register it for a subset of routes.

### Register for all routes

```php
use DI\Container;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

// Start PHP session
session_start();

$app = new \Slim\App();
// Create Container
$container = new Container();
AppFactory::setContainer($container);

// Register with container
$container = $app->getContainer();
$container['csrf'] = function ($c) {
return new \Slim\Csrf\Guard;
};
// Create App
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
$app = AppFactory::create();
$responseFactory = $app->getResponseFactory();

// Register Middleware On Container
$container->set('csrf', function () use ($responseFactory) {
return new Guard($responseFactory);
});

// Register middleware for all routes
// If you are implementing per-route checks you must not add this
$app->add($container->get('csrf'));
// Register Middleware To Be Executed On All Routes
$app->add('csrf');

$app->get('/foo', function ($request, $response, $args) {
// CSRF token name and value
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
$nameKey = $this->csrf->getTokenNameKey();
$valueKey = $this->csrf->getTokenValueKey();
$csrf = $this->get('csrf');
$nameKey = $csrf->getTokenNameKey();
$valueKey = $csrf->getTokenValueKey();
$name = $request->getAttribute($nameKey);
$value = $request->getAttribute($valueKey);

Expand All @@ -64,20 +74,32 @@ $app->run();
### Register per route

```php
use DI\Container;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

// Start PHP session
session_start();

$app = new \Slim\App();
// Create Container
$container = new Container();
AppFactory::setContainer($container);

// Register with container
$container = $app->getContainer();
$container['csrf'] = function ($c) {
return new \Slim\Csrf\Guard;
};
// Create App
$app = AppFactory::create();
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
$responseFactory = $app->getResponseFactory();

// Register Middleware On Container
$container->set('csrf', function () use ($responseFactory) {
return new Guard($responseFactory);
});

$app->get('/api/myEndPoint',function ($request, $response, $args) {
$nameKey = $this->csrf->getTokenNameKey();
$valueKey = $this->csrf->getTokenValueKey();
$app->get('/api/route',function ($request, $response, $args) {
$csrf = $this->get('csrf');
$nameKey = $csrf->getTokenNameKey();
$valueKey = $csrf->getTokenValueKey();
$name = $request->getAttribute($nameKey);
$value = $request->getAttribute($valueKey);

Expand All @@ -87,11 +109,11 @@ $app->get('/api/myEndPoint',function ($request, $response, $args) {
];

return $response->write(json_encode($tokenArray));
})->add($container->get('csrf'));
})->add('csrf');

$app->post('/api/myEndPoint',function ($request, $response, $args) {
//Do my Things Securely!
})->add($container->get('csrf'));
})->add('csrf');

$app->run();
```
Expand All @@ -101,19 +123,23 @@ $app->run();
If you are willing to use `Slim\Csrf\Guard` outside a `Slim\App` or not as a middleware, be careful to validate the storage:

```php
use Slim\Csrf\Guard;
use Slim\Psr7\Factory\ResponseFactory;

// Start PHP session
session_start();

$slimGuard = new \Slim\Csrf\Guard;
$slimGuard->validateStorage();
// Create Middleware
$responseFactory = new ResponseFactory(); // Note that you will need to import
$guard = new Guard($responseFactory);

// Generate new tokens
$csrfNameKey = $slimGuard->getTokenNameKey();
$csrfValueKey = $slimGuard->getTokenValueKey();
$keyPair = $slimGuard->generateToken();
$csrfNameKey = $guard->getTokenNameKey();
$csrfValueKey = $guard->getTokenValueKey();
$keyPair = $guard->generateToken();

// Validate retrieved tokens
$slimGuard->validateToken($_POST[$csrfNameKey], $_POST[$csrfValueKey]);
$guard->validateToken($_POST[$csrfNameKey], $_POST[$csrfValueKey]);
```

## Token persistence
Expand All @@ -122,21 +148,21 @@ By default, `Slim\Csrf\Guard` will generate a fresh name/value pair after each r

To use persistent tokens, set the sixth parameter of the constructor to `true`. No matter what, the token will be regenerated after a failed CSRF check. In this case, you will probably want to detect this condition and instruct your users to reload the page in their legitimate browser tab (or automatically reload on the next failed request).


### Accessing the token pair in templates (Twig, etc)

In many situations, you will want to access the token pair without needing to go through the request object. In these cases, you can use `getTokenName()` and `getTokenValue()` directly on the `Guard` middleware instance. This can be useful, for example in a [Twig extension](http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension):
In many situations, you will want to access the token pair without needing to go through the request object. In these cases, you can use `getTokenName()` and `getTokenValue()` directly on the `Guard` middleware instance. This can be useful, for example in a [Twig extension](https://twig.symfony.com/doc/2.x/advanced.html#creating-an-extension):

```php
class CsrfExtension extends \Twig_Extension implements Twig_Extension_GlobalsInterface
{
use Slim\Csrf\Guard;

class CsrfExtension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
/**
* @var \Slim\Csrf\Guard
* @var Guard
*/
protected $csrf;

public function __construct(\Slim\Csrf\Guard $csrf)
public function __construct(Guard $csrf)
{
$this->csrf = $csrf;
}
Expand All @@ -160,11 +186,6 @@ class CsrfExtension extends \Twig_Extension implements Twig_Extension_GlobalsInt
]
];
}

public function getName()
{
return 'slim/csrf';
}
}
```

Expand All @@ -182,20 +203,21 @@ By default, `Slim\Csrf\Guard` will return a Response with a 400 status code and
a simple plain text error message.

To override this, provide a callable as the third parameter to the constructor
or via `setFailureCallable()`. This callable has the same signature as
middleware: `function($request, $response, $next)` and must return a Response.
or via `setFailureHandler()`. This callable has the same signature as
middleware: `function($request, $handler)` and must return a Response.

For example:

```php
$container['csrf'] = function ($c) {
$guard = new \Slim\Csrf\Guard();
$guard->setFailureCallable(function ($request, $response, $next) {
$request = $request->withAttribute("csrf_status", false);
return $next($request, $response);
});
return $guard;
};
use Slim\Csrf\Guard;
use Slim\Psr7\Factory\ResponseFactory;

$responseFactory = new ResponseFactory();
$guard = new Guard($responseFactory);
$guard->setFailureHandler(function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
$request = $request->withAttribute("csrf_status", false);
return $handler->handle($request);
});
```

In this example, an attribute is set on the request object that can then be
Expand Down
13 changes: 8 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "slim/csrf",
"type": "library",
"description": "Slim Framework 3 CSRF protection middleware",
"description": "Slim Framework 4 CSRF protection PSR-15 middleware",
"keywords": ["slim","framework","middleware","csrf"],
"homepage": "http://slimframework.com",
"license": "MIT",
Expand All @@ -13,13 +13,16 @@
}
],
"require": {
"php": ">=5.5.0",
"php": "^7.1",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0",
"paragonie/random_compat": "^1.1|^2.0|^9.99"
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0"
},
"require-dev": {
"slim/slim": "~3.0",
"phpunit/phpunit": "^4.0"
"phpunit/phpunit": "^7.5",
"phpspec/prophecy": "^1.8",
"squizlabs/php_codesniffer": "^3.4.2"
},
"autoload": {
"psr-4": {
Expand Down
17 changes: 17 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<ruleset name="Slim coding standard">
<description>Slim coding standard</description>

<!-- display progress -->
<arg value="p"/>
<!-- use colors in output -->
<arg name="colors"/>

<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>

<!-- Paths to check -->
<file>src</file>
<file>tests</file>
</ruleset>
Loading