Merge branch 'master' into develop

This commit is contained in:
Lucas Bartholemy 2019-11-26 17:19:48 +01:00
commit 1778325b2e
20 changed files with 336 additions and 74 deletions

View File

@ -58,8 +58,7 @@
"yiisoft/yii2-redis": "~2.0.0",
"yiisoft/yii2-swiftmailer": "~2.0.0",
"zendframework/zend-http": "*",
"zendframework/zend-ldap": "^2.5",
"ext-zip": "*"
"zendframework/zend-ldap": "^2.5"
},
"require-dev": {
"codeception/codeception": "2.4.5",

View File

@ -1,9 +1,26 @@
HumHub Change Log
=================
1.3.18 (November 22, 2019)
--------------------------
1.3.16 (Unreleased)
-------------------------
- Fix #3698: Approval by group manager broken
- Enh: Set max signs of User tags to 250
- Fix: https://github.com/humhub/humhub-modules-wiki/issues/103 Topic attachment fails with given topic instances
- Fix #3366: Set max-width for stream entry image file attachments
- Fix: `ContentContainerController::getAccess` uses `ContentContainerControllerAccess` also for global access
- Fix: Global content does not allow guest access
- Fix #3727 Bug with Bluebird and Firefox 71 breaking mentioning requests
1.3.17 (October 17, 2019)
--------------------------
- Fix #3690: Rollback from broken version 2.8.6 to 2.8.1 of bower-asset/clipboard.js
1.3.16 (October 17, 2019)
--------------------------
- Fix #3647: GroupID field is not translatable in Registration Form
- Fix #3655: Fix CommentLink widget Comment class usage conflict

View File

@ -52,6 +52,20 @@ Available params:
- `allowedLanguages` see the [Translations Section](translations.md)
- `enablePjax` used to disable/enable pjax support (default true)
# Module Configuration
Some modules may allow further configurations by overwriting fields of their `Module.php` class.
Those configurations can be overwritten within your `common.php` file as follows:
```php
return [
'modules' => [
'directory' => [
'guestAccess' => false
]
]
]
```
# Statistics/Tracking

View File

@ -45,7 +45,7 @@ In order to provide scheme updates for new versions, just follow the same steps
You can manually execute new migrations by the following commands:
```
php yii migrate/up --inclueModuleMigrations=1
php yii migrate/up --includeModuleMigrations=1
```
or by grunt

View File

@ -8,16 +8,15 @@
namespace humhub\modules\admin\controllers;
use humhub\components\access\ControllerAccess;
use humhub\modules\admin\models\UserApprovalSearch;
use Yii;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\web\ForbiddenHttpException;
use yii\web\HttpException;
use humhub\modules\user\models\User;
use humhub\modules\admin\components\Controller;
use humhub\modules\admin\models\forms\ApproveUserForm;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\admin\permissions\ManageGroups;
/**
* ApprovalController handels new user approvals
@ -29,6 +28,9 @@ class ApprovalController extends Controller
*/
public $adminOnly = false;
/**
* @inheritdoc
*/
public function init()
{
$this->subLayout = '@admin/views/layouts/user';
@ -43,22 +45,32 @@ class ApprovalController extends Controller
public function getAccessRules()
{
return [
['permissions' => [
ManageUsers::class,
ManageGroups::class
]]
[ControllerAccess::RULE_LOGGED_IN_ONLY],
['checkCanApproveUsers'],
];
}
/**
* @param $rule
* @param $access
* @return bool
* @throws \Throwable
*/
public function checkCanApproveUsers($rule, $access)
{
if (!Yii::$app->user->getIdentity()->canApproveUsers()) {
$access->code = 403;
return false;
}
return true;
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if (!Yii::$app->user->getIdentity()->canApproveUsers()) {
throw new ForbiddenHttpException(Yii::t('error', 'You are not allowed to perform this action.'));
}
if (!Yii::$app->user->isAdmin()) {
$this->subLayout = "@humhub/modules/admin/views/approval/_layoutNoAdmin";
}
@ -68,7 +80,7 @@ class ApprovalController extends Controller
public function actionIndex()
{
$searchModel = new \humhub\modules\admin\models\UserApprovalSearch();
$searchModel = new UserApprovalSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
@ -77,12 +89,9 @@ class ApprovalController extends Controller
]);
}
public function actionApprove()
public function actionApprove($id)
{
$user = User::findOne(['id' => (int) Yii::$app->request->get('id')]);
if ($user == null)
throw new HttpException(404, Yii::t('AdminModule.user', 'User not found!'));
$user = $this->getUser($id);
$model = new ApproveUserForm;
$model->subject = Yii::t('AdminModule.user', "Account Request for '{displayName}' has been approved.", ['{displayName}' => Html::encode($user->displayName)]);
@ -106,13 +115,9 @@ class ApprovalController extends Controller
]);
}
public function actionDecline()
public function actionDecline($id)
{
$user = User::findOne(['id' => (int) Yii::$app->request->get('id')]);
if ($user == null)
throw new HttpException(404, Yii::t('AdminModule.user', 'User not found!'));
$user = $this->getUser($id);
$model = new ApproveUserForm;
$model->subject = Yii::t('AdminModule.user', 'Account Request for \'{displayName}\' has been declined.', ['{displayName}' => Html::encode($user->displayName)]);
@ -133,4 +138,18 @@ class ApprovalController extends Controller
]);
}
private function getUser($id)
{
$user = User::find()
->andWhere(['user.id' => (int)Yii::$app->request->get('id'), 'user.status' => User::STATUS_NEED_APPROVAL])
->administrableBy(Yii::$app->user->getIdentity())
->one();
if ($user == null) {
throw new HttpException(404, Yii::t('AdminModule.controllers_ApprovalController', 'User not found!'));
}
return $user;
}
}

