-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 53eaae3
Showing
9 changed files
with
342 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/vendor | ||
vendor |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,19 @@ | ||
{ | ||
"name": "waiyanhein/lumen-signed-url", | ||
"description": "Package to generate temporary signd URL for Lumen framework", | ||
"authors": [ | ||
{ | ||
"name": "Wai Yan Hein", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"php": ">=7.2.0", | ||
"laravel/lumen-framework": ">=5.5.x-dev" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"Waiyanhein\\LumenSignedUrl\\": "src/" | ||
} | ||
} | ||
} |
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,23 @@ | ||
### Package to generate signed url for Lumen framework | ||
|
||
This package use Laravel file storage system to save the signed URLs, https://laravel.com/docs/8.x/filesystem. | ||
|
||
##### Installation | ||
`composer require waiyanhein/lumen-signed-url` | ||
|
||
##### Generating temporary Signed URL | ||
``` | ||
$signedUrl = URLSigner::sign("http://testing.com", Carbon::now()->addMinutes(10)->format('Y-m-d H:i:s')); | ||
``` | ||
- Note: the date must be in `Y-m-d H:i:s` format. | ||
|
||
##### Signing URL with parameters | ||
If your URL has parameters you can pass them as the third parameter as array. | ||
``` | ||
$signedUrl = URLSigner::sign("http://testing.com", Carbon::now()->addMinutes(10)->format('Y-m-d H:i:s'), [ 'first_name' = 'Wai', 'last_name' => 'Hein' ]); | ||
``` | ||
|
||
##### Validating the Signed URL | ||
``` | ||
$isValid = URLSigner::validate($signedUrl); | ||
``` |
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,102 @@ | ||
<?php | ||
|
||
namespace Waiyanhein\LumenSignedUrl; | ||
|
||
use Carbon\Carbon; | ||
use Illuminate\Support\Facades\Storage; | ||
|
||
class URLSigner | ||
{ | ||
static $FILENAME = 'generated-signed-urls.txt'; | ||
static $STORAGE_DISK = 'local'; | ||
|
||
//$expireAt should be string in Y-m-d H:i:s format | ||
//$params is for passing the additional parameters | ||
public static function sign($url, $expireAt, $params = [ ]): ?string | ||
{ | ||
if (empty($url)) { | ||
return null; | ||
} | ||
|
||
if (empty($expireAt)) { | ||
return null; | ||
} | ||
|
||
$millsNow = (string)time(); | ||
$signature = sha1($url . $expireAt . $millsNow) . uniqid() . uniqid(); | ||
$encodedExpireAt = base64_encode($expireAt); | ||
$signedUrl = "{$url}?signature={$signature}&expireAt={$encodedExpireAt}"; | ||
if (count($params) > 0) { | ||
foreach ($params as $key => $value) { | ||
$signedUrl = "{$signedUrl}&{$key}={$value}"; | ||
} | ||
} | ||
$signedUrlData = [ | ||
'expire_at' => $expireAt, | ||
'signature' => $signature, | ||
'url' => $url, | ||
'signed_url' => $signedUrl, | ||
'params' => $params, | ||
]; | ||
|
||
$existingFileContent = ''; | ||
if (Storage::exists(static::$FILENAME)) { | ||
$existingFileContent = Storage::disk(static::$STORAGE_DISK)->get(static::$FILENAME); | ||
} | ||
$signedUrlDataList = []; | ||
if ($existingFileContent) { | ||
try { | ||
$signedUrlDataList = json_decode($existingFileContent); | ||
} catch (\Exception $e) { | ||
|
||
} | ||
} | ||
|
||
$signedUrlDataList[] = $signedUrlData; | ||
Storage::disk(static::$STORAGE_DISK)->put(static::$FILENAME, json_encode($signedUrlDataList)); | ||
|
||
return $signedUrl; | ||
} | ||
|
||
public static function validate($signedUrl) | ||
{ | ||
if (empty($signedUrl)) { | ||
return false; | ||
} | ||
|
||
$urlComponents = parse_url($signedUrl); | ||
if (! isset($urlComponents['query'])) { | ||
return false; | ||
} | ||
|
||
$params = []; | ||
parse_str($urlComponents['query'], $params); | ||
|
||
if (!(isset($params['expireAt']) && isset($params['signature']))) { | ||
return false; | ||
} | ||
|
||
try { | ||
$expireAt = base64_decode($params['expireAt']); | ||
$signature = $params['signature']; | ||
$fileContent = Storage::disk(static::$STORAGE_DISK)->get(static::$FILENAME); | ||
$signedUrlDataList = json_decode($fileContent); | ||
if (! $signedUrlDataList) { | ||
return false; | ||
} | ||
|
||
$signedUrlDataList = collect($signedUrlDataList); | ||
$signedUrl = $signedUrlDataList->where('signature', '=', $signature)->first(); | ||
if (! $signedUrl) { | ||
return false; | ||
} | ||
if ($signedUrl->expire_at != $expireAt) { | ||
return false; | ||
} | ||
|
||
return Carbon::createFromFormat('Y-m-d H:i:s', $expireAt)->gte(Carbon::now()); | ||
} catch (\Exception $exception) { | ||
return false; | ||
} | ||
} | ||
} |
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,104 @@ | ||
<?php | ||
|
||
use Waiyanhein\LumenSignedUrl\URLSigner; | ||
use Carbon\Carbon; | ||
use Illuminate\Support\Facades\Storage; | ||
|
||
class UrlSignerTest extends TestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
if (Storage::exists(URLSigner::$FILENAME)) { | ||
Storage::disk(URLSigner::$STORAGE_DISK)->delete(URLSigner::$FILENAME); | ||
} | ||
} | ||
|
||
public function testItCanGenerateSignedUrl() | ||
{ | ||
$url = 'http://testing.com'; | ||
$expireAt = Carbon::now()->addDays(5)->format('Y-m-d H:i:s'); | ||
$signedUrl = URLSigner::sign($url, $expireAt); | ||
|
||
$this->assertNotEmpty($signedUrl); | ||
//test the file content to ensure that the meta data is correct. | ||
$fileContent = Storage::disk(URLSigner::$STORAGE_DISK)->get(URLSigner::$FILENAME); | ||
$signedUrlList = json_decode($fileContent); | ||
$this->assertEquals($expireAt, $signedUrlList[0]->expire_at); | ||
$this->assertNotEmpty($signedUrlList[0]->signature); | ||
$this->assertEquals($url, $signedUrlList[0]->url); | ||
$encodedExpireAt = base64_encode($expireAt); | ||
$this->assertEquals("{$url}?signature={$signedUrlList[0]->signature}&expireAt={$encodedExpireAt}", $signedUrlList[0]->signed_url); | ||
} | ||
|
||
public function testItCanGenerateSignedUrlWithParams() | ||
{ | ||
$url = 'http://testing.com'; | ||
$expireAt = Carbon::now()->addDays(5)->format('Y-m-d H:i:s'); | ||
$params = [ | ||
'first_name' => 'Wai', | ||
'last_name' => 'Hein', | ||
]; | ||
$signedUrl = URLSigner::sign($url, $expireAt, $params); | ||
|
||
$this->assertNotEmpty($signedUrl); | ||
//test the file content to ensure that the meta data is correct. | ||
$fileContent = Storage::disk(URLSigner::$STORAGE_DISK)->get(URLSigner::$FILENAME); | ||
$signedUrlList = json_decode($fileContent); | ||
$encodedExpireAt = base64_encode($expireAt); | ||
$this->assertEquals("{$url}?signature={$signedUrlList[0]->signature}&expireAt={$encodedExpireAt}&first_name=Wai&last_name=Hein", $signedUrlList[0]->signed_url); | ||
$this->assertEquals('Wai', $signedUrlList[0]->params->first_name); | ||
$this->assertEquals('Hein', $signedUrlList[0]->params->last_name); | ||
} | ||
|
||
public function testSignedUrlValidationPasses() | ||
{ | ||
$url = 'http://testing.com'; | ||
$expireAt = Carbon::now()->addSeconds(10)->format('Y-m-d H:i:s'); | ||
$params = [ | ||
'first_name' => 'Wai', | ||
'last_name' => 'Hein', | ||
]; | ||
$signedUrl = URLSigner::sign($url, $expireAt, $params); | ||
|
||
$this->assertTrue(URLSigner::validate($signedUrl)); | ||
} | ||
|
||
public function testSignedUrlValidationFails() | ||
{ | ||
$url = 'http://testing.com'; | ||
$expireAt = Carbon::now()->subSeconds(5)->format('Y-m-d H:i:s'); | ||
$params = [ | ||
'first_name' => 'Wai', | ||
'last_name' => 'Hein', | ||
]; | ||
$signedUrl = URLSigner::sign($url, $expireAt, $params); | ||
|
||
$this->assertFalse(URLSigner::validate($signedUrl)); | ||
} | ||
|
||
public function testItSavesMultipleSignedUrlsInStorage() | ||
{ | ||
$urls = [ | ||
[ | ||
'url' => 'http://testing1.com', | ||
'expire_at' => Carbon::now()->addMinutes(5)->format('Y-m-d H:i:s'), | ||
], | ||
[ | ||
'url' => 'http://testing2.com', | ||
'expire_at' => Carbon::now()->addMinutes(3)->format('Y-m-d H:i:s'), | ||
] | ||
]; | ||
|
||
$signedUrls = []; | ||
foreach ($urls as $url) { | ||
$signedUrls[] = URLSigner::sign($url['url'], $url['expire_at']); | ||
} | ||
|
||
$fileContent = Storage::disk(URLSigner::$STORAGE_DISK)->get(URLSigner::$FILENAME); | ||
$signedUrlList = json_decode($fileContent); | ||
$this->assertEquals(2, count($signedUrlList)); | ||
$this->assertEquals($signedUrls[0], $signedUrlList[0]->signed_url); | ||
$this->assertEquals($signedUrls[1], $signedUrlList[1]->signed_url); | ||
} | ||
} |