Skip to content

Commit

Permalink
Merge pull request #547 from TonisOrmisson/fix-profile-open
Browse files Browse the repository at this point in the history
Fixes #546: all profiles publicly viewable to anyone by default
  • Loading branch information
maxxer authored Sep 18, 2024
2 parents b70f389 + 771b946 commit bddb47d
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Enh: Changed exception thrown in PasswordRecoveryService from `RuntimeException` to `NotFoundException`. (eseperio)
- New #553: created Da\User\AuthClient\Microsoft365 auth client (edegaudenzi)
- Ehh: Added SecurityHelper to the Bootstrap classMap
- Fix #546: The profile/show page must not be visible by default, implement configurable policy (TonisOrmisson)
- Fix #397: No more fatal Exceptions when connecting to already taken Social Network (edegaudenzi)
- Ehh: Added option to pre-fill recovery email via url parameter (TonisOrmisson)

Expand Down
14 changes: 9 additions & 5 deletions docs/install/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,15 @@ simple backends with static administrators that won't change throughout time.

Configures the permission name for `administrators`. See [AuthHelper](../../src/User/Helper/AuthHelper.php).

#### profileVisibility (type: `integer`, default:`0` (ProfileController::PROFILE_VISIBILITY_OWNER))

Configures to whom users 'profile/show' (public profile) page is shown. Constant values are defined in
[ProfileController](../../src/User/Controller/ProfileController.php) as constants. The visibility levels are:
- `0` (ProfileController::PROFILE_VISIBILITY_OWNER): The users profile page is shown ONLY to user itself, the owner of the profile.
- `1` (ProfileController::PROFILE_VISIBILITY_ADMIN): The users profile is shown ONLY to user itself (owner) AND users defined by module as admins.
- `2` (ProfileController::PROFILE_VISIBILITY_USERS): Any users profile page is shown to any other non-guest user.
- `3` (ProfileController::PROFILE_VISIBILITY_PUBLIC): Any user profile views are globally public and visible to anyone (including guests).

#### prefix (type: `string`, default: `user`)

Configures the URL prefix for the module.
Expand Down Expand Up @@ -313,11 +322,6 @@ Set to `true` to restrict user assignments to roles only.

If `true` registration and last login IPs are not logged into users table, instead a dummy 127.0.0.1 is used


#### disableProfileViewsForRegularUsers (type: `boolean`, default: `false`)

If `true` only admin users have access to view any other user's profile. By default any user can see any other users public profile page.

#### minPasswordRequirements (type: `array`, default: `['lower' => 1, 'digit' => 1, 'upper' => 1]`)