View File

@ -48,12 +48,13 @@ class UserApprovalSearch extends User
* Creates data provider instance with search query applied
*
* @param array $params
*
* @return ActiveDataProvider
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
*/
public function search($params = [])
{
$query = User::find()->joinWith(['profile', 'groups']);
$query = User::find()->joinWith(['profile']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
@ -78,25 +79,7 @@ class UserApprovalSearch extends User
return $dataProvider;
}
/**
* Limit Groups
*/
$groups = $this->getGroups();
$groupIds = [];
foreach ($groups as $group) {
$groupIds[] = $group->id;
}
if (Yii::$app->user->isAdmin()) {
$query->andWhere([
'or',
['IN', 'group.id', $groupIds],
'group.id IS NULL'
]);
} else {
$query->andWhere(['IN', 'group.id', $groupIds]);
}
$query->administrableBy(Yii::$app->user->getIdentity());
$query->andWhere(['status' => User::STATUS_NEED_APPROVAL]);
$query->andFilterWhere(['id' => $this->id]);
@ -114,15 +97,4 @@ class UserApprovalSearch extends User
return User::find()->where(['status' => User::STATUS_NEED_APPROVAL])->count();
}
/**
* Get approval groups
*/
public function getGroups()
{
if (Yii::$app->user->isAdmin()) {
return \humhub\modules\user\models\Group::findAll(['is_admin_group' => '0']);
} else {
return Yii::$app->user->getIdentity()->managerGroups;
}
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace admin\functional;
use humhub\modules\admin\permissions\ManageModules;
use humhub\modules\admin\permissions\ManageSpaces;
use humhub\modules\admin\permissions\SeeAdminInformation;
use humhub\modules\user\models\Invite;
use tests\codeception\_pages\AdminPage;
use admin\FunctionalTester;
use Yii;
class ApprovalCest
{
public function testApproveByAdmin(FunctionalTester $I)
{
$I->wantTo('ensure that admins can approve users');
$settingsManager = Yii::$app->getModule('user')->settings;
$settingsManager->set('auth.needApproval', 1);
$settingsManager->set('auth.anonymousRegistration', 1);
$settingsManager->set('auth.allowGuestAccess', 0);
$this->register($I);
$I->amAdmin();
$this->approveUser($I);
}
public function testApproveByGroupManager(FunctionalTester $I)
{
$I->wantTo('ensure that group manager can approve users');
$settingsManager = Yii::$app->getModule('user')->settings;
$settingsManager->set('auth.needApproval', 1);
$settingsManager->set('auth.anonymousRegistration', 1);
$settingsManager->set('auth.allowGuestAccess', 0);
$this->register($I);
// User1 is group manager of the User group which is the only gorup available at registration
$I->amUser1();
$this->approveUser($I);
}
public function testApproveNotAllowedByOtherGroupManager(FunctionalTester $I)
{
$I->wantTo('ensure that group manager can not approve users of another group');
$settingsManager = Yii::$app->getModule('user')->settings;
$settingsManager->set('auth.needApproval', 1);
$settingsManager->set('auth.anonymousRegistration', 1);
$settingsManager->set('auth.allowGuestAccess', 0);
$this->register($I);
// User2
$I->amUser2();
$I->amOnDashboard();
$I->see('New approval requests');
$I->click('Click here to review');
$I->see('Pending user approvals');
$I->dontSee('approvalTest@test.de');
// This user was created by fixtures
$I->see('unnapproved@example.com');
// Try to approve the user of another group
$I->amOnRoute('/admin/approval/approve', ['id' => 8]);
$I->seeResponseCodeIs(404);
}
public function testApproveNotAllowedByNormalUser(FunctionalTester $I)
{
$I->wantTo('ensure that normal users have no access to the approval page');
$settingsManager = Yii::$app->getModule('user')->settings;
$settingsManager->set('auth.needApproval', 1);
$settingsManager->set('auth.anonymousRegistration', 1);
$settingsManager->set('auth.allowGuestAccess', 0);
$this->register($I);
// User2
$I->amUser3();
$I->amOnDashboard();
$I->dontSee('New approval requests');
$I->amOnRoute('/admin/approval');
$I->seeResponseCodeIs(403);
$I->amOnRoute('/admin/approval/approve', ['id' => 8]);
$I->seeResponseCodeIs(403);
}
private function register(FunctionalTester $I)
{
$I->amOnRoute('/user/auth/login');
$I->see('Sign up');
$I->fillField('#register-email', 'approvalTest@test.de');
$I->click('Register');
$I->see('Registration successful!');
$invte = Invite::find()->all()[0];
$I->amOnRoute('/user/registration', ['token' => $invte->token]);
$I->see('Account registration');
$I->fillField(['name' => 'User[username]'], 'approvalTest');
$I->fillField(['name' => 'Password[newPassword]'], 'approva1TestPassword');
$I->fillField(['name' => 'Password[newPasswordConfirm]'], 'approva1TestPassword');
$I->fillField(['name' => 'Profile[firstname]'], 'approval');
$I->fillField(['name' => 'Profile[lastname]'], 'test');
$I->click('Create account');
$I->see('Your account has been successfully created!');
$I->see('After activating your account by the administrator');
}
private function approveUser(FunctionalTester $I)
{
Yii::$app->settings->set('displayNameFormat', '{profile.firstname} {profile.lastname}');
$I->amOnDashboard();
$I->see('New approval requests');
$I->click('Click here to review');
$I->see('Pending user approvals');
$I->see('approvalTest@test.de');
$I->amOnRoute('/admin/approval/approve', ['id' => 8]);
$I->see('Accept user: approval test');
$I->click('Send & save');
$I->logout();
$I->amUser('approvalTest', 'approva1TestPassword');
$I->seeElement('#wallStream');
}
}

View File

@ -119,9 +119,13 @@ class ContentContainerController extends Controller
*/
public function getAccess()
{
if($this->contentContainer) {
return new ContentContainerControllerAccess(['contentContainer' => $this->contentContainer]);
}
return parent::getAccess();
}
/**
* Checks if the requested module is available in this contentContainer.
*

View File

@ -791,8 +791,20 @@ class Content extends ActiveRecord implements Movable, ContentOwner
return false;
}
// Check container visibility for guests
return ($this->container instanceof Space && $this->container->visibility == Space::VISIBILITY_ALL) || ($this->container instanceof User && $this->container->visibility == User::VISIBILITY_ALL);
// GLobal content
if(!$this->container) {
return $this->isPublic();
}
if($this->container instanceof Space) {
return $this->isPublic() && $this->container->visibility == Space::VISIBILITY_ALL;
}
if($this->container instanceof User) {
return $this->isPublic() && $this->container->visibility == User::VISIBILITY_ALL;
}
return false;
}
/**

View File

@ -31,7 +31,7 @@ class Sidebar extends BaseSidebar
if ($module->hideActivitySidebarWidget) {
foreach ($this->widgets as $k => $widget) {
if (isset($widget[0]) && $widget[0] === ActivityStreamViewer::class) {
if (isset($widget[0]) && ($widget[0] === ActivityStreamViewer::class || $widget[0] === 'humhub\modules\activity\widgets\Stream')) {
unset($this->widgets[$k]);
}
}

View File

@ -8,6 +8,8 @@
namespace humhub\modules\user\components;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\user\models\GroupUser;
use humhub\modules\user\models\Group;
use humhub\modules\user\models\ProfileField;
use yii\db\ActiveQuery;
@ -137,4 +139,29 @@ class ActiveQueryUser extends ActiveQuery
return $this;
}
/**
* Returns only users which are administrable by the given user.
*
* @param UserModel $user
* @return ActiveQueryUser the query
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
*/
public function administrableBy(UserModel $user)
{
if (!(new PermissionManager(['subject' => $user]))->can([ManageUsers::class])) {
$this->joinWith('groups');
$groupIds = [];
foreach (GroupUser::find()->where(['user_id' => $user->id, 'is_group_manager' => 1])->all() as $gu) {
$groupIds[] = $gu->group_id;
}
$this->andWhere(['IN', 'group.id', $groupIds]);
}
return $this;
}
}

