Skip to content

Commit

Permalink
Merge pull request from GHSA-qmw3-87hr-5wgx
Browse files Browse the repository at this point in the history
  • Loading branch information
cedric-anne committed Nov 25, 2020
1 parent e0d6a24 commit 5272803
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 48 deletions.
45 changes: 4 additions & 41 deletions inc/caldav/backend/principal.class.php
Expand Up @@ -37,6 +37,7 @@
}

use Glpi\CalDAV\Node\Property;
use Glpi\CalDAV\Traits\CalDAVPrincipalsTrait;
use Glpi\CalDAV\Traits\CalDAVUriUtilTrait;
use Sabre\DAV\PropPatch;
use Sabre\DAVACL\PrincipalBackend\AbstractBackend;
Expand All @@ -50,6 +51,7 @@
*/
class Principal extends AbstractBackend {

use CalDAVPrincipalsTrait;
use CalDAVUriUtilTrait;

const PRINCIPALS_ROOT = 'principals';
Expand All @@ -58,56 +60,17 @@ class Principal extends AbstractBackend {

public function getPrincipalsByPrefix($prefixPath) {

global $DB;

$principals = [];

switch ($prefixPath) {
case self::PREFIX_GROUPS:
if (!\Session::haveRight(\Planning::$rightname, \Planning::READALL)
&& empty($_SESSION['glpigroups'])) {
// User cannot read planning of everyone and has no groups.
break;
}

$groups_criteria = getEntitiesRestrictCriteria(
\Group::getTable(),
'entities_id',
$_SESSION['glpiactiveentities'],
true
);

// Limit to groups visible in planning (see Planning::showAddGroupForm())
$groups_criteria['is_task'] = 1;

// Limit to users groups if user cannot read planning of everyone
if (!\Session::haveRight(\Planning::$rightname, \Planning::READALL)) {
$groups_criteria['id'] = $_SESSION['glpigroups'];
}

$groups_iterator = $DB->request(
[
'FROM' => \Group::getTable(),
'WHERE' => $groups_criteria,
]
);
$groups_iterator = $this->getVisibleGroupsIterator();
foreach ($groups_iterator as $group_fields) {
$principals[] = $this->getPrincipalFromGroupFields($group_fields);
}
break;
case self::PREFIX_USERS:
if (!\Session::haveRightsOr(\Planning::$rightname, [\Planning::READALL, \Planning::READGROUP])) {
// Can see only personnal planning
$rights = 'id';
} else if (\Session::haveRight(\Planning::$rightname, \Planning::READGROUP)
&& !\Session::haveRight(\Planning::$rightname, \Planning::READALL)) {
// Can see only planning from users sharing same groups
$rights = 'groups';
} else {
// Can see planning from users having rights on planning elements
$rights = ['change', 'problem', 'reminder', 'task', 'projecttask'];
}
$users_iterator = \User::getSqlSearchResult(false, $rights);
$users_iterator = $this->getVisibleUsersIterator();
foreach ($users_iterator as $user_fields) {
$principals[] = $this->getPrincipalFromUserFields($user_fields);
}
Expand Down
11 changes: 9 additions & 2 deletions inc/caldav/plugin/acl.class.php
Expand Up @@ -37,10 +37,13 @@
}

use Glpi\CalDAV\Backend\Principal;
use Glpi\CalDAV\Traits\CalDAVPrincipalsTrait;
use Glpi\CalDAV\Traits\CalDAVUriUtilTrait;
use Sabre\CalDAV\Calendar;
use Sabre\CalDAV\CalendarObject;
use Sabre\DAVACL\IACL;
use Sabre\DAVACL\Plugin;
use Session;

/**
* ACL plugin for CalDAV server.
Expand All @@ -49,6 +52,7 @@
*/
class Acl extends Plugin {

use CalDAVPrincipalsTrait;
use CalDAVUriUtilTrait;

public $principalCollectionSet = [
Expand All @@ -65,8 +69,11 @@ public function getAcl($node) {

$acl = parent::getAcl($node);

// Authenticated user have read access to all nodes, as node list only contains elements
// that user can read.
if (!($node instanceof IACL) || ($owner_path = $node->getOwner()) === null
|| !$this->canViewPrincipalObjects($owner_path)) {
return $acl;
}

$acl[] = [
'principal' => '{DAV:}authenticated',
'privilege' => '{DAV:}read',
Expand Down
173 changes: 173 additions & 0 deletions inc/caldav/traits/caldavprincipalstrait.class.php
@@ -0,0 +1,173 @@
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/

namespace Glpi\CalDAV\Traits;

use EmptyIterator;
use Group;
use Iterator;
use Planning;
use Session;
use User;

if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}

/**
* Trait used for CalDAV Principals listing and visibility management.
*
* @since 9.5.3
*/
trait CalDAVPrincipalsTrait {

/**
* Check if principal objects are visible for current session.
*
* @param string $path
*
* @return bool
*/
protected function canViewPrincipalObjects(string $path): bool {
$principal_type = $this->getPrincipalItemtypeFromUri($path);
switch ($principal_type) {
case Group::class:
$can_view = $this->canViewGroupObjects($this->getGroupIdFromPrincipalUri($path));
break;
case User::class:
$can_view = $this->canViewUserObjects($this->getUsernameFromPrincipalUri($path));
break;
default:
$can_view = false;
break;
}

return $can_view;
}

/**
* Check if group objects are visible for current session.
*
* @param int $group_id
*
* @return bool
*/
protected function canViewGroupObjects(int $group_id): bool {
$groups_iterator = $this->getVisibleGroupsIterator();
foreach ($groups_iterator as $group) {
if ($group['id'] === $group_id) {
return true;
}
}

return false;
}

/**
* Check if user objects are visible for current session.
*
* @param string $username
*
* @return bool
*/
protected function canViewUserObjects(string $username): bool {
$users_iterator = $this->getVisibleUsersIterator();
foreach ($users_iterator as $user) {
if ($user['name'] === $username) {
return true;
}
}

return false;
}

/**
* Get visible groups for current session.
*
* @return array
*/
protected function getVisibleGroupsIterator(): Iterator {

global $DB;

if (!Session::haveRight(Planning::$rightname, Planning::READALL)
&& empty($_SESSION['glpigroups'])) {
// User cannot read planning of everyone and has no groups.
return new EmptyIterator();
}

$groups_criteria = getEntitiesRestrictCriteria(
Group::getTable(),
'entities_id',
$_SESSION['glpiactiveentities'],
true
);

// Limit to groups visible in planning (see Planning::showAddGroupForm())
$groups_criteria['is_task'] = 1;

// Limit to users groups if user cannot read planning of everyone
if (!Session::haveRight(Planning::$rightname, Planning::READALL)) {
$groups_criteria['id'] = $_SESSION['glpigroups'];
}

$groups_iterator = $DB->request(
[
'FROM' => Group::getTable(),
'WHERE' => $groups_criteria,
]
);

return $groups_iterator;
}

/**
* Get visible users for current session.
*
* @return array
*/
protected function getVisibleUsersIterator(): Iterator {

if (!Session::haveRightsOr(Planning::$rightname, [Planning::READALL, Planning::READGROUP])) {
// Can see only personnal planning
$rights = 'id';
} else if (Session::haveRight(Planning::$rightname, Planning::READGROUP)
&& !Session::haveRight(Planning::$rightname, Planning::READALL)) {
// Can see only planning from users sharing same groups
$rights = 'groups';
} else {
// Can see planning from users having rights on planning elements
$rights = ['change', 'problem', 'reminder', 'task', 'projecttask'];
}
return User::getSqlSearchResult(false, $rights);
}
}
68 changes: 63 additions & 5 deletions tests/functionnal/Glpi/CalDAV/Server.php
Expand Up @@ -407,15 +407,14 @@ public function testPropfindOnMainEndpoints(string $path, array $expected_result
*/
public function testPropfindOnPrincipalCalendar() {

$login = TU_USER;
$pass = TU_PASS;
$login = 'tech';
$pass = 'tech';
$user = getItemByTypeName('User', $login);

$this->login($login, $pass);

$group = new \Group();
$group_id = (int)$group->add([
'name' => 'Test group'
'name' => 'Test group',
'is_task' => 1,
]);
$this->integer($group_id)->isGreaterThan(0);
$group->getFromDB($group_id);
Expand All @@ -428,6 +427,8 @@ public function testPropfindOnPrincipalCalendar() {
])
)->isGreaterThan(0);

$this->login($login, $pass);

$calendars = [
[
'path' => 'calendars/users/' . $user->fields['name'] . '/calendar/',
Expand Down Expand Up @@ -488,6 +489,62 @@ public function testPropfindOnPrincipalCalendar() {
}
}

/**
* Test ACL on main objects.
*/
public function testAcl() {

$user = getItemByTypeName('User', 'tech');

$group = new \Group();
$group_id = (int)$group->add([
'name' => 'Test group',
'is_task' => 1,
]);
$this->integer($group_id)->isGreaterThan(0);
$group->getFromDB($group_id);

$group_user = new \Group_User();
$this->integer(
(int)$group_user->add([
'groups_id' => $group_id,
'users_id' => $user->fields['id'],
])
)->isGreaterThan(0);

$objects = [
'principals/users/' . $user->fields['name'],
'calendars/users/' . $user->fields['name'] . '/calendar/',
'principals/groups/' . $group_id,
'calendars/groups/' . $group_id . '/calendar/',
];

$users_access = [
'normal' => 'HTTP/1.1 403 Forbidden',
'tech' => 'HTTP/1.1 200 OK',
];

foreach ($users_access as $username => $expected_status) {
$this->login($username, $username);

foreach ($objects as $path) {
$server = $this->getServerInstance('PROPFIND', $path);

$this->validateThatAuthenticationIsRequired($server);

$server->httpRequest->addHeader('Authorization', 'Basic ' . base64_encode($username . ':' . $username));

$response = new \Sabre\HTTP\Response();
$server->invokeMethod($server->httpRequest, $response, false);
$this->validateResponseIsOk($response, 207, 'application/xml'); // 207 'Multi-Status'

$xpath = $this->getXpathFromResponse($response);
$result_path = '/d:multistatus/d:response[1]';
$this->string($xpath->evaluate('string(' . $result_path . '/d:propstat/d:status)'))->isEqualTo($expected_status);
}
}
}

/**
* Test PROPFIND, GET, DELETE, PUT methods on calendar events.
* Tests validates that mandatory properties are correctly set.
Expand Down Expand Up @@ -1529,6 +1586,7 @@ function() use($server) {
*
* @param \Sabre\HTTP\Response $response
* @param integer $status
* @param string|null $content_type
*/
private function validateResponseIsOk(\Sabre\HTTP\Response $response, int $status, string $content_type) {
$this->integer($response->getStatus())->isEqualTo($status);
Expand Down

0 comments on commit 5272803

Please sign in to comment.