Minimum requirements when a new password is automatically generated.
Expand Down
37 changes: 34 additions & 3 deletions src/User/Controller/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ class ProfileController extends Controller
{
use ModuleAwareTrait;

/** @var int will allow only profile owner */
const PROFILE_VISIBILITY_OWNER = 0;
/** @var int will allow profile owner and admin users */
const PROFILE_VISIBILITY_ADMIN = 1;
/** @var int will allow any logged-in users */
const PROFILE_VISIBILITY_USERS = 2;
/** @var int will allow anyone, including guests */
public const PROFILE_VISIBILITY_PUBLIC = 3;

protected $profileQuery;

/**
Expand Down Expand Up @@ -73,10 +82,32 @@ public function actionIndex()
public function actionShow($id)
{
$user = Yii::$app->user;
/** @var User $identity */
$id = (int) $id;

/** @var ?User $identity */
$identity = $user->getIdentity();
if($user->getId() != $id && $this->module->disableProfileViewsForRegularUsers && !$identity->getIsAdmin()) {
throw new ForbiddenHttpException();

switch($this->module->profileVisibility) {
case static::PROFILE_VISIBILITY_OWNER:
if($identity === null || $id !== $user->getId()) {
throw new ForbiddenHttpException();
}
break;
case static::PROFILE_VISIBILITY_ADMIN:
if($id === $user->getId() || ($identity !== null && $identity->getIsAdmin())) {
break;
}
throw new ForbiddenHttpException();
case static::PROFILE_VISIBILITY_USERS:
if((!$user->getIsGuest())) {
break;
}
throw new ForbiddenHttpException();
case static::PROFILE_VISIBILITY_PUBLIC:
break;
default:
throw new ForbiddenHttpException();

}

$profile = $this->profileQuery->whereUserId($id)->one();
Expand Down
11 changes: 7 additions & 4 deletions src/User/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Da\User;

use Da\User\Contracts\MailChangeStrategyInterface;
use Da\User\Controller\ProfileController;
use Da\User\Filter\AccessRuleFilter;
use Yii;
use yii\base\Module as BaseModule;
Expand Down Expand Up @@ -181,6 +182,12 @@ class Module extends BaseModule
* @var string the administrator permission name
*/
public $administratorPermissionName;
/**
* @var int $profileVisibility Defines the level of user's profile page visibility.
* Defaults to ProfileController::PROFILE_VISIBILITY_OWNER meaning no-one except the user itself can view
* the profile. @see ProfileController constants for possible options
*/
public $profileVisibility = ProfileController::PROFILE_VISIBILITY_OWNER;
/**
* @var string the route prefix
*/
Expand Down Expand Up @@ -242,10 +249,6 @@ class Module extends BaseModule
* @var boolean whether to disable IP logging into user table
*/
public $disableIpLogging = false;
/**
* @var boolean whether to disable viewing any user's profile for non-admin users
*/
public $disableProfileViewsForRegularUsers = false;
/**
* @var array Minimum requirements when a new password is automatically generated.
* Array structure: `requirement => minimum number characters`.
Expand Down
4 changes: 4 additions & 0 deletions tests/_fixtures/data/profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
'user_id' => 1,
'name' => 'John Doe',
],
'seconduser' => [
'user_id' => 9,
'name' => 'John Doe 2',
],
];
26 changes: 26 additions & 0 deletions tests/_fixtures/data/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,30 @@
'confirmed_at' => $time,
'gdpr_consent' => false,
],
'admin' => [
'id' => 8,
'username' => 'admin',
'email' => 'admin@example.com',
'password_hash' => '$2y$13$qY.ImaYBppt66qez6B31QO92jc5DYVRzo5NxM1ivItkW74WsSG6Ui',
'auth_key' => '39HU0m5lpjWtqstFVGFjj6lFb7UZDeRq',
'auth_tf_key' => '',
'auth_tf_enabled' => false,
'created_at' => $time,
'updated_at' => $time,
'confirmed_at' => $time,
'gdpr_consent' => false,
],
'seconduser' => [
'id' => 9,
'username' => 'seconduser',
'email' => 'seconduser@example.com',
'password_hash' => '$2y$13$qY.ImaYBppt66qez6B31QO92jc5DYVRzo5NxM1ivItkW74WsSG6Ui',
'auth_key' => '776960890cec5ac53525f0e910716f5a',
'auth_tf_key' => '',
'auth_tf_enabled' => false,
'created_at' => $time,
'updated_at' => $time,
'confirmed_at' => $time,
'gdpr_consent' => false,
],
];
110 changes: 110 additions & 0 deletions tests/functional/ProfileCept.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

/**
* @var Codeception\Scenario
*/

use tests\_fixtures\ProfileFixture;
use tests\_fixtures\UserFixture;


$I = new FunctionalTester($scenario);
$I->haveFixtures([
'user' => UserFixture::class,
'profile' => ProfileFixture::class
]);
$user = $I->grabFixture('user', 'user');
$secondUser = $I->grabFixture('user', 'seconduser');
$adminUser = $I->grabFixture('user', 'admin');
$I->wantTo('Ensure that profile profile pages are shown only to when user has correct permissions and else forbidden');

Yii::$app->getModule('user')->profileVisibility = \Da\User\Controller\ProfileController::PROFILE_VISIBILITY_OWNER;
Yii::$app->getModule('user')->administrators = ['admin'];

$I->amLoggedInAs($user);
$I->amGoingTo('try to open users own profile page');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

$I->amGoingTo('Profile visibility::OWNER: try to open another users profile page');
$I->amOnRoute('/user/profile/show', ['id' => $secondUser->id]);
$I->expectTo('See the profile page');
$I->see('Forbidden');
$I->dontSee('Joined on');

Yii::$app->user->logout();
$I->amGoingTo('Profile visibility::OWNER: try to open users profile page as guest');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->see('Forbidden');
$I->dontSee('Joined on');


Yii::$app->getModule('user')->profileVisibility = \Da\User\Controller\ProfileController::PROFILE_VISIBILITY_ADMIN;
$I->amLoggedInAs($user);
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_ADMIN: try to open users own profile page');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_ADMIN: try to open another users profile page as regular user');
$I->amOnRoute('/user/profile/show', ['id' => $secondUser->id]);
$I->expectTo('See the profile page');
$I->see('Forbidden');
$I->dontSee('Joined on');

$I->amLoggedInAs($adminUser);
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_ADMIN: try to open another users profile page as admin');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

Yii::$app->user->logout();
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_ADMIN: try to open users profile page as guest');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->see('Forbidden');
$I->dontSee('Joined on');


Yii::$app->getModule('user')->profileVisibility = \Da\User\Controller\ProfileController::PROFILE_VISIBILITY_USERS;
$I->amLoggedInAs($user);
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_USERS: try to open users own profile page');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_USERS: try to open another users profile page as regular user');
$I->amOnRoute('/user/profile/show', ['id' => $secondUser->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

$I->amLoggedInAs($adminUser);
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_USERS: try to open another users profile page as admin');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

Yii::$app->user->logout();
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_USERS: try to open users profile page as guest');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->see('Forbidden');
$I->dontSee('Joined on');

Yii::$app->getModule('user')->profileVisibility = \Da\User\Controller\ProfileController::PROFILE_VISIBILITY_PUBLIC;

Yii::$app->user->logout();
$I->amGoingTo('Profile visibility::PROFILE_VISIBILITY_PUBLIC: try to open users profile page as guest');
$I->amOnRoute('/user/profile/show', ['id' => $user->id]);
$I->expectTo('See the profile page');
$I->dontSee('Forbidden');
$I->see('Joined on');

0 comments on commit bddb47d

Please sign in to comment.