Skip to content

Commit

Permalink
Merge branch 'master' into enh/space-membership-attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
luke- committed Jul 31, 2024
2 parents 476dcdb + 0f8f768 commit 01dcd8c
Show file tree
Hide file tree
Showing 88 changed files with 6,058 additions and 4,895 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/php-test-next.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: PHP Codeception Tests - next

on:
push:
schedule:
- cron: "0 0 * * 0"

jobs:
tests:
uses: humhub/actions/.github/workflows/module-tests-next.yml@main
with:
module-id: rest
4 changes: 3 additions & 1 deletion Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static function onBeforeRequest($event)

// Auth
['pattern' => 'auth/login', 'route' => 'rest/auth/auth/index', 'verb' => ['POST']],
['pattern' => 'auth/impersonate', 'route' => 'rest/auth/auth/impersonate', 'verb' => ['POST']],
['pattern' => 'auth/current', 'route' => 'rest/auth/auth/current', 'verb' => ['GET', 'HEAD']],

// User: Default Controller
Expand All @@ -55,7 +56,8 @@ public static function onBeforeRequest($event)
['pattern' => 'user/group/<id:\d+>/member', 'route' => 'rest/user/group/member-remove', 'verb' => ['DELETE']],

// User: Invite Controller
//['pattern' => 'user/invite', 'route' => 'api/user/invite/index', 'verb' => 'POST'],
['pattern' => 'user/invite', 'route' => 'rest/user/invite/index', 'verb' => 'POST'],
['pattern' => 'user/invite', 'route' => 'rest/user/invite/list', 'verb' => 'GET'],

// User: Session Controller
['pattern' => 'user/session/all/<id:\d+>', 'route' => 'rest/user/session/delete-from-user', 'verb' => 'DELETE'],
Expand Down
14 changes: 1 addition & 13 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
namespace humhub\modules\rest;

use humhub\components\bootstrap\ModuleAutoLoader;
use \humhub\components\Module as BaseModule;
use humhub\modules\user\models\User as UserModel;
use humhub\modules\rest\components\User as UserComponent;
use humhub\components\Module as BaseModule;
use Yii;
use yii\helpers\Url;

Expand All @@ -32,16 +30,6 @@ class Module extends BaseModule
*/
public $resourcesPath = 'resources';

public function init()
{
Yii::$app->set('user', [
'class' => UserComponent::class,
'identityClass' => UserModel::class,
]);

parent::init();
}

/**
* @inheritdoc
*/
Expand Down
106 changes: 61 additions & 45 deletions components/BaseContentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use humhub\modules\topic\models\Topic;
use humhub\modules\topic\permissions\AddTopic;
use Yii;
use yii\base\DynamicModel;
use yii\web\HttpException;
use yii\web\UploadedFile;

Expand All @@ -30,6 +31,7 @@
*
* @package humhub\modules\rest\components
*/

