Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ LB_PRIVACY_VIEW_SCHEDULES=true
# When false (default), public views show only that a slot is occupied with no content. (true/false)
LB_PRIVACY_VIEW_RESERVATIONS=false

# When true, public (unauthenticated) users can open reservation details from public views.
# Requires privacy.view.reservations. (true/false)
LB_PRIVACY_VIEW_PUBLIC_RESERVATION_DETAILS=false

# When true, non-owner/non-admin users see "Private" instead of the organizer name and email (true/false)
LB_PRIVACY_HIDE_USER_DETAILS=false

Expand Down
13 changes: 11 additions & 2 deletions Pages/Ajax/ReservationPopupPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ public function PageLoad()
$user = $this->_userRepository->LoadById(ServiceLocator::GetServer()->GetUserSession()->UserId);
$owner = $this->_userRepository->LoadById($reservation->OwnerId);

$this->UserResourcePermissions(ServiceLocator::GetServer()->GetUserSession()->UserId);
$this->UserResourcePermissions(ServiceLocator::GetServer()->GetUserSession()->UserId, $reservation);
$this->_page->SetCurrentUserParticipating($this->IsCurrentUserParticipating(ServiceLocator::GetServer()->GetUserSession()->UserId));
$this->_page->SetCurrentUserInvited($this->IsCurrentUserInvited(ServiceLocator::GetServer()->GetUserSession()->UserId));