View File

@ -58,9 +58,17 @@ class Group extends ActiveRecord
[['space_id', 'sort_order'], 'integer'],
[['description'], 'string'],
[['name'], 'string', 'max' => 45],
['show_at_registration', 'validateShowAtRegistration'],
];
}
public function validateShowAtRegistration($attribute, $params)
{
if($this->is_admin_group && $this->show_at_registration) {
$this->addError($attribute, 'Admin group can\'t be a registration group!');
}
}
/**
* @inheritdoc
*/
@ -328,7 +336,7 @@ class Group extends ActiveRecord
return $groups;
}
} else {
$groups = self::find()->where(['show_at_registration' => '1'])->orderBy('name ASC')->all();
$groups = self::find()->where(['show_at_registration' => 1, 'is_admin_group' => 0])->orderBy('name ASC')->all();
}
return $groups;

View File

@ -9,6 +9,8 @@
namespace humhub\modules\user\models;
use humhub\components\behaviors\GUID;
use humhub\modules\admin\permissions\ManageGroups;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\content\components\behaviors\CompatModuleManager;
use humhub\modules\content\components\behaviors\SettingsBehavior;
use humhub\modules\content\components\ContentContainerActiveRecord;
@ -24,6 +26,7 @@ use humhub\modules\user\authclient\Password as PasswordAuth;
use humhub\modules\user\behaviors\Followable;
use humhub\modules\user\behaviors\ProfileController;
use humhub\modules\user\components\ActiveQueryUser;
use humhub\modules\user\components\PermissionManager;
use humhub\modules\user\events\UserEvent;
use humhub\modules\user\helpers\AuthHelper;
use humhub\modules\user\Module;
@ -741,6 +744,7 @@ class User extends ContentContainerActiveRecord implements IdentityInterface, Se
* User can approve other users
*
* @return boolean
* @throws \yii\base\InvalidConfigException
*/
public function canApproveUsers()
{
@ -748,6 +752,10 @@ class User extends ContentContainerActiveRecord implements IdentityInterface, Se
return true;
}
if((new PermissionManager(['subject' => $this]))->can([ManageUsers::class, ManageGroups::class])) {
return true;
}
return $this->getManagerGroups()->count() > 0;
}