abstract class BaseContentController extends BaseController
{
/**
Expand Down Expand Up @@ -296,50 +298,6 @@ public function actionRemoveFile($id, $fileId)
}
}

/**********************************************************************************
* Helpers
*********************************************************************************
*
/*
* Prepare date formats for Calendar entries and Tasks
*
* @param array $requestParams
* @param string $formName
* @param string $modelName
* @return array
* @throws HttpException
*/
protected function prepareRequestParams(array $requestParams, string $formName, string $modelName): array
{
if ($this instanceof TasksController && empty($requestParams[$modelName]['scheduling'])) {
return $requestParams;
}

if (empty($requestParams[$formName]['start_date'])) {
throw new HttpException(400, 'Start date cannot be blank');
} else {
$requestParams[$modelName]['all_day'] = 0;
}

if (empty($requestParams[$formName]['end_date'])) {
throw new HttpException(400, 'End date cannot be blank');
} else {
$requestParams[$modelName]['all_day'] = 0;
}

if (!preg_match(DbDateValidator::REGEX_DBFORMAT_DATE, $requestParams[$formName]['start_date']) &&
!preg_match(DbDateValidator::REGEX_DBFORMAT_DATETIME, $requestParams[$formName]['start_date'])) {
throw new HttpException(400, 'Wrong start date format.');
}

if (!preg_match(DbDateValidator::REGEX_DBFORMAT_DATE, $requestParams[$formName]['end_date']) &&
!preg_match(DbDateValidator::REGEX_DBFORMAT_DATETIME, $requestParams[$formName]['end_date'])) {
throw new HttpException(400, 'Wrong end date format.');
}

return $requestParams;
}

protected function saveRecord(ContentActiveRecord $contentRecord): bool
{
$data = Yii::$app->request->getBodyParam('data', []);
Expand Down Expand Up @@ -428,6 +386,10 @@ protected function updateMetadata(ContentActiveRecord $activeRecord, array $data
return false;
}

if (!$this->updateVisibility($activeRecord, $data['metadata'])) {
return false;
}

if (!$this->updateArchived($activeRecord, $data['metadata'])) {
return false;
}
Expand All @@ -440,6 +402,14 @@ protected function updateMetadata(ContentActiveRecord $activeRecord, array $data
return false;
}

if (!$this->updateScheduledAt($activeRecord, $data['metadata'])) {
return false;
}

if (Yii::$app->user->identity->isSystemAdmin() && !$this->updateCreatedAt($activeRecord, $data['metadata'])) {
return false;
}

return true;
}

Expand Down Expand Up @@ -526,4 +496,50 @@ protected function updateLockedComments(ContentActiveRecord $activeRecord, array
$activeRecord->content->locked_comments = $data['locked_comments'];
return $activeRecord->content->save();
}
}

protected function updateScheduledAt(ContentActiveRecord $activeRecord, array $data): bool
{
if (!isset($data['scheduled_at'])) {
return true;
}

$validator = DynamicModel::validateData([
'scheduled_at' => $data['scheduled_at']
], [
['scheduled_at', 'datetime', 'format' => 'php:Y-m-d H:i:s']
]);

if (!$validator->validate()) {
$activeRecord->addError('scheduled_at', $validator->getFirstError('scheduled_at'));

return false;
}

$activeRecord->content->getStateService()->schedule($data['scheduled_at']);

return $activeRecord->content->save();
}

public function updateCreatedAt(ContentActiveRecord $activeRecord, array $data): bool
{
if (!isset($data['created_at'])) {
return true;
}

$validator = DynamicModel::validateData([
'created_at' => $data['created_at']
], [
['created_at', 'datetime', 'format' => 'php:Y-m-d H:i:s']
]);

if (!$validator->validate()) {
$activeRecord->addError('created_at', $validator->getFirstError('created_at'));

return false;
}

$activeRecord->content->created_at = $data['created_at'];

return $activeRecord->content->save();
}
}
26 changes: 18 additions & 8 deletions components/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@

namespace humhub\modules\rest\components;

use humhub\components\access\ControllerAccess;
use humhub\components\Controller;
use humhub\modules\content\models\Content;
use humhub\modules\rest\components\auth\ImpersonateAuth;
use humhub\modules\rest\components\User as UserComponent;
use humhub\modules\rest\components\auth\JwtAuth;
use humhub\modules\rest\controllers\auth\AuthController;
use humhub\modules\rest\models\ConfigureForm;
use humhub\modules\user\models\User;
use Yii;
use yii\data\Pagination;
use yii\db\ActiveQuery;
Expand All @@ -16,14 +25,6 @@
use yii\filters\auth\QueryParamAuth;
use yii\helpers\ArrayHelper;
use yii\web\JsonParser;
use Firebase\JWT\JWT;
use humhub\components\access\ControllerAccess;
use humhub\components\Controller;
use humhub\modules\content\models\Content;
use humhub\modules\rest\components\auth\JwtAuth;
use humhub\modules\rest\controllers\auth\AuthController;
use humhub\modules\rest\models\ConfigureForm;
use humhub\modules\user\models\User;

