Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
waixanda committed Jan 15, 2021
0 parents commit 53eaae3
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor
vendor
8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/php.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions .idea/url-signer.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions composer.json
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/"
}
}
}
23 changes: 23 additions & 0 deletions readme.md
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);
```
102 changes: 102 additions & 0 deletions src/URLSigner.php
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;
}
}
}
104 changes: 104 additions & 0 deletions tests/UrlSignerTest.php
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);
}
}

0 comments on commit 53eaae3

Please sign in to comment.