diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..5ccc87c
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: spatie
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..b26e2ec
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Ask a question
+ url: https://github.com/spatie/lighthouse-php/discussions/new?category=q-a
+ about: Ask the community for help
+ - name: Request a feature
+ url: https://github.com/spatie/lighthouse-php/discussions/new?category=ideas
+ about: Share ideas for new features
+ - name: Report a security issue
+ url: https://github.com/spatie/lighthouse-php/security/policy
+ about: Learn how to notify us for sensitive bugs
+ - name: Report a bug
+ url: https://github.com/spatie/lighthouse-php/issues/new
+ about: Report a reproducable bug
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..30c8a49
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
\ No newline at end of file
diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml
new file mode 100644
index 0000000..e7e28b9
--- /dev/null
+++ b/.github/workflows/dependabot-auto-merge.yml
@@ -0,0 +1,32 @@
+name: dependabot-auto-merge
+on: pull_request_target
+
+permissions:
+ pull-requests: write
+ contents: write
+
+jobs:
+ dependabot:
+ runs-on: ubuntu-latest
+ if: ${{ github.actor == 'dependabot[bot]' }}
+ steps:
+
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v1.3.5
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+ - name: Auto-merge Dependabot PRs for semver-minor updates
+ if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+
+ - name: Auto-merge Dependabot PRs for semver-patch updates
+ if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/fix-php-code-style-issues.yml
similarity index 52%
rename from .github/workflows/php-cs-fixer.yml
rename to .github/workflows/fix-php-code-style-issues.yml
index 5811f0c..150750c 100644
--- a/.github/workflows/php-cs-fixer.yml
+++ b/.github/workflows/fix-php-code-style-issues.yml
@@ -1,21 +1,22 @@
-name: Check & fix styling
+name: Fix PHP code style issues
-on: [push]
+on:
+ push:
+ paths:
+ - '**.php'
jobs:
- php-cs-fixer:
+ php-code-styling:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Run PHP CS Fixer
- uses: docker://oskarstark/php-cs-fixer-ga
- with:
- args: --config=.php-cs-fixer.php --allow-risky=yes
+ - name: Fix PHP code style issues
+ uses: aglipanci/laravel-pint-action@1.0.0
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index ffe3e86..9cbb4ff 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -6,31 +6,32 @@ jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
- fail-fast: true
+ fail-fast: false
matrix:
- os: [ubuntu-latest, windows-latest]
- php: [8.1, 8.0, 7.4]
- stability: [prefer-lowest, prefer-stable]
+ os: [ubuntu-latest]
+ php: [8.2, 8.1]
+ stability: [prefer-stable]
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
- name: Install dependencies
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: Execute tests
- run: vendor/bin/phpunit
+ run: composer test
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
deleted file mode 100644
index ea229df..0000000
--- a/.php-cs-fixer.php
+++ /dev/null
@@ -1,35 +0,0 @@
-in([
- __DIR__ . '/src',
- __DIR__ . '/tests',
- ])
- ->name('*.php')
- ->notName('*.blade.php')
- ->ignoreDotFiles(true)
- ->ignoreVCS(true);
-
-return (new PhpCsFixer\Config())
- ->setRules([
- '@PSR12' => true,
- 'array_syntax' => ['syntax' => 'short'],
- 'ordered_imports' => ['sort_algorithm' => 'alpha'],
- 'no_unused_imports' => true,
- 'not_operator_with_successor_space' => true,
- 'trailing_comma_in_multiline' => true,
- 'phpdoc_scalar' => true,
- 'unary_operator_spaces' => true,
- 'binary_operator_spaces' => true,
- 'blank_line_before_statement' => [
- 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
- ],
- 'phpdoc_single_line_var_spacing' => true,
- 'phpdoc_var_without_name' => true,
- 'method_argument_space' => [
- 'on_multiline' => 'ensure_fully_multiline',
- 'keep_multiple_spaces_after_comma' => true,
- ],
- 'single_trait_insert_per_statement' => true,
- ])
- ->setFinder($finder);
diff --git a/README.md b/README.md
index 1691802..307c2b4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-
-[](https://supportukrainenow.org)
-
# Create secured URLs with a limited lifetime
[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/url-signer.svg?style=flat-square)](https://packagist.org/packages/spatie/url-signer)
@@ -16,7 +13,7 @@ $urlSigner = new MD5UrlSigner('randomkey');
$urlSigner->sign('https://myapp.com', 30);
-// => The generated url will be valid for 30 days
+// => The generated url will be valid for 30 seconds
```
This will output an URL that looks like `https://myapp.com/?expires=xxxx&signature=xxxx`.
@@ -28,8 +25,6 @@ your application can validate it with:
$urlSigner->validate('https://myapp.com/?expires=xxxx&signature=xxxx');
```
-Spatie is a webdesign agency in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
-
## Support us
[](https://spatie.be/github-ad-click/url-signer)
@@ -58,9 +53,9 @@ composer require spatie/url-signer
A signer-object can sign URLs and validate signed URLs. A secret key is used to generate signatures.
```php
-use Spatie\UrlSigner\MD5UrlSigner;
+use Spatie\UrlSigner\Md5UrlSigner;
-$urlSigner = new MD5UrlSigner('mysecretkey');
+$urlSigner = new Md5UrlSigner('mysecretkey');
```
### Generating URLs
@@ -75,12 +70,12 @@ $urlSigner->sign('https://myapp.com', $expirationDate);
// => The generated url will be valid for 10 days
```
-If an integer is provided as expiration date, the url will be valid for that amount of days.
+If an integer is provided as expiration date, the url will be valid for that amount of seconds.
```php
$urlSigner->sign('https://myapp.com', 30);
-// => The generated url will be valid for 30 days
+// => The generated url will be valid for 30 seconds
```
### Validating URLs
@@ -98,6 +93,7 @@ $urlSigner->validate('https://myapp.com/?expires=1439223344&signature=2d42f65bd0
```
## Writing custom signers
+
This packages provides a signer that uses md5 to generate signature. You can create your own
signer by implementing the `Spatie\UrlSigner\UrlSigner`-interface. If you let your signer extend
`Spatie\UrlSigner\BaseUrlSigner` you'll only need to provide the `createSignature`-method.
@@ -107,10 +103,11 @@ signer by implementing the `Spatie\UrlSigner\UrlSigner`-interface. If you let yo
The tests can be run with:
```
-$ vendor/bin/phpspec run
+composer test
```
## Integrations
+
To get started quickly in Laravel you can use the [spatie/laravel-url-signer](https://github.com/spatie/laravel-url-signer) package.
## Changelog
@@ -127,6 +124,7 @@ If you've found a bug regarding security please mail [security@spatie.be](mailto
## Credits
+- [Freek Van der Herten](https://github.com/freekmurze)
- [Sebastian De Deyne](https://github.com/sebastiandedeyne)
- [All Contributors](../../contributors)
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000..3450892
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,4 @@
+## From v1 to v2
+
+- the expiration passed to `sign` and `validate` is now in seconds instead of days
+- all the rest of the API has stayed the same 👍
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 53260a0..3c7deb2 100644
--- a/composer.json
+++ b/composer.json
@@ -11,6 +11,12 @@
"homepage": "https://github.com/spatie/url-signer",
"license": "MIT",
"authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://github.com/freekmurze",
+ "role": "Developer"
+ },
{
"name": "Sebastian De Deyne",
"email": "sebastian@spatie.be",
@@ -19,24 +25,33 @@
}
],
"require": {
- "php": "^7.4|^8.0",
- "league/uri": "^6.0",
- "league/uri-components": "^2.2"
+ "php": "^8.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.5"
+ "pestphp/pest": "^1.22"
},
"autoload": {
"psr-4": {
"Spatie\\UrlSigner\\": "src"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "Spatie\\UrlSigner\\Tests\\": "tests"
+ }
+ },
"scripts": {
- "test": "phpunit"
+ "test": "vendor/bin/pest",
+ "test-coverage": "vendor/bin/pest --coverage",
+ "format": "vendor/bin/pint"
},
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
+ "config": {
+ "sort-packages": true,
+ "allow-plugins": {
+ "phpstan/extension-installer": true,
+ "pestphp/pest-plugin": true
}
- }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
}
diff --git a/phpspec.yml b/phpspec.yml
deleted file mode 100644
index 7fcfac7..0000000
--- a/phpspec.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-suites:
- urlsigner_suite:
- namespace: Spatie\UrlSigner
- psr4_prefix: Spatie\UrlSigner
- src_path: src
diff --git a/src/BaseUrlSigner.php b/src/BaseUrlSigner.php
index 740ebad..58d342f 100644
--- a/src/BaseUrlSigner.php
+++ b/src/BaseUrlSigner.php
@@ -3,244 +3,128 @@
namespace Spatie\UrlSigner;
use DateTime;
-use League\Uri\Http;
-use League\Uri\QueryString;
-use Psr\Http\Message\UriInterface;
use Spatie\UrlSigner\Exceptions\InvalidExpiration;
use Spatie\UrlSigner\Exceptions\InvalidSignatureKey;
+use Spatie\UrlSigner\Support\Url;
abstract class BaseUrlSigner implements UrlSigner
{
- /**
- * The key that is used to generate secure signatures.
- *
- * @var string
- */
- protected $signatureKey;
-
- /**
- * The URL's query parameter name for the expiration.
- *
- * @var string
- */
- protected $expiresParameter;
-
- /**
- * The URL's query parameter name for the signature.
- *
- * @var string
- */
- protected $signatureParameter;
-
- /**
- * @param string $signatureKey
- * @param string $expiresParameter
- * @param string $signatureParameter
- *
- * @throws InvalidSignatureKey
- */
- public function __construct($signatureKey, $expiresParameter = 'expires', $signatureParameter = 'signature')
- {
- if ($signatureKey == '') {
- throw new InvalidSignatureKey('The signature key is empty');
+ public function __construct(
+ protected string $defaultSignatureKey,
+ protected string $expiresParameterName = 'expires',
+ protected string $signatureParameterName = 'signature'
+ ) {
+ if ($this->defaultSignatureKey == '') {
+ throw InvalidSignatureKey::signatureEmpty();
}
-
- $this->signatureKey = $signatureKey;
- $this->expiresParameter = $expiresParameter;
- $this->signatureParameter = $signatureParameter;
}
- /**
- * Get a secure URL to a controller action.
- *
- * @param string $url
- * @param \DateTime|int $expiration
- *
- * @throws InvalidExpiration
- *
- * @return string
- */
- public function sign($url, $expiration)
- {
- $url = Http::createFromString($url);
+ abstract protected function createSignature(
+ string $url,
+ string $expiration,
+ string $signatureKey,
+ ): string;
+
+ public function sign(
+ string $url,
+ int|DateTime $expiration,
+ string $signatureKey = null,
+ ): string {
+ $signatureKey ??= $this->defaultSignatureKey;
$expiration = $this->getExpirationTimestamp($expiration);
- $signature = $this->createSignature((string) $url, $expiration);
- return (string) $this->signUrl($url, $expiration, $signature);
+ $signature = $this->createSignature($url, $expiration, $signatureKey);
+
+ return $this->signUrl($url, $expiration, $signature);
}
- /**
- * Add expiration and signature query parameters to an url.
- *
- * @param UriInterface $url
- * @param string $expiration
- * @param string $signature
- *
- * @return \League\Url\UrlImmutable
- */
- protected function signUrl(UriInterface $url, $expiration, $signature)
+ protected function signUrl(string $url, string $expiration, $signature): string
{
- $query = QueryString::extract($url->getQuery());
-
- $query[$this->expiresParameter] = $expiration;
- $query[$this->signatureParameter] = $signature;
-
- return $url->withQuery($this->buildQueryStringFromArray($query));
+ return Url::addQueryParameters($url, [
+ $this->expiresParameterName => $expiration,
+ $this->signatureParameterName => $signature,
+ ]);
}
- /**
- * Validate a signed url.
- *
- * @param string $url
- *
- * @return bool
- */
- public function validate($url)
+ public function validate(string $url, string $signatureKey = null): bool
{
- $url = Http::createFromString($url);
-
- $query = QueryString::extract($url->getQuery());
+ $signatureKey ??= $this->defaultSignatureKey;
- if ($this->isMissingAQueryParameter($query)) {
+ $queryParameters = Url::queryParameters($url);
+ if ($this->isMissingAQueryParameter($queryParameters)) {
return false;
}
- $expiration = $query[$this->expiresParameter];
+ $expiration = $queryParameters[$this->expiresParameterName];
if (! $this->isFuture($expiration)) {
return false;
}
- if (! $this->hasValidSignature($url)) {
+ if (! $this->hasValidSignature($url, $signatureKey)) {
return false;
}
return true;
}
- /**
- * Generate a token to identify the secure action.
- *
- * @param UriInterface|string $url
- * @param string $expiration
- *
- * @return string
- */
- abstract protected function createSignature($url, string $expiration);
-
- /**
- * Check if a query is missing a necessary parameter.
- *
- * @param array $query
- *
- * @return bool
- */
- protected function isMissingAQueryParameter(array $query)
+ protected function isMissingAQueryParameter(array $query): bool
{
- if (! isset($query[$this->expiresParameter])) {
+ if (! isset($query[$this->expiresParameterName])) {
return true;
}
- if (! isset($query[$this->signatureParameter])) {
+ if (! isset($query[$this->signatureParameterName])) {
return true;
}
return false;
}
- /**
- * Check if a timestamp is in the future.
- *
- * @param int $timestamp
- *
- * @return bool
- */
- protected function isFuture($timestamp)
+ protected function isFuture(int $timestamp): bool
{
- return ((int) $timestamp) >= (new DateTime())->getTimestamp();
+ return $timestamp >= (new DateTime())->getTimestamp();
}
- /**
- * Retrieve the intended URL by stripping off the UrlSigner specific parameters.
- *
- * @param UriInterface $url
- *
- * @return UriInterface
- */
- protected function getIntendedUrl(UriInterface $url)
+ protected function getIntendedUrl(string $url): string
{
- $intendedQuery = QueryString::extract($url->getQuery());
-
- unset($intendedQuery[$this->expiresParameter]);
- unset($intendedQuery[$this->signatureParameter]);
-
- return $url->withQuery($this->buildQueryStringFromArray($intendedQuery) ?? '');
+ return Url::withoutParameters($url, [
+ $this->expiresParameterName,
+ $this->signatureParameterName,
+ ]);
}
- /**
- * Retrieve the expiration timestamp for a link based on an absolute DateTime or a relative number of days.
- *
- * @param \DateTime|int $expiration The expiration date of this link.
- * - DateTime: The value will be used as expiration date
- * - int: The expiration time will be set to X days from now
- *
- * @throws \Spatie\UrlSigner\Exceptions\InvalidExpiration
- *
- * @return string
- */
- protected function getExpirationTimestamp($expiration)
+ protected function getExpirationTimestamp(DateTime|int $expirationInSeconds): string
{
- if (is_int($expiration)) {
- $expiration = (new DateTime())->modify((int) $expiration.' days');
+ if (is_int($expirationInSeconds)) {
+ $expirationInSeconds = (new DateTime())->modify($expirationInSeconds.' seconds');
}
- if (! $expiration instanceof DateTime) {
- throw new InvalidExpiration('Expiration date must be an instance of DateTime or an integer');
+ if (! $expirationInSeconds instanceof DateTime) {
+ throw InvalidExpiration::wrongType();
}
- if (! $this->isFuture($expiration->getTimestamp())) {
- throw new InvalidExpiration('Expiration date must be in the future');
+ if (! $this->isFuture($expirationInSeconds->getTimestamp())) {
+ throw InvalidExpiration::isInPast();
}
- return (string) $expiration->getTimestamp();
+ return (string) $expirationInSeconds->getTimestamp();
}
- /**
- * Determine if the url has a forged signature.
- *
- * @param UriInterface $url
- *
- * @return bool
- */
- protected function hasValidSignature(UriInterface $url)
- {
- $query = QueryString::extract($url->getQuery());
+ protected function hasValidSignature(
+ string $url,
+ string $signatureKey,
+ ): bool {
+ $queryParameters = Url::queryParameters($url);
- $expiration = $query[$this->expiresParameter];
- $providedSignature = $query[$this->signatureParameter];
+ $expiration = $queryParameters[$this->expiresParameterName];
+ $providedSignature = $queryParameters[$this->signatureParameterName];
$intendedUrl = $this->getIntendedUrl($url);
- $validSignature = $this->createSignature($intendedUrl, $expiration);
+ $validSignature = $this->createSignature($intendedUrl, $expiration, $signatureKey);
return hash_equals($validSignature, $providedSignature);
}
-
- /**
- * Turn a key => value associate array into a query string.
- *
- * @param array $query
- *
- * @return string|null
- */
- protected function buildQueryStringFromArray(array $query)
- {
- $buildQuery = [];
- foreach ($query as $key => $value) {
- $buildQuery[] = [$key, $value];
- }
-
- return QueryString::build($buildQuery);
- }
}
diff --git a/src/Exceptions/InvalidExpiration.php b/src/Exceptions/InvalidExpiration.php
index 1d49edc..b41ae4a 100644
--- a/src/Exceptions/InvalidExpiration.php
+++ b/src/Exceptions/InvalidExpiration.php
@@ -2,6 +2,17 @@
namespace Spatie\UrlSigner\Exceptions;
-class InvalidExpiration extends \Exception
+use Exception;
+
+class InvalidExpiration extends Exception
{
+ public static function isInPast(): self
+ {
+ return new self('Expiration date must be in the future');
+ }
+
+ public static function wrongType(): self
+ {
+ return new self('Expiration date must be an instance of DateTime or an integer');
+ }
}
diff --git a/src/Exceptions/InvalidSignatureKey.php b/src/Exceptions/InvalidSignatureKey.php
index dd7f8bb..3bf0b10 100644
--- a/src/Exceptions/InvalidSignatureKey.php
+++ b/src/Exceptions/InvalidSignatureKey.php
@@ -2,6 +2,12 @@
namespace Spatie\UrlSigner\Exceptions;
-class InvalidSignatureKey extends \Exception
+use Exception;
+
+class InvalidSignatureKey extends Exception
{
+ public static function signatureEmpty(): self
+ {
+ return new self('The signature key is empty');
+ }
}
diff --git a/src/MD5UrlSigner.php b/src/MD5UrlSigner.php
deleted file mode 100644
index ac5e54a..0000000
--- a/src/MD5UrlSigner.php
+++ /dev/null
@@ -1,21 +0,0 @@
-signatureKey}");
- }
-}
diff --git a/src/Md5UrlSigner.php b/src/Md5UrlSigner.php
new file mode 100644
index 0000000..72bb144
--- /dev/null
+++ b/src/Md5UrlSigner.php
@@ -0,0 +1,14 @@
+assertInstanceOf(MD5UrlSigner::class, $urlSigner);
- }
-
- /** @test */
- public function it_will_throw_an_exception_for_an_empty_signatureKey()
- {
- $this->expectException(InvalidSignatureKey::class);
-
- $urlSigner = new MD5UrlSigner('');
- }
-
- /** @test */
- public function it_returns_false_when_validating_a_forged_url()
- {
- $signedUrl = 'http://myapp.com/somewhereelse/?expires=4594900544&signature=41d5c3a92c6ef94e73cb70c7dcda0859';
- $urlSigner = new MD5UrlSigner('random_monkey');
-
- $this->assertFalse($urlSigner->validate($signedUrl));
- }
-
- /** @test */
- public function it_returns_false_when_validating_an_expired_url()
- {
- $signedUrl = 'http://myapp.com/?expires=1123690544&signature=93e02326d7572632dd6edfa2665f2743';
- $urlSigner = new MD5UrlSigner('random_monkey');
-
- $this->assertFalse($urlSigner->validate($signedUrl));
- }
-
- /** @test */
- public function it_returns_true_when_validating_an_non_expired_url()
- {
- $url = 'http://myapp.com';
- $expiration = 10000;
- $urlSigner = new MD5UrlSigner('random_monkey');
- $signedUrl = $urlSigner->sign($url, $expiration);
-
- $this->assertTrue($urlSigner->validate($signedUrl));
- }
-
- public function unsignedUrlProvider()
- {
- return [
- ['http://myapp.com/?expires=4594900544'],
- ['http://myapp.com/?signature=41d5c3a92c6ef94e73cb70c7dcda0859'],
- ];
- }
-
- /**
- * @test
- * @dataProvider unsignedUrlProvider
- */
- public function it_returns_false_when_validating_an_unsigned_url($unsignedUrl)
- {
- $urlSigner = new MD5UrlSigner('random_monkey');
-
- $this->assertFalse($urlSigner->validate($unsignedUrl));
- }
-
- /** @test */
- public function it_does_a_strict_check_on_expirations()
- {
- $url = 'http://myapp.com';
- $expiration = '30';
- $urlSigner = new MD5UrlSigner('random_monkey');
-
- $this->expectException(InvalidExpiration::class);
-
- $urlSigner->sign($url, $expiration);
- }
-
- public function pastExpirationProvider()
- {
- return [
- [DateTime::createFromFormat('d/m/Y H:i:s', '10/08/2005 18:15:44')],
- [-10],
- ];
- }
-
- /**
- * @test
- * @dataProvider pastExpirationProvider
- */
- public function it_doesnt_allow_expirations_in_the_past($pastExpiration)
- {
- $url = 'http://myapp.com';
- $urlSigner = new MD5UrlSigner('random_monkey');
-
- $this->expectException(InvalidExpiration::class);
-
- $urlSigner->sign($url, $pastExpiration);
- }
-
- /** @test */
- public function it_keeps_the_urls_query_parameters_intact()
- {
- $url = 'https://myapp.com/?foo=bar&baz=qux';
- $expiration = DateTime::createFromFormat(
- 'd/m/Y H:i:s',
- '10/08/2115 18:15:44',
- new DateTimeZone('Europe/Brussels')
- );
- $expected = 'https://myapp.com/?foo=bar&baz=qux&expires=4594900544&signature=728971d9fd0682793d2a1e96b734d949';
-
- $urlSigner = new MD5UrlSigner('random_monkey');
- $signedUrl = $urlSigner->sign($url, $expiration);
-
- $this->assertStringContainsString('?foo=bar&baz=qux', $signedUrl);
- $this->assertTrue($urlSigner->validate($signedUrl));
- }
-}
diff --git a/tests/Md5UrlSignerTest.php b/tests/Md5UrlSignerTest.php
new file mode 100644
index 0000000..1cb27f9
--- /dev/null
+++ b/tests/Md5UrlSignerTest.php
@@ -0,0 +1,85 @@
+urlSigner = new Md5UrlSigner('random_monkey');
+});
+
+it('can be initialized', function () {
+ expect($this->urlSigner)->toBeInstanceOf(UrlSigner::class);
+});
+
+it('will throw an exception fro an empty signature key', function () {
+ new Md5UrlSigner('');
+})->throws(InvalidSignatureKey::class);
+
+it('returns false when validating a forged url', function () {
+ $signedUrl = 'http://myapp.com/somewhereelse/?expires=4594900544&signature=41d5c3a92c6ef94e73cb70c7dcda0859';
+
+ expect($this->urlSigner->validate($signedUrl))->toBeFalse();
+});
+
+it('returns false when validating an expired url', function () {
+ $signedUrl = 'http://myapp.com/?expires=1123690544&signature=93e02326d7572632dd6edfa2665f2743';
+
+ expect($this->urlSigner->validate($signedUrl))->toBeFalse();
+});
+
+it('returns true when validating a non-expired url', function () {
+ $url = 'http://myapp.com';
+
+ $expiration = 10000;
+ $signedUrl = $this->urlSigner->sign($url, $expiration);
+
+ expect($this->urlSigner->validate($signedUrl))->toBeTrue();
+});
+
+it('returns false when validating an unsigned url', function (string $unsignedUrl) {
+ expect($this->urlSigner->validate($unsignedUrl))->toBeFalse();
+})->with('unsignedUrls');
+
+it('does not allow expirations in the past', function ($pastExpiration) {
+ $url = 'http://myapp.com';
+
+ $this->urlSigner->sign($url, $pastExpiration);
+})->with([
+ [DateTime::createFromFormat('d/m/Y H:i:s', '10/08/2005 18:15:44')],
+ [-10],
+])->throws(InvalidExpiration::class);
+
+it('will keep url query parameters intact', function () {
+ $url = 'https://myapp.com/?foo=bar&baz=qux';
+ $expiration = DateTime::createFromFormat(
+ 'd/m/Y H:i:s',
+ '10/08/2115 18:15:44',
+ new DateTimeZone('Europe/Brussels')
+ );
+
+ $signedUrl = $this->urlSigner->sign($url, $expiration);
+
+ expect($signedUrl)->toContain('?foo=bar&baz=qux');
+ expect($this->urlSigner->validate($signedUrl))->toBeTrue();
+});
+
+dataset('unsignedUrls', [
+ ['http://myapp.com/?expires=4594900544'],
+ ['http://myapp.com/?signature=41d5c3a92c6ef94e73cb70c7dcda0859'],
+]);
+
+it('using a custom key results in a different signed url', function () {
+ $signedUsingRegularKey = $this->urlSigner->sign('https://spatie.be', 5);
+ $signedUsingCustomKey = $this->urlSigner->sign('https://spatie.be', 5, 'custom-key');
+
+ expect($signedUsingRegularKey)->not()->toBe($signedUsingCustomKey);
+});
+
+it('can sign and validate urls with a custom key', function () {
+ $signedUsingCustomKey = $this->urlSigner->sign('https://spatie.be', 5, 'custom-key');
+
+ expect($this->urlSigner->validate($signedUsingCustomKey, 'custom-key'))->toBeTrue();
+ expect($this->urlSigner->validate($signedUsingCustomKey, 'wrong-custom-key'))->toBeFalse();
+});
diff --git a/tests/Support/StrTest.php b/tests/Support/StrTest.php
new file mode 100644
index 0000000..570c5dd
--- /dev/null
+++ b/tests/Support/StrTest.php
@@ -0,0 +1,28 @@
+toBe($expected);
+})->with([
+ ['https://spatie.be?hey', '?', 'hey'],
+ ['https://spatie.be', '?', ''],
+ ['https://spatie.be?', '?', ''],
+ ['https://?spatie.be?', '?', 'spatie.be?'],
+ ['https://?spatie.be?', '!', ''],
+]);
+
+it('can get the string before a string', function (string $string, string $after, string $expected) {
+ $actual = Str::before($string, $after);
+
+ expect($actual)->toBe($expected);
+})->with([
+ ['https://spatie.be?hey', '?', 'https://spatie.be'],
+ ['https://spatie.be', '?', 'https://spatie.be'],
+ ['https://?spatie.be?', '?', 'https://'],
+ ['https://?spatie.be?', '!', 'https://?spatie.be?'],
+ ['?https://spatie.be?hey', '?', ''],
+
+]);
diff --git a/tests/Support/UrlTest.php b/tests/Support/UrlTest.php
new file mode 100644
index 0000000..eeebd24
--- /dev/null
+++ b/tests/Support/UrlTest.php
@@ -0,0 +1,20 @@
+toBe($actualParameters);
+})->with([
+ ['spatie.be?a=1&b=2', ['a' => '1', 'b' => '2']],
+ //['spatie.be', []],
+]);
+
+it('can add query parameters to a URL', function (string $url, array $add, string $expectedUrl) {
+ $actualUrl = Url::addQueryParameters($url, $add);
+
+ expect($expectedUrl)->toBe($actualUrl);
+})->with([
+ ['spatie.be', ['a' => 1, 'b' => 2], 'spatie.be?a=1&b=2'],
+]);