View File

@ -30,7 +30,7 @@ class AccountSettings extends \yii\base\Model
public function rules()
{
return [
['tags', 'string', 'max' => 100],
['tags', 'string', 'max' => 250],
[['show_introduction_tour'], 'boolean'],
[['timeZone'], 'in', 'range' => \DateTimeZone::listIdentifiers()],
['language', 'in', 'range' => array_keys(Yii::$app->i18n->getAllowedLanguages())],

View File

@ -21,6 +21,7 @@ class UserFullFixture extends ActiveFixture
'humhub\modules\content\tests\codeception\fixtures\ContentContainerFixture',
'humhub\modules\user\tests\codeception\fixtures\UserPasswordFixture',
'humhub\modules\user\tests\codeception\fixtures\UserFollowFixture',
InviteFixture::class,
'humhub\modules\user\tests\codeception\fixtures\GroupFixture'
];

View File

@ -18,7 +18,7 @@
* GNU Affero General Public License for more details.
*/
return [
['id' => '1', 'space_id' => 1, 'name' => 'Administrator', 'description' => 'Administrator Group', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null, 'is_admin_group' => 1],
['id' => '2', 'space_id' => 1, 'name' => 'Users', 'description' => 'Example Group by Installer', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null],
['id' => '3', 'space_id' => 1, 'name' => 'Moderators', 'description' => 'Example Moderator group', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null]
['id' => '1', 'space_id' => 1, 'name' => 'Administrator', 'description' => 'Administrator Group', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null, 'show_at_registration' => 0, 'is_admin_group' => 1],
['id' => '2', 'space_id' => 1, 'name' => 'Users', 'description' => 'Example Group by Installer', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null, 'show_at_registration' => 1],
['id' => '3', 'space_id' => 1, 'name' => 'Moderators', 'description' => 'Example Moderator group', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'ldap_dn' => null, 'show_at_registration' => 0]
];

View File

@ -19,7 +19,7 @@
*/
return [
['id' => 1, 'user_id' => 1, 'group_id' => 1, 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null],
['id' => 2, 'user_id' => 2, 'group_id' => 2, 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null],
['id' => 2, 'user_id' => 2, 'group_id' => 2, 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null, 'is_group_manager' => 1],
['id' => 3, 'user_id' => 3, 'group_id' => 3, 'is_group_manager' => '1', 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null],
['id' => 4, 'user_id' => 6, 'group_id' => 3, 'created_at' => '2014-08-30 14:03:49', 'created_by' => null, 'updated_at' => null, 'updated_by' => null]
];

View File

@ -0,0 +1,34 @@
<?php
namespace tests\codeception\unit;
use tests\codeception\_support\HumHubDbTestCase;
use humhub\modules\user\models\Group;
class GroupTest extends HumHubDbTestCase
{
public function testRegistrationGroups()
{
$groups = Group::getRegistrationGroups();
$this->assertCount(1, $groups);
$this->assertEquals('Users', $groups[0]->name);
$adminGroup = Group::getAdminGroup();
$this->assertEquals(1, $adminGroup->is_admin_group);
$adminGroup->show_at_registration = 1;
$this->assertFalse($adminGroup->save());
// Force save
$adminGroup->save(false);
// Update moderator group
Group::findOne(['id' => 3])->updateAttributes(['show_at_registration' => 1]);
// Make sure the admin group is not contained in registration groups even if show_at_registration is set
$groups = Group::getRegistrationGroups();
$this->assertCount(2, $groups);
$this->assertEquals('Moderators', $groups[0]->name);
$this->assertEquals('Users', $groups[1]->name);
}
}

View File

@ -29,6 +29,7 @@
margin-bottom: 3px;
margin-right: 5px;
max-height: 100%;
max-width:100%;
-webkit-animation-duration: 2s; /* Safari 4.0 - 8.0 */
animation-duration: 2s;
}

View File

@ -11,3 +11,7 @@
text-overflow: ellipsis;
}
}
#user-tags-panel .tags .tag {
max-width:250px;
}