168 lines
5.3 KiB
PHP
168 lines
5.3 KiB
PHP
|
|
<?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;
|
||
|
|
}
|
||
|
|
}
|