mirror of
https://github.com/humhub/humhub.git
synced 2025-01-16 21:58:17 +01:00
Allow to read image URLs with token param (#4995)
Co-authored-by: Lucas Bartholemy <luke-@users.noreply.github.com>
This commit is contained in:
parent
c481e03787
commit
95233c1c19
@ -11,6 +11,7 @@ HumHub Changelog
|
||||
- Enh #4967: Module update broken with expired licence key
|
||||
- Enh #4972: Fix enabling to send notification on remove user from group
|
||||
- Fix #4985: Fix Activity Mail QueryParams on console mode
|
||||
- Enh #23: Allow to read image URLs with token param
|
||||
- Fix #4989: Translate profile field title in admin list
|
||||
- Fix #5002: Fix loading of fixture spaces on tests
|
||||
- Fix #5018: Activity stream problems with many user accounts
|
||||
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\widgets\richtext\converter;
|
||||
|
||||
use humhub\modules\content\widgets\richtext\extensions\link\LinkParserBlock;
|
||||
use humhub\modules\file\actions\DownloadAction;
|
||||
use humhub\modules\file\models\File;
|
||||
use humhub\modules\user\models\User;
|
||||
|
||||
/**
|
||||
* This parser can be used to convert HumHub richtext directly to email html in order to view images from email inbox where
|
||||
* user is not logged in so access is restricted.
|
||||
*
|
||||
* @since 1.8.2
|
||||
*/
|
||||
class RichTextToEmailHtmlConverter extends RichTextToHtmlConverter
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function renderPlainImage(LinkParserBlock $linkBlock): string
|
||||
{
|
||||
return parent::renderPlainImage($this->tokenizeBlock($linkBlock));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a param 'token' to the URL in order to allow see it when user is not logged in e.g. from email inbox
|
||||
*
|
||||
* @param LinkParserBlock $linkBlock
|
||||
* @return LinkParserBlock
|
||||
*/
|
||||
protected function tokenizeBlock(LinkParserBlock $linkBlock): LinkParserBlock
|
||||
{
|
||||
/* @var User $receiver */
|
||||
$receiver = $this->getOption('receiver');
|
||||
|
||||
if (!($receiver && $linkBlock->getUrl() && $linkBlock->getFileId())) {
|
||||
return $linkBlock;
|
||||
}
|
||||
|
||||
$token = '';
|
||||
if ($linkBlock->getFileId() !== null) {
|
||||
$file = File::findOne(['id' => $linkBlock->getFileId()]);
|
||||
if ($file !== null) {
|
||||
$token = DownloadAction::generateDownloadToken($file, $receiver);
|
||||
}
|
||||
}
|
||||
|
||||
$linkBlock->setUrl($linkBlock->getUrl() . (strpos($linkBlock->getUrl(), '?') === false ? '?' : '&') . 'token=' . $token);
|
||||
|
||||
return $linkBlock;
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ class FileExtension extends RichTextLinkExtension
|
||||
return;
|
||||
}
|
||||
|
||||
$linkBlock->setBlock($linkBlock->getParsedText(), $file->getUrl());
|
||||
$linkBlock->setBlock($linkBlock->getParsedText(), $file->getUrl(), null, $file->id);
|
||||
}
|
||||
|
||||
public static function buildFileLink(File $file) : string
|
||||
|
@ -16,6 +16,7 @@ class LinkParserBlock extends Model
|
||||
const BLOCK_KEY_TITLE = 'title';
|
||||
const BLOCK_KEY_MD = 'orig';
|
||||
const BLOCK_KEY_TEXT = 'text';
|
||||
const BLOCK_KEY_FILE_ID = 'fileId';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@ -88,6 +89,16 @@ class LinkParserBlock extends Model
|
||||
$this->block[static::BLOCK_KEY_TITLE] = $title;
|
||||
}
|
||||
|
||||
public function getFileId() : ?string
|
||||
{
|
||||
return $this->block[static::BLOCK_KEY_FILE_ID] ?? null;
|
||||
}
|
||||
|
||||
public function setFileId(string $fileId = null)
|
||||
{
|
||||
$this->block[static::BLOCK_KEY_FILE_ID] = $fileId;
|
||||
}
|
||||
|
||||
public function getParsedText()
|
||||
{
|
||||
return $this->parsedText;
|
||||
@ -98,11 +109,12 @@ class LinkParserBlock extends Model
|
||||
$this->parsedText = $text;
|
||||
}
|
||||
|
||||
public function setBlock(string $text, string $url, string $title = null)
|
||||
public function setBlock(string $text, string $url, string $title = null, $fileId = null)
|
||||
{
|
||||
$this->setUrl($url);
|
||||
$this->setText($text);
|
||||
$this->setTitle($title);
|
||||
$this->setFileId($fileId);
|
||||
}
|
||||
|
||||
public function invalidate()
|
||||
|
@ -8,7 +8,11 @@
|
||||
|
||||
namespace humhub\modules\file\actions;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use humhub\modules\file\Module;
|
||||
use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
use yii\helpers\Url;
|
||||
use yii\web\HttpException;
|
||||
use yii\base\Action;
|
||||
use humhub\modules\file\models\File;
|
||||
@ -50,8 +54,8 @@ class DownloadAction extends Action
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->loadFile(Yii::$app->request->get('guid'));
|
||||
$this->download = (boolean) Yii::$app->request->get('download', false);
|
||||
$this->loadFile(Yii::$app->request->get('guid'), Yii::$app->request->get('token'));
|
||||
$this->download = (boolean)Yii::$app->request->get('download', false);
|
||||
$this->loadVariant(Yii::$app->request->get('variant', null));
|
||||
$this->checkFileExists();
|
||||
}
|
||||
@ -62,7 +66,7 @@ class DownloadAction extends Action
|
||||
*/
|
||||
public function beforeRun()
|
||||
{
|
||||
if(Yii::$app->request->isPjax) {
|
||||
if (Yii::$app->request->isPjax) {
|
||||
throw new HttpException(400, 'File downloads are not allowed with pjax!');
|
||||
}
|
||||
|
||||
@ -74,10 +78,10 @@ class DownloadAction extends Action
|
||||
}
|
||||
|
||||
$httpCache = new HttpCache();
|
||||
$httpCache->lastModified = function() {
|
||||
$httpCache->lastModified = function () {
|
||||
return Yii::$app->formatter->asTimestamp($this->file->updated_at);
|
||||
};
|
||||
$httpCache->etagSeed = function() {
|
||||
$httpCache->etagSeed = function () {
|
||||
if (file_exists($this->getStoredFilePath())) {
|
||||
return md5_file($this->getStoredFilePath());
|
||||
}
|
||||
@ -114,23 +118,31 @@ class DownloadAction extends Action
|
||||
* Loads the file by given guid
|
||||
*
|
||||
* @param string $guid
|
||||
* @param string $token
|
||||
* @return File the loaded file instance
|
||||
* @throws HttpException
|
||||
*/
|
||||
protected function loadFile($guid)
|
||||
protected function loadFile($guid, $token = null)
|
||||
{
|
||||
$file = File::findOne(['guid' => $guid]);
|
||||
|
||||
if ($file == null) {
|
||||
throw new HttpException(404, Yii::t('FileModule.base', 'Could not find requested file!'));
|
||||
}
|
||||
if (!$file->canRead()) {
|
||||
|
||||
$user = nulL;
|
||||
if ($token !== null) {
|
||||
$user = static::getUserByDownloadToken($token, $file);
|
||||
}
|
||||
|
||||
if (!$file->canRead($user)) {
|
||||
throw new HttpException(401, Yii::t('FileModule.base', 'Insufficient permissions!'));
|
||||
}
|
||||
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a variant and verifies
|
||||
*
|
||||
@ -222,4 +234,60 @@ class DownloadAction extends Action
|
||||
return $this->file->store->get($this->variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the User model by given JWT token
|
||||
*
|
||||
* @param string $token
|
||||
* @param File $file
|
||||
* @return User|null
|
||||
*/
|
||||
public static function getUserByDownloadToken(string $token, File $file)
|
||||
{
|
||||
try {
|
||||
$decoded = JWT::decode($token, static::getDownloadTokenKey(), ['HS256']);
|
||||
} catch (\Exception $ex) {
|
||||
Yii::warning('Could not decode provided JWT token. ' . $ex->getMessage());
|
||||
}
|
||||
if (!empty($decoded['sub']) && !empty($decoded['aud']) && $decoded['aud'] == $file->id) {
|
||||
return User::findOne(['id' => $decoded['sub']]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token to access this file by JWT token
|
||||
*
|
||||
* @param File $file
|
||||
* @param User $user
|
||||
* @return string
|
||||
*/
|
||||
public static function generateDownloadToken(File $file, User $user)
|
||||
{
|
||||
$token = [
|
||||
'iss' => 'dld-token-v1',
|
||||
'sub' => Yii::$app->user->id,
|
||||
'aud' => $file->id
|
||||
];
|
||||
return JWT::encode($token, static::getDownloadTokenKey());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string the secret key for file download tokens
|
||||
* @throws \yii\base\Exception
|
||||
*/
|
||||
private static function getDownloadTokenKey()
|
||||
{
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('file');
|
||||
|
||||
$key = $module->settings->get('downloadTokenKey');
|
||||
if (empty($key)) {
|
||||
$key = Yii::$app->security->generateRandomString(32);
|
||||
$module->settings->set('downloadTokenKey', $key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
namespace humhub\modules\file\models;
|
||||
|
||||
use humhub\modules\user\models\User;
|
||||
use yii\db\ActiveRecord;
|
||||
use Yii;
|
||||
use yii\helpers\Url;
|
||||
@ -173,6 +174,9 @@ class File extends FileCompat
|
||||
*
|
||||
* If the file is not an instance of HActiveRecordContent or HActiveRecordContentAddon
|
||||
* the file is readable for all.
|
||||
|
||||
* @param string|User $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($userId = "")
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user