diff --git a/src/Bkwld/Croppa/Handler.php b/src/Bkwld/Croppa/Handler.php index 864e060..f8731fd 100644 --- a/src/Bkwld/Croppa/Handler.php +++ b/src/Bkwld/Croppa/Handler.php @@ -1,9 +1,11 @@ url = $url; $this->storage = $storage; + $this->request = $request; } /** @@ -40,6 +44,12 @@ public function __construct(URL $url, Storage $storage) { */ public function handle($request) { + // Validate the signing token + if (($token = $this->url->signingToken($request)) + && $token != $this->request->input('token')) { + throw new NotFoundHttpException('Token missmatch'); + } + // Get crop path relative to it's dir $crop_path = $this->url->relativePath($request); diff --git a/src/Bkwld/Croppa/ServiceProvider.php b/src/Bkwld/Croppa/ServiceProvider.php index 4b64b83..6955693 100755 --- a/src/Bkwld/Croppa/ServiceProvider.php +++ b/src/Bkwld/Croppa/ServiceProvider.php @@ -29,7 +29,9 @@ public function register() { // Handle the request for an image, this cooridnates the main logic $this->app->singleton('Bkwld\Croppa\Handler', function($app) { - return new Handler($app['Bkwld\Croppa\URL'], $app['Bkwld\Croppa\Storage']); + return new Handler($app['Bkwld\Croppa\URL'], + $app['Bkwld\Croppa\Storage'], + $app['request']); }); // Interact with the disk @@ -109,7 +111,13 @@ public function bootLaravel5() { */ public function getConfig() { $key = $this->version() == 5 ? 'croppa' : 'croppa::config'; - return $this->app->make('config')->get($key); + $config = $this->app->make('config')->get($key); + + // Use Laravel's encryption key if instructed to + if (isset($config['secure_key']) && $config['secure_key'] == 'app.key') { + $config['secure_key'] = $this->app->make('config')->get('app.key'); + } + return $config; } /** diff --git a/src/Bkwld/Croppa/URL.php b/src/Bkwld/Croppa/URL.php index f457a67..4da82f7 100644 --- a/src/Bkwld/Croppa/URL.php +++ b/src/Bkwld/Croppa/URL.php @@ -66,11 +66,17 @@ public function generate($url, $width = null, $height = null, $options = null) { } } - // Assemble the new path and return + // Assemble the new path $parts = pathinfo($path); $path = trim($parts['dirname'],'/').'/'.$parts['filename'].$suffix; if (isset($parts['extension'])) $path .= '.'.$parts['extension']; - return $this->pathToUrl($path); + $url = $this->pathToUrl($path); + + // Secure with hash token + if ($token = $this->signingToken($url)) $url .= '?token='.$token; + + // Return the $url + return $url; } /** @@ -95,6 +101,20 @@ public function pathToUrl($path) { else return rtrim($this->config['url_prefix'], '/').'/'.$this->relativePath($path); } + /** + * Generate the signing token from a URL or path. Or, if no key was defined, + * return nothing. + * + * @param string path or url + * @return string|void + */ + public function signingToken($url) { + if (isset($this->config['signing_key']) + && ($key = $this->config['signing_key'])) { + return md5($key.basename($url)); + } + } + /** * Make the regex for the route definition. This works by wrapping both the * basic Croppa pattern and the `path` config in positive regex lookaheads so diff --git a/src/config/config.php b/src/config/config.php index be9a467..e4402ca 100755 --- a/src/config/config.php +++ b/src/config/config.php @@ -31,11 +31,12 @@ /** * Maximum number of sizes to allow for a particular source file. This is to * limit scripts from filling up your hard drive with images. Set to falsey or - * comment out to have no limit. + * comment out to have no limit. This is disabled by default because the + * `signing_key` is a better prevention of malicilous usage. * * @var integer | boolean */ - 'max_crops' => 12, + 'max_crops' => false, /* @@ -74,6 +75,19 @@ // 'url_prefix' => '//'.Request::getHttpHost().'/uploads/', // Local // 'url_prefix' => 'https://your-bucket.s3.amazonaws.com/uploads/', // S3 + /** + * Reject attempts to maliciously create images by signing the generated the + * request with a hash based on the request parameters and this signing key. + * Set to 'app.key' to use Laravel's `app.key` config, any other string to use + * THAT as the key, or false to disable. + * + * If you are generating URLs outside of `Croppa::url()`, like the croppa.js + * module, you can disable this feature by setting the `signing_key` config + * to false. + * + * @var string|boolean + */ + 'signing_key' => 'app.key', /* |----------------------------------------------------------------------------- diff --git a/tests/TestUrlGenerator.php b/tests/TestUrlGenerator.php index 5f89c2a..3637da8 100644 --- a/tests/TestUrlGenerator.php +++ b/tests/TestUrlGenerator.php @@ -69,4 +69,14 @@ public function testCropsInSubDirectory() { $this->assertEquals('/images/crops/file-200x100.png', $url->generate('/images/file.png', 200, 100)); } + public function testSecure() { + + $url = new URL([ 'signing_key' => 'test' ]); + $this->assertEquals('/path/file-200x100.png?token=dc0787d205f619a2b2df8554c960072e', $url->generate('/path/file.png', 200, 100)); + + $url = new URL([ 'signing_key' => 'test' ]); + $this->assertNotEquals('/path/file-200x100.png?token=dc0787d205f619a2b2df8554c960072e', $url->generate('/path/file.png', 200, 200)); + + } + } \ No newline at end of file