-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
To prevent BC breaks, a new interface and new classes have been created. While at it, I introduced a SHA256 signer, a lot more secure than the MD5 one.
- Loading branch information
1 parent
56fa414
commit cdb9f7a
Showing
9 changed files
with
287 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,22 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit bootstrap="vendor/autoload.php" | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" | ||
bootstrap="vendor/autoload.php" | ||
backupGlobals="false" | ||
backupStaticAttributes="false" | ||
backupStaticProperties="false" | ||
cacheDirectory=".phpunit.cache" | ||
colors="true" | ||
verbose="true" | ||
convertErrorsToExceptions="true" | ||
convertNoticesToExceptions="true" | ||
convertWarningsToExceptions="true" | ||
processIsolation="false" | ||
stopOnFailure="false"> | ||
<coverage/> | ||
<testsuites> | ||
<testsuite name="spatie Test Suite"> | ||
<directory>tests</directory> | ||
</testsuite> | ||
</testsuites> | ||
<filter> | ||
<whitelist> | ||
<source> | ||
<include> | ||
<directory suffix=".php">src/</directory> | ||
</whitelist> | ||
</filter> | ||
</include> | ||
</source> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<?php | ||
|
||
namespace Spatie\UrlSigner; | ||
|
||
use DateTime; | ||
use DateTimeInterface; | ||
use Spatie\UrlSigner\Contracts\UrlSigner as UrlSignerContract; | ||
use Spatie\UrlSigner\Exceptions\InvalidExpiration; | ||
use Spatie\UrlSigner\Exceptions\InvalidSignatureKey; | ||
use Spatie\UrlSigner\Support\Url; | ||
|
||
abstract class AbstractUrlSigner implements UrlSignerContract | ||
{ | ||
public function __construct( | ||
protected string $defaultSignatureKey, | ||
protected string $expiresParameterName = 'expires', | ||
protected string $signatureParameterName = 'signature' | ||
) { | ||
if ($this->defaultSignatureKey == '') { | ||
throw InvalidSignatureKey::signatureEmpty(); | ||
} | ||
} | ||
|
||
abstract protected function createSignature( | ||
string $url, | ||
string $expiration, | ||
string $signatureKey, | ||
): string; | ||
|
||
public function sign( | ||
string $url, | ||
int|DateTimeInterface $expiration, | ||
string $signatureKey = null, | ||
): string { | ||
$signatureKey ??= $this->defaultSignatureKey; | ||
|
||
$expiration = $this->getExpirationTimestamp($expiration); | ||
|
||
$signature = $this->createSignature($url, $expiration, $signatureKey); | ||
|
||
return $this->signUrl($url, $expiration, $signature); | ||
} | ||
|
||
protected function signUrl(string $url, string $expiration, $signature): string | ||
{ | ||
return Url::addQueryParameters($url, [ | ||
$this->expiresParameterName => $expiration, | ||
$this->signatureParameterName => $signature, | ||
]); | ||
} | ||
|
||
public function validate(string $url, string $signatureKey = null): bool | ||
{ | ||
$signatureKey ??= $this->defaultSignatureKey; | ||
|
||
$queryParameters = Url::queryParameters($url); | ||
if ($this->isMissingAQueryParameter($queryParameters)) { | ||
return false; | ||
} | ||
|
||
$expiration = $queryParameters[$this->expiresParameterName]; | ||
|
||
if (! $this->isFuture($expiration)) { | ||
return false; | ||
} | ||
|
||
if (! $this->hasValidSignature($url, $signatureKey)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
protected function isMissingAQueryParameter(array $query): bool | ||
{ | ||
if (! isset($query[$this->expiresParameterName])) { | ||
return true; | ||
} | ||
|
||
if (! isset($query[$this->signatureParameterName])) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
protected function isFuture(int $timestamp): bool | ||
{ | ||
return $timestamp >= (new DateTime())->getTimestamp(); | ||
} | ||
|
||
protected function getIntendedUrl(string $url): string | ||
{ | ||
return Url::withoutParameters($url, [ | ||
$this->expiresParameterName, | ||
$this->signatureParameterName, | ||
]); | ||
} | ||
|
||
protected function getExpirationTimestamp(DateTimeInterface|int $expirationInSeconds): string | ||
{ | ||
if (is_int($expirationInSeconds)) { | ||
$expirationInSeconds = (new DateTime())->modify($expirationInSeconds.' seconds'); | ||
} | ||
|
||
if (! $expirationInSeconds instanceof DateTimeInterface) { | ||
throw InvalidExpiration::wrongType(); | ||
} | ||
|
||
if (! $this->isFuture($expirationInSeconds->getTimestamp())) { | ||
throw InvalidExpiration::isInPast(); | ||
} | ||
|
||
return (string) $expirationInSeconds->getTimestamp(); | ||
} | ||
|
||
protected function hasValidSignature( | ||
string $url, | ||
string $signatureKey, | ||
): bool { | ||
$queryParameters = Url::queryParameters($url); | ||
|
||
$expiration = $queryParameters[$this->expiresParameterName]; | ||
$providedSignature = $queryParameters[$this->signatureParameterName]; | ||
|
||
$intendedUrl = $this->getIntendedUrl($url); | ||
|
||
$validSignature = $this->createSignature($intendedUrl, $expiration, $signatureKey); | ||
|
||
return hash_equals($validSignature, $providedSignature); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
namespace Spatie\UrlSigner\Contracts; | ||
|
||
use DateTimeInterface; | ||
|
||
interface UrlSigner | ||
{ | ||
public function sign( | ||
string $url, | ||
int|DateTimeInterface $expiration, | ||
string $signatureKey = null | ||
): string; | ||
|
||
public function validate(string $url, string $signatureKey = null): bool; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
namespace Spatie\UrlSigner; | ||
|
||
class Sha256UrlSigner extends AbstractUrlSigner | ||
{ | ||
protected function createSignature( | ||
string $url, | ||
string $expiration, | ||
string $signatureKey | ||
): string { | ||
return hash_hmac('sha256', "{$url}::{$expiration}", $signatureKey); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.