mirror of
https://github.com/humhub/humhub.git
synced 2025-01-17 06:08:21 +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 #4967: Module update broken with expired licence key
|
||||||
- Enh #4972: Fix enabling to send notification on remove user from group
|
- Enh #4972: Fix enabling to send notification on remove user from group
|
||||||
- Fix #4985: Fix Activity Mail QueryParams on console mode
|
- 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 #4989: Translate profile field title in admin list
|
||||||
- Fix #5002: Fix loading of fixture spaces on tests
|
- Fix #5002: Fix loading of fixture spaces on tests
|
||||||
- Fix #5018: Activity stream problems with many user accounts
|
- 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$linkBlock->setBlock($linkBlock->getParsedText(), $file->getUrl());
|
$linkBlock->setBlock($linkBlock->getParsedText(), $file->getUrl(), null, $file->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildFileLink(File $file) : string
|
public static function buildFileLink(File $file) : string
|
||||||
|
@ -16,6 +16,7 @@ class LinkParserBlock extends Model
|
|||||||
const BLOCK_KEY_TITLE = 'title';
|
const BLOCK_KEY_TITLE = 'title';
|
||||||
const BLOCK_KEY_MD = 'orig';
|
const BLOCK_KEY_MD = 'orig';
|
||||||
const BLOCK_KEY_TEXT = 'text';
|
const BLOCK_KEY_TEXT = 'text';
|
||||||
|
const BLOCK_KEY_FILE_ID = 'fileId';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
@ -88,6 +89,16 @@ class LinkParserBlock extends Model
|
|||||||
$this->block[static::BLOCK_KEY_TITLE] = $title;
|
$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()
|
public function getParsedText()
|
||||||
{
|
{
|
||||||
return $this->parsedText;
|
return $this->parsedText;
|
||||||
@ -98,11 +109,12 @@ class LinkParserBlock extends Model
|
|||||||
$this->parsedText = $text;
|
$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->setUrl($url);
|
||||||
$this->setText($text);
|
$this->setText($text);
|
||||||
$this->setTitle($title);
|
$this->setTitle($title);
|
||||||
|
$this->setFileId($fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidate()
|
public function invalidate()
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
|
|
||||||
namespace humhub\modules\file\actions;
|
namespace humhub\modules\file\actions;
|
||||||
|
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use humhub\modules\file\Module;
|
||||||
|
use humhub\modules\user\models\User;
|
||||||
use Yii;
|
use Yii;
|
||||||
|
use yii\helpers\Url;
|
||||||
use yii\web\HttpException;
|
use yii\web\HttpException;
|
||||||
use yii\base\Action;
|
use yii\base\Action;
|
||||||
use humhub\modules\file\models\File;
|
use humhub\modules\file\models\File;
|
||||||
@ -50,8 +54,8 @@ class DownloadAction extends Action
|
|||||||
*/
|
*/
|
||||||
public function init()
|
public function init()
|
||||||
{
|
{
|
||||||
$this->loadFile(Yii::$app->request->get('guid'));
|
$this->loadFile(Yii::$app->request->get('guid'), Yii::$app->request->get('token'));
|
||||||
$this->download = (boolean) Yii::$app->request->get('download', false);
|
$this->download = (boolean)Yii::$app->request->get('download', false);
|
||||||
$this->loadVariant(Yii::$app->request->get('variant', null));
|
$this->loadVariant(Yii::$app->request->get('variant', null));
|
||||||
$this->checkFileExists();
|
$this->checkFileExists();
|
||||||
}
|
}
|
||||||
@ -62,7 +66,7 @@ class DownloadAction extends Action
|
|||||||
*/
|
*/
|
||||||
public function beforeRun()
|
public function beforeRun()
|
||||||
{
|
{
|
||||||
if(Yii::$app->request->isPjax) {
|
if (Yii::$app->request->isPjax) {
|
||||||
throw new HttpException(400, 'File downloads are not allowed with pjax!');
|
throw new HttpException(400, 'File downloads are not allowed with pjax!');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,10 +78,10 @@ class DownloadAction extends Action
|
|||||||
}
|
}
|
||||||
|
|
||||||
$httpCache = new HttpCache();
|
$httpCache = new HttpCache();
|
||||||
$httpCache->lastModified = function() {
|
$httpCache->lastModified = function () {
|
||||||
return Yii::$app->formatter->asTimestamp($this->file->updated_at);
|
return Yii::$app->formatter->asTimestamp($this->file->updated_at);
|
||||||
};
|
};
|
||||||
$httpCache->etagSeed = function() {
|
$httpCache->etagSeed = function () {
|
||||||
if (file_exists($this->getStoredFilePath())) {
|
if (file_exists($this->getStoredFilePath())) {
|
||||||
return md5_file($this->getStoredFilePath());
|
return md5_file($this->getStoredFilePath());
|
||||||
}
|
}
|
||||||
@ -114,23 +118,31 @@ class DownloadAction extends Action
|
|||||||
* Loads the file by given guid
|
* Loads the file by given guid
|
||||||
*
|
*
|
||||||
* @param string $guid
|
* @param string $guid
|
||||||
|
* @param string $token
|
||||||
* @return File the loaded file instance
|
* @return File the loaded file instance
|
||||||
* @throws HttpException
|
* @throws HttpException
|
||||||
*/
|
*/
|
||||||
protected function loadFile($guid)
|
protected function loadFile($guid, $token = null)
|
||||||
{
|
{
|
||||||
$file = File::findOne(['guid' => $guid]);
|
$file = File::findOne(['guid' => $guid]);
|
||||||
|
|
||||||
if ($file == null) {
|
if ($file == null) {
|
||||||
throw new HttpException(404, Yii::t('FileModule.base', 'Could not find requested file!'));
|
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!'));
|
throw new HttpException(401, Yii::t('FileModule.base', 'Insufficient permissions!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->file = $file;
|
$this->file = $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a variant and verifies
|
* Loads a variant and verifies
|
||||||
*
|
*
|
||||||
@ -222,4 +234,60 @@ class DownloadAction extends Action
|
|||||||
return $this->file->store->get($this->variant);
|
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;
|
namespace humhub\modules\file\models;
|
||||||
|
|
||||||
|
use humhub\modules\user\models\User;
|
||||||
use yii\db\ActiveRecord;
|
use yii\db\ActiveRecord;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\helpers\Url;
|
use yii\helpers\Url;
|
||||||
@ -173,6 +174,9 @@ class File extends FileCompat
|
|||||||
*
|
*
|
||||||
* If the file is not an instance of HActiveRecordContent or HActiveRecordContentAddon
|
* If the file is not an instance of HActiveRecordContent or HActiveRecordContentAddon
|
||||||
* the file is readable for all.
|
* the file is readable for all.
|
||||||
|
|
||||||
|
* @param string|User $userId
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function canRead($userId = "")
|
public function canRead($userId = "")
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user