Initial Upload
This commit is contained in:
167
lib/Helper/LinkSigner.php
Normal file
167
lib/Helper/LinkSigner.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Helper;
|
||||
|
||||
use Xibo\Entity\Display;
|
||||
|
||||
/**
|
||||
* S3 style links
|
||||
* inspired by https://gist.github.com/kelvinmo/d78be66c4f36415a6b80
|
||||
*/
|
||||
class LinkSigner
|
||||
{
|
||||
/**
|
||||
* @param \Xibo\Entity\Display $display
|
||||
* @param string $encryptionKey
|
||||
* @param string|null $cdnUrl
|
||||
* @param $type
|
||||
* @param $itemId
|
||||
* @param string $storedAs
|
||||
* @param string|null $fileType
|
||||
* @return string
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public static function generateSignedLink(
|
||||
Display $display,
|
||||
string $encryptionKey,
|
||||
?string $cdnUrl,
|
||||
$type,
|
||||
$itemId,
|
||||
string $storedAs,
|
||||
string $fileType = null,
|
||||
bool $isRequestFromPwa = false,
|
||||
): string {
|
||||
// Start with the base url, which should correctly account for running with a CMS_ALIAS
|
||||
$xmdsRoot = (new HttpsDetect())->getBaseUrl();
|
||||
|
||||
// PWA requests resources via `/pwa/getResource`, but the link should be served from `/xmds.php`
|
||||
if ($isRequestFromPwa) {
|
||||
$xmdsRoot = str_replace('/pwa/getResource', '/xmds.php', $xmdsRoot);
|
||||
}
|
||||
|
||||
// Build the rest of the URL
|
||||
$saveAsPath = $xmdsRoot
|
||||
. '?file=' . $storedAs
|
||||
. '&displayId=' . $display->displayId
|
||||
. '&type=' . $type
|
||||
. '&itemId=' . $itemId;
|
||||
|
||||
if ($fileType !== null) {
|
||||
$saveAsPath .= '&fileType=' . $fileType;
|
||||
}
|
||||
|
||||
$saveAsPath .= '&' . LinkSigner::getSignature(
|
||||
parse_url($xmdsRoot, PHP_URL_HOST),
|
||||
$storedAs,
|
||||
time() + ($display->getSetting('collectionInterval', 300) * 2),
|
||||
$encryptionKey,
|
||||
);
|
||||
|
||||
// CDN?
|
||||
if (!empty($cdnUrl)) {
|
||||
// Serve a link to the CDN
|
||||
// CDN_URL has a `?dl=` parameter on the end already, so we just encode our string and concatenate it
|
||||
return 'http' . (
|
||||
(
|
||||
(isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
|
||||
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
|
||||
) ? 's' : '')
|
||||
. '://' . $cdnUrl . urlencode($saveAsPath);
|
||||
} else {
|
||||
// Serve a HTTP link to XMDS
|
||||
return $saveAsPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a S3 compatible signature
|
||||
*/
|
||||
public static function getSignature(
|
||||
string $host,
|
||||
string $uri,
|
||||
int $expires,
|
||||
string $secretKey,
|
||||
?string $timeText = null,
|
||||
?bool $isReturnSignature = false
|
||||
): string {
|
||||
$encodedUri = str_replace('%2F', '/', rawurlencode($uri));
|
||||
$headerString = 'host:' . $host . "\n";
|
||||
$signedHeadersString = 'host';
|
||||
|
||||
if ($timeText === null) {
|
||||
$timestamp = time();
|
||||
$dateText = gmdate('Ymd', $timestamp);
|
||||
$timeText = $dateText . 'T000000Z';
|
||||
} else {
|
||||
$dateText = explode('T', $timeText)[0];
|
||||
}
|
||||
|
||||
$algorithm = 'AWS4-HMAC-SHA256';
|
||||
$scope = $dateText . '/all/s3/aws4_request';
|
||||
|
||||
$amzParams = [
|
||||
'X-Amz-Algorithm' => $algorithm,
|
||||
'X-Amz-Date' => $timeText,
|
||||
'X-Amz-SignedHeaders' => $signedHeadersString
|
||||
];
|
||||
if ($expires > 0) {
|
||||
$amzParams['X-Amz-Expires'] = $expires;
|
||||
}
|
||||
ksort($amzParams);
|
||||
|
||||
$queryStringItems = [];
|
||||
foreach ($amzParams as $key => $value) {
|
||||
$queryStringItems[] = rawurlencode($key) . '=' . rawurlencode($value);
|
||||
}
|
||||
$queryString = implode('&', $queryStringItems);
|
||||
|
||||
$request = 'GET' . "\n" . $encodedUri . "\n" . $queryString . "\n" . $headerString . "\n"
|
||||
. $signedHeadersString . "\nUNSIGNED-PAYLOAD";
|
||||
$stringToSign = $algorithm . "\n" . $timeText . "\n" . $scope . "\n" . hash('sha256', $request);
|
||||
$signingKey = hash_hmac(
|
||||
'sha256',
|
||||
'aws4_request',
|
||||
hash_hmac(
|
||||
'sha256',
|
||||
's3',
|
||||
hash_hmac(
|
||||
'sha256',
|
||||
'all',
|
||||
hash_hmac(
|
||||
'sha256',
|
||||
$dateText,
|
||||
'AWS4' . $secretKey,
|
||||
true
|
||||
),
|
||||
true
|
||||
),
|
||||
true
|
||||
),
|
||||
true
|
||||
);
|
||||
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
|
||||
|
||||
return ($isReturnSignature) ? $signature : $queryString . '&X-Amz-Signature=' . $signature;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user