/**
* Class BaseController
Expand Down Expand Up @@ -75,6 +76,9 @@ public function behaviors()
return null;
},
]] : [],
[[
'class' => ImpersonateAuth::class,
]]
),
],
], parent::behaviors());
Expand All @@ -85,6 +89,12 @@ public function behaviors()
*/
public function beforeAction($action)
{
Yii::$app->set('user', [
'class' => UserComponent::class,
'identityClass' => User::class,
'enableSession' => false,
]);

Yii::$app->response->format = 'json';

Yii::$app->request->setBodyParams(null);
Expand Down
61 changes: 61 additions & 0 deletions components/auth/ImpersonateAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\rest\components\auth;

use Yii;
use yii\db\Expression;
use yii\filters\auth\HttpBearerAuth;
use yii\helpers\StringHelper;
use humhub\modules\rest\models\ImpersonateAuthToken;
use humhub\modules\user\models\User;
use Firebase\JWT\JWT;

class ImpersonateAuth extends HttpBearerAuth
{
public $pattern = '/^Impersonate\s+(.*?)$/';

public function authenticate($user, $request, $response)
{
$authHeader = $request->getHeaders()->get($this->header);

if ($authHeader !== null) {
if ($this->pattern !== null) {
if (preg_match($this->pattern, $authHeader, $matches)) {
$authHeader = $matches[1];
} else {
return null;
}

if (!StringHelper::startsWith($authHeader, 'impersonated-')) {
return null;
}
}

$accessToken = ImpersonateAuthToken::find()
->where(['token' => $authHeader])
->andWhere(['>', 'expiration', new Expression('NOW()')])
->one();

if ($accessToken && ($identity = $accessToken->user)) {
$user->login($identity);
Yii::$app->user->isImpersonated = true;
} else {
$identity = null;
}

if ($identity === null) {
$this->challenge($response);
$this->handleFailure($response);
}

return $identity;
}

return null;
}
}
3 changes: 2 additions & 1 deletion components/auth/JwtAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use yii\filters\auth\HttpBearerAuth;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use humhub\modules\rest\models\JwtAuthForm;
use humhub\modules\user\models\User;

Expand All @@ -23,7 +24,7 @@ public function authenticate($user, $request, $response)
$token = $matches[1];

try {
$validData = JWT::decode($token, JwtAuthForm::getInstance()->jwtKey, ['HS512']);
$validData = JWT::decode($token, new Key(JwtAuthForm::getInstance()->jwtKey, 'HS512'));
if (
!empty($validData->uid) &&
($identity = User::find()->active()->andWhere(['user.id' => $validData->uid])->one()) &&
Expand Down
32 changes: 31 additions & 1 deletion controllers/auth/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
use Firebase\JWT\JWT;
use humhub\modules\rest\components\BaseController;
use humhub\modules\rest\definitions\UserDefinitions;
use humhub\modules\rest\models\ImpersonateAuthToken;
use humhub\modules\rest\models\JwtAuthForm;
use humhub\modules\user\models\forms\Login;
use humhub\modules\user\models\User;
use humhub\modules\user\services\AuthClientService;
use Yii;
use yii\web\ForbiddenHttpException;
use yii\web\JsonParser;
use yii\web\NotFoundHttpException;

class AuthController extends BaseController
{
public function beforeAction($action)
{
if ($action->id == 'current') {
if (in_array($action->id, ['current', 'impersonate'])) {
return parent::beforeAction($action);
}

Expand Down Expand Up @@ -92,4 +95,31 @@ public function actionCurrent()

return UserDefinitions::getUser($user);
}

public function actionImpersonate()
{
if (!Yii::$app->user->isAdmin()) {
throw new ForbiddenHttpException();
}

$user = User::findOne(['id' => Yii::$app->request->getBodyParam('userId')]);

if ($user === null) {
throw new NotFoundHttpException();
}

if ($token = ImpersonateAuthToken::findOne(['user_id' => $user->id])) {
$token->delete();
}

$token = new ImpersonateAuthToken();
$token->user_id = $user->id;
$token->save();
$token->refresh();

return [
'token' => $token->token,
'expires' => strtotime($token->expiration),
];
}
}
Loading

0 comments on commit 01dcd8c

Please sign in to comment.