Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #546: all profiles publicly viewable to anyone by default #547

Merged
merged 9 commits into from
Sep 18, 2024
Merged
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' => '[email protected]',
'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' => '[email protected]',
'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');

Loading