mirror of
https://github.com/humhub/humhub.git
synced 2025-01-16 21:58:17 +01:00
Always allow invitation by link (#7322)
* Always allow invitation by link * Remove unused method `LinkRegistrationService->isEnabled()` * Introduce different targets for invitation links (Administration vs People) * Changes for invitation by link * Update CHANGELOG.md for invitation by link
This commit is contained in:
parent
864a18fc6c
commit
642d327d0f
@ -17,6 +17,7 @@ HumHub Changelog
|
||||
- Fix #7278: Don't remove html tags by JS from search post record because it is done by PHP
|
||||
- Fix #7296: Fix email validation of invite new users
|
||||
- Fix #7319: Display correct profile field value in user subtitle
|
||||
- Fix #7322: Always allow invitation by link from Administration. Implement separate invitation by link from People.
|
||||
|
||||
1.16.2 (September 5, 2024)
|
||||
--------------------------
|
||||
|
@ -26,6 +26,7 @@ use humhub\modules\user\models\Invite;
|
||||
use humhub\modules\user\models\Profile;
|
||||
use humhub\modules\user\models\ProfileField;
|
||||
use humhub\modules\user\models\User;
|
||||
use humhub\modules\user\services\LinkRegistrationService;
|
||||
use Yii;
|
||||
use yii\base\Exception;
|
||||
use yii\db\Query;
|
||||
@ -268,7 +269,7 @@ class UserController extends Controller
|
||||
return $this->redirect(['edit', 'id' => $registration->getUser()->id]);
|
||||
}
|
||||
|
||||
$invite = new InviteForm();
|
||||
$invite = new InviteForm(['target' => LinkRegistrationService::TARGET_ADMIN]);
|
||||
|
||||
return $this->render('add', [
|
||||
'hForm' => $registration,
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use humhub\compat\HForm;
|
||||
use humhub\modules\user\services\LinkRegistrationService;
|
||||
use humhub\widgets\Button;
|
||||
use humhub\widgets\ModalButton;
|
||||
use humhub\modules\ui\form\widgets\ActiveForm;
|
||||
@ -20,7 +21,9 @@ use humhub\modules\ui\form\widgets\ActiveForm;
|
||||
|
||||
<?php if ($canInviteByEmail || $canInviteByLink): ?>
|
||||
<?= ModalButton::success(Yii::t('AdminModule.user', 'Invite new people'))
|
||||
->load(['/user/invite'])->icon('invite')->sm() ?>
|
||||
->load(['/user/invite', 'target' => LinkRegistrationService::TARGET_ADMIN])
|
||||
->icon('invite')
|
||||
->sm() ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
@ -50,6 +50,10 @@ class InviteController extends Controller
|
||||
{
|
||||
$model = new InviteForm();
|
||||
|
||||
if ($target = Yii::$app->request->get('target')) {
|
||||
$model->target = $target;
|
||||
}
|
||||
|
||||
$canInviteByEmail = $model->canInviteByEmail();
|
||||
$canInviteByLink = $model->canInviteByLink();
|
||||
if (!$canInviteByEmail && !$canInviteByLink) {
|
||||
@ -105,6 +109,10 @@ class InviteController extends Controller
|
||||
{
|
||||
$model = new InviteForm();
|
||||
|
||||
if ($target = Yii::$app->request->get('target')) {
|
||||
$model->target = $target;
|
||||
}
|
||||
|
||||
if (!Yii::$app->user->can([ManageUsers::class, ManageGroups::class])) {
|
||||
$this->forbidden();
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ use yii\authclient\BaseClient;
|
||||
use yii\authclient\ClientInterface;
|
||||
use yii\base\Exception;
|
||||
use yii\db\StaleObjectException;
|
||||
use yii\web\BadRequestHttpException;
|
||||
use yii\web\ForbiddenHttpException;
|
||||
use yii\web\HttpException;
|
||||
|
||||
/**
|
||||
@ -138,11 +140,11 @@ class RegistrationController extends Controller
|
||||
$linkRegistrationService = new LinkRegistrationService($token, Space::findOne(['id' => (int)$spaceId]));
|
||||
|
||||
if (!$linkRegistrationService->isEnabled()) {
|
||||
throw new HttpException(404);
|
||||
throw new ForbiddenHttpException('Registration is disabled!');
|
||||
}
|
||||
|
||||
if ($token === null || !$linkRegistrationService->isValid()) {
|
||||
throw new HttpException(400, 'Invalid token provided!');
|
||||
throw new BadRequestHttpException('Invalid token provided!');
|
||||
}
|
||||
|
||||
$linkRegistrationService->storeInSession();
|
||||
|
@ -29,6 +29,11 @@ use yii\validators\StringValidator;
|
||||
*/
|
||||
class Invite extends Model
|
||||
{
|
||||
/**
|
||||
* @var string Target where this form is used
|
||||
*/
|
||||
public string $target = LinkRegistrationService::TARGET_PEOPLE;
|
||||
|
||||
/**
|
||||
* @var string user's username or email address
|
||||
*/
|
||||
@ -81,7 +86,7 @@ class Invite extends Model
|
||||
*
|
||||
* @return array the emails
|
||||
*/
|
||||
public function getEmails()
|
||||
public function getEmails(): array
|
||||
{
|
||||
$emails = [];
|
||||
foreach (explode(',', $this->emails) as $email) {
|
||||
@ -98,7 +103,7 @@ class Invite extends Model
|
||||
* @throws InvalidConfigException
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function canInviteByEmail()
|
||||
public function canInviteByEmail(): bool
|
||||
{
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
@ -115,14 +120,21 @@ class Invite extends Model
|
||||
* @throws Throwable
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function canInviteByLink()
|
||||
public function canInviteByLink(): bool
|
||||
{
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
|
||||
return (!Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByLink'))
|
||||
|| Yii::$app->user->isAdmin()
|
||||
|| Yii::$app->user->can([ManageUsers::class, ManageGroups::class]);
|
||||
if (!Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByLink')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->target === LinkRegistrationService::TARGET_ADMIN) {
|
||||
// Admins always can invite by link
|
||||
return Yii::$app->user->isAdmin() || Yii::$app->user->can([ManageUsers::class, ManageGroups::class]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,9 +142,10 @@ class Invite extends Model
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getInviteLink($forceResetToken = false)
|
||||
public function getInviteLink(bool $forceResetToken = false): string
|
||||
{
|
||||
$linkRegistrationService = new LinkRegistrationService();
|
||||
$linkRegistrationService->target = $this->target;
|
||||
$token = $linkRegistrationService->getStoredToken();
|
||||
if ($forceResetToken || !$token) {
|
||||
$token = $linkRegistrationService->setNewToken();
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
namespace humhub\modules\user\services;
|
||||
|
||||
use humhub\components\SettingsManager;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\modules\user\models\Invite;
|
||||
use humhub\modules\user\models\User;
|
||||
@ -26,9 +27,15 @@ final class LinkRegistrationService
|
||||
{
|
||||
public const SETTING_VAR_ENABLED = 'auth.internalUsersCanInviteByLink';
|
||||
public const SETTING_VAR_SPACE_TOKEN = 'inviteToken';
|
||||
public const SETTING_VAR_TOKEN = 'registration.inviteToken';
|
||||
public const SETTING_VAR_ADMIN_TOKEN = 'registration.inviteToken';
|
||||
public const SETTING_VAR_PEOPLE_TOKEN = 'people.inviteToken';
|
||||
|
||||
public const TARGET_ADMIN = 'admin';
|
||||
public const TARGET_PEOPLE = 'people';
|
||||
|
||||
private ?Space $space;
|
||||
private ?string $token;
|
||||
public ?string $target = null;
|
||||
|
||||
public static function createFromRequest(): LinkRegistrationService
|
||||
{
|
||||
@ -43,24 +50,37 @@ final class LinkRegistrationService
|
||||
return new LinkRegistrationService($token, Space::findOne(['id' => $spaceId]));
|
||||
}
|
||||
|
||||
|
||||
public function __construct(?string $token = null, ?Space $space = null)
|
||||
{
|
||||
$this->token = $token;
|
||||
$this->space = $space;
|
||||
$this->initTarget();
|
||||
}
|
||||
|
||||
private function initTarget(): void
|
||||
{
|
||||
if ($this->token && $this->target === null) {
|
||||
if ($this->token === $this->getSettings()->get(self::SETTING_VAR_ADMIN_TOKEN)) {
|
||||
$this->target = self::TARGET_ADMIN;
|
||||
} elseif ($this->token === $this->getSettings()->get(self::SETTING_VAR_PEOPLE_TOKEN)) {
|
||||
$this->target = self::TARGET_PEOPLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return ($this->isEnabled() && $this->getStoredToken() === $this->token);
|
||||
return $this->isEnabled() && $this->getStoredToken() === $this->token;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
if ($this->target === self::TARGET_ADMIN) {
|
||||
// The link registration with token from Administration is always enabled
|
||||
return true;
|
||||
}
|
||||
|
||||
return (!empty($module->settings->get(self::SETTING_VAR_ENABLED)));
|
||||
return (bool) $this->getSettings()->get(self::SETTING_VAR_ENABLED, false);
|
||||
}
|
||||
|
||||
public function getStoredToken(): ?string
|
||||
@ -69,10 +89,15 @@ final class LinkRegistrationService
|
||||
return $this->space->settings->get(self::SETTING_VAR_SPACE_TOKEN);
|
||||
}
|
||||
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
if ($this->target === self::TARGET_ADMIN) {
|
||||
return $this->getSettings()->get(self::SETTING_VAR_ADMIN_TOKEN);
|
||||
}
|
||||
|
||||
return $module->settings->get(self::SETTING_VAR_TOKEN);
|
||||
if ($this->target === self::TARGET_PEOPLE) {
|
||||
return $this->getSettings()->get(self::SETTING_VAR_PEOPLE_TOKEN);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setNewToken(): string
|
||||
@ -81,10 +106,10 @@ final class LinkRegistrationService
|
||||
if ($this->space) {
|
||||
$this->space->settings->set(self::SETTING_VAR_SPACE_TOKEN, $newToken);
|
||||
} else {
|
||||
/** @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
|
||||
$module->settings->set(self::SETTING_VAR_TOKEN, $newToken);
|
||||
$settingName = $this->target === 'admin'
|
||||
? self::SETTING_VAR_ADMIN_TOKEN
|
||||
: self::SETTING_VAR_PEOPLE_TOKEN;
|
||||
$this->getSettings()->set($settingName, $newToken);
|
||||
}
|
||||
|
||||
return $newToken;
|
||||
@ -147,4 +172,11 @@ final class LinkRegistrationService
|
||||
{
|
||||
return $this->space;
|
||||
}
|
||||
|
||||
private function getSettings(): SettingsManager
|
||||
{
|
||||
/* @var Module $module */
|
||||
$module = Yii::$app->getModule('user');
|
||||
return $module->settings;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use humhub\modules\space\models\forms\InviteForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\modules\user\models\forms\Invite as UserInviteForm;
|
||||
use humhub\modules\user\services\LinkRegistrationService;
|
||||
use user\FunctionalTester;
|
||||
|
||||
@ -9,16 +10,25 @@ class LinkInviteCest
|
||||
{
|
||||
public function testDisabledLinkInvite(FunctionalTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that invite by link is correctly disabled');
|
||||
$I->wantTo('ensure that invitation links are correctly disabled');
|
||||
|
||||
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', 0);
|
||||
|
||||
$inviteForm = new InviteForm();
|
||||
$inviteForm->space = Space::findOne(['name' => 'Space 2']);
|
||||
$inviteUrl = $inviteForm->getInviteLink();
|
||||
$I->amOnPage($inviteForm->getInviteLink());
|
||||
$I->seeResponseCodeIs(403);
|
||||
|
||||
$I->amOnPage($inviteUrl);
|
||||
$I->seeResponseCodeIs(404);
|
||||
$inviteForm = new UserInviteForm();
|
||||
$inviteForm->target = LinkRegistrationService::TARGET_ADMIN;
|
||||
$I->amOnPage($inviteForm->getInviteLink());
|
||||
// The invitation by link is never disabled because admins or user managers always can send it
|
||||
$I->seeResponseCodeIs(200);
|
||||
|
||||
$inviteForm = new UserInviteForm();
|
||||
$inviteForm->target = LinkRegistrationService::TARGET_PEOPLE;
|
||||
$I->amOnPage($inviteForm->getInviteLink());
|
||||
$I->seeResponseCodeIs(403);
|
||||
}
|
||||
|
||||
public function testInvalidToken(FunctionalTester $I)
|
||||
@ -31,18 +41,56 @@ class LinkInviteCest
|
||||
$I->seeResponseCodeIs(400);
|
||||
}
|
||||
|
||||
public function testValidTokenDifferentTarget(FunctionalTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that invitation links are different between targets');
|
||||
|
||||
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', 1);
|
||||
|
||||
$adminInviteForm = new UserInviteForm();
|
||||
$adminInviteForm->target = LinkRegistrationService::TARGET_ADMIN;
|
||||
$firstAdminInviteLink = $adminInviteForm->getInviteLink();
|
||||
|
||||
$peopleInviteForm = new UserInviteForm();
|
||||
$peopleInviteForm->target = LinkRegistrationService::TARGET_PEOPLE;
|
||||
$firstPeopleInviteLink = $peopleInviteForm->getInviteLink();
|
||||
|
||||
$I->amOnPage($firstAdminInviteLink);
|
||||
$I->seeResponseCodeIs(200);
|
||||
|
||||
$I->amOnPage($firstPeopleInviteLink);
|
||||
$I->seeResponseCodeIs(200);
|
||||
|
||||
// Reset only the link with admin target
|
||||
$secondAdminInviteLink = $adminInviteForm->getInviteLink(true);
|
||||
$I->amOnPage($firstAdminInviteLink);
|
||||
$I->seeResponseCodeIs(400); // Invalid token
|
||||
$I->amOnPage($secondAdminInviteLink);
|
||||
$I->seeResponseCodeIs(200); // The second admin token is valid now
|
||||
$I->amOnPage($firstPeopleInviteLink);
|
||||
$I->seeResponseCodeIs(200); // The first people token must be still valid
|
||||
|
||||
// Reset the link with people target
|
||||
$secondPeopleInviteLink = $peopleInviteForm->getInviteLink(true);
|
||||
$I->amOnPage($secondAdminInviteLink);
|
||||
$I->seeResponseCodeIs(200); // The second admin token should be valid
|
||||
$I->amOnPage($firstPeopleInviteLink);
|
||||
$I->seeResponseCodeIs(400); // The first people token is invalid after reset
|
||||
$I->amOnPage($secondPeopleInviteLink);
|
||||
$I->seeResponseCodeIs(200); // The second people token is valid now
|
||||
}
|
||||
|
||||
public function testValidTokenDifferentSpaceId(FunctionalTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that invite by link is with valid token and different space ID');
|
||||
|
||||
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', 1);
|
||||
|
||||
|
||||
// Generate Token
|
||||
$space = Space::findOne(['name' => 'Space 2']);
|
||||
$inviteForm = new InviteForm();
|
||||
$inviteForm->space = $space;
|
||||
$inviteUrl = $inviteForm->getInviteLink();
|
||||
$inviteForm->getInviteLink();
|
||||
|
||||
$linkRegistrationService = new LinkRegistrationService(null, $space);
|
||||
$I->amOnRoute('/user/registration/by-link', ['token' => $linkRegistrationService->getStoredToken(), 'spaceId' => $space->id]);
|
||||
@ -51,14 +99,11 @@ class LinkInviteCest
|
||||
$I->amOnRoute('/user/registration/by-link', ['token' => $linkRegistrationService->getStoredToken(), 'spaceId' => 1]);
|
||||
$I->seeResponseCodeIs(400);
|
||||
|
||||
|
||||
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', 0);
|
||||
$I->amOnRoute('/user/registration/by-link', ['token' => 'abc', 'spaceId' => 1]);
|
||||
$I->seeResponseCodeIs(404);
|
||||
|
||||
$I->seeResponseCodeIs(403);
|
||||
}
|
||||
|
||||
|
||||
public function testSpaceInvite(FunctionalTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that invited users become member of the space');
|
||||
@ -105,6 +150,4 @@ class LinkInviteCest
|
||||
$I->see('User is not member of invited Space!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ use yii\bootstrap\ActiveForm;
|
||||
data-action-confirm-header="<?= Yii::t('SpaceModule.base', 'Create new link') ?>" ,
|
||||
data-action-confirm="<?= Yii::t('SpaceModule.base', 'Please note that any links you have previously created will become invalid as soon as you create a new one. Would you like to proceed?') ?>"
|
||||
data-action-click="ui.modal.load"
|
||||
data-action-click-url="<?= Url::to(['/user/invite/reset-invite-link']) ?>">
|
||||
data-action-click-url="<?= Url::to(['/user/invite/reset-invite-link', 'target' => $model->target]) ?>">
|
||||
<small><?= Yii::t('SpaceModule.base', 'Create new link'); ?></small>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
Loading…
x
Reference in New Issue
Block a user