Expand All @@ -426,9 +426,18 @@ public function PageLoad()
/**
* Gets the resources the user has permissions (full access and view only permissions)
* This is used to block a user from seeing reservation details if he has no permissions to it's resources
* Public (not logged-in) users have no permission rows, so when public details are enabled
* they may see the resources of the reservation they opened.
*/
private function UserResourcePermissions($userId)
private function UserResourcePermissions($userId, $reservation)
{
if (ReservationDetailsFilter::CanPublicViewDetails()) {
$this->_page->BindViewableResourceReservations(
array_map(fn ($resource) => $resource->GetResourceId(), $reservation->Resources)
);
return;
}

$resourceRepo = new ResourceRepository();
$resourceIds = [];

Expand Down
4 changes: 4 additions & 0 deletions config/config.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,10 @@
# When false (default), public views show only that a slot is occupied with no content. (true/false)
'view.reservations' => false,

# When true, public (unauthenticated) users can open reservation details from public views.
# Requires privacy.view.reservations. (true/false)
'view.public.reservation.details' => false,

# When true, non-owner/non-admin users see "Private" instead of the organizer name and email (true/false)
'hide.user.details' => false,

Expand Down
4 changes: 4 additions & 0 deletions docs/source/ADMINISTRATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ read-only schedule. By default, only available and unavailable times are
shown. Setting this to ``true`` will show full reservation details to
unauthenticated users.

``view.public.reservation.details`` additionally lets unauthenticated users
open the reservation popup from the read-only schedule. It has no effect
unless ``view.reservations`` is ``true``.

Resources
---------

Expand Down
5 changes: 5 additions & 0 deletions docs/source/BASIC-CONFIGURATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ Basic Privacy Settings
'privacy' => [
'view.schedules' => true,
'view.reservations' => false,
'view.public.reservation.details' => false,
'allow.guest.reservations' => false,
],

Expand All @@ -351,6 +352,10 @@ Basic Privacy Settings
**privacy.view.reservations**
Allow users to view reservations.

**privacy.view.public.reservation.details**
Allow public (not logged-in) users to open reservation details from the
public schedule and calendar. Requires ``privacy.view.reservations``.

**privacy.allow.guest.reservations**
Allow guests to make reservations.

Expand Down
8 changes: 8 additions & 0 deletions lib/Application/Reservation/ReservationComponentBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,17 @@ private function SetAutoReleaseMinutes()
/**
* Gets the resources the user has permissions (full access and view only permissions)
* This is used to block a user from seeing reservation details if he has no permissions to it's resources
* Public users may see the resources of the reservation when public details are enabled.
*/
private function UserResourcePermissions($userId)
{
if (ReservationDetailsFilter::CanPublicViewDetails()) {
$this->page->BindViewableResourceReservations(
array_map(fn ($resource) => $resource->GetResourceId(), $this->reservationView->Resources)
);
return;
}

$resourceRepo = new ResourceRepository();
$resourceIds = [];

Expand Down
15 changes: 15 additions & 0 deletions lib/Application/Reservation/ReservationDetailsFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,19 @@ public static function HideReservationDetails($reservationStart = null, $reserva
$converter = new BooleanConverter();
return $converter->Convert($hideReservationDetails);
}

/**
* Public (not logged-in) users have no resource permission rows, so
* they may open reservation details only when both settings allow it.
* @return bool
*/
public static function CanPublicViewDetails()
{
$converter = new BooleanConverter();
$config = Configuration::Instance();

return !ServiceLocator::GetServer()->GetUserSession()->IsLoggedIn()
&& $config->GetKey(ConfigKeys::PRIVACY_VIEW_RESERVATIONS, $converter)
&& $config->GetKey(ConfigKeys::PRIVACY_VIEW_PUBLIC_RESERVATION_DETAILS, $converter);
}
}
9 changes: 9 additions & 0 deletions lib/Config/ConfigKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,15 @@ class ConfigKeys extends AbstractConfigKeys
'config_file_comment' => "When true, public (unauthenticated) views show reservation details.\nWhen false (default), public views show only that a slot is occupied with no content.",
'section' => 'privacy'
];
public const PRIVACY_VIEW_PUBLIC_RESERVATION_DETAILS = [
'key' => 'privacy.view.public.reservation.details',
'type' => 'boolean',
'default' => false,
'label' => 'View Public Reservation Details',
'description' => 'When true, public (not logged-in) users can open the reservation popup from the public schedule and calendar to see reservation details. Requires privacy.view.reservations to be true.',
'config_file_comment' => "When true, public (unauthenticated) users can open reservation details from public views.\nRequires privacy.view.reservations.",
'section' => 'privacy'
];
public const PRIVACY_HIDE_USER_DETAILS = [
'key' => 'privacy.hide.user.details',
'type' => 'boolean',
Expand Down
47 changes: 47 additions & 0 deletions tests/Application/Reservation/ReservationDetailsFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

class ReservationDetailsFilterTest extends TestBase
{
public function setUp(): void
{
parent::setup();
$this->fakeServer->SetUserSession(new NullUserSession());
}

public function testPublicUserCanViewDetailsWhenBothSettingsAreEnabled()
{
$this->SetSettings(true, true);

$this->assertTrue(ReservationDetailsFilter::CanPublicViewDetails());
}

public function testPublicUserCannotViewDetailsWithoutViewReservations()
{
$this->SetSettings(false, true);

$this->assertFalse(ReservationDetailsFilter::CanPublicViewDetails());
}

public function testPublicUserCannotViewDetailsWithoutPublicDetailsSetting()
{
$this->SetSettings(true, false);

$this->assertFalse(ReservationDetailsFilter::CanPublicViewDetails());
}

public function testLoggedInUserIsNotTreatedAsPublic()
{
$this->SetSettings(true, true);
$this->fakeServer->SetUserSession(new FakeUserSession());

$this->assertFalse(ReservationDetailsFilter::CanPublicViewDetails());
}

private function SetSettings(bool $viewReservations, bool $publicDetails)
{
$this->fakeConfig->SetKey(ConfigKeys::PRIVACY_VIEW_RESERVATIONS, $viewReservations);
$this->fakeConfig->SetKey(ConfigKeys::PRIVACY_VIEW_PUBLIC_RESERVATION_DETAILS, $publicDetails);
}
}
53 changes: 53 additions & 0 deletions tests/Pages/Ajax/ReservationPopupPresenterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

require_once(ROOT_DIR . 'Pages/Ajax/ReservationPopupPage.php');

class ReservationPopupPresenterTest extends TestBase
{
private $page;
private $reservationRepository;
private $presenter;

public function setUp(): void
{
parent::setup();

$this->page = $this->createMock(IReservationPopupPage::class);
$this->page->method('GetReservationId')->willReturn(1);

$view = new ReservationView();
$view->StartDate = Date::Now();
$view->EndDate = Date::Now()->AddDays(1);
$view->Resources = [
new ReservationResourceView(10, 'r1', null, 1, null, false, null),
new ReservationResourceView(11, 'r2', null, 1, null, false, null),
];

$this->reservationRepository = $this->createMock(IReservationViewRepository::class);
$this->reservationRepository->method('GetReservationForEditing')->willReturn($view);

$userRepository = new FakeUserRepository();
$userRepository->_User = new FakeUser(0);

$this->presenter = new ReservationPopupPresenter(
$this->page,
$this->reservationRepository,
$this->createMock(IReservationAuthorization::class),
new FakeAttributeService(),
$userRepository
);
}

public function testPublicUserSeesReservationResourcesWhenPublicDetailsEnabled()
{
$this->fakeServer->SetUserSession(new NullUserSession());
$this->fakeConfig->SetKey(ConfigKeys::PRIVACY_VIEW_RESERVATIONS, true);
$this->fakeConfig->SetKey(ConfigKeys::PRIVACY_VIEW_PUBLIC_RESERVATION_DETAILS, true);

$this->page->expects($this->once())->method('BindViewableResourceReservations')->with([10, 11]);

$this->presenter->PageLoad();
}
}
Loading