Skip to content

Commit

Permalink
Merge pull request #105 from l0gicgate/PSR15-Support
Browse files Browse the repository at this point in the history
PSR-15 Support
  • Loading branch information
l0gicgate committed Sep 13, 2019
2 parents 9c0e77a + 7afb074 commit 827c915
Show file tree
Hide file tree
Showing 14 changed files with 830 additions and 783 deletions.
1 change: 1 addition & 0 deletions .coveralls.yml
@@ -0,0 +1 @@
json_path: coveralls-upload.json
8 changes: 8 additions & 0 deletions .editorconfig
@@ -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
@@ -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
@@ -1,4 +1,4 @@
.idea
composer.lock
phpunit.xml
vendor
.idea
coverage
29 changes: 18 additions & 11 deletions .travis.yml
@@ -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
@@ -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
$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
$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();
$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
@@ -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
@@ -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>

0 comments on commit 827c915

Please sign in to comment.