An error occurred while loading the file. Please try again.
-
dockx thibault authored
General lock is bypassed by supann.
Verifieddfbd724c
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003-2010 Cajus Pollmeier
Copyright (C) 2011-2020 FusionDirectory
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Lock
{
public $dn;
public $objectDn;
public $userDn;
public $timestamp;
public function __construct (string $dn, string $objectDn, string $userDn, DateTime $timestamp)
{
$this->dn = $dn;
$this->objectDn = $objectDn;
$this->userDn = $userDn;
$this->timestamp = $timestamp;
}
/*!
* \brief Add a lock for object(s)
*
* Adds a lock by the specified user for one or multiple objects.
* If a lock for that object already exists from another user, an error is triggered.
*
* \param array $object The object or array of objects to lock
*
* \param string $user The user who shall own the lock
*/
public static function add ($object, string $user = NULL)
{
global $config, $ui;
/* Remember which entries were opened as read only, because we
don't need to remove any locks for them later */
if (!session::is_set('LOCK_CACHE')) {
session::set('LOCK_CACHE', ['']);
}
if (is_array($object)) {
foreach ($object as $obj) {
static::add($obj, $user);
}
return;
}
if ($user === NULL) {
$user = $ui->dn;
}
$cache = &session::get_ref('LOCK_CACHE');
if (isset($_POST['open_readonly'])) {
$cache['READ_ONLY'][$object] = TRUE;
return;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
}
if (isset($cache['READ_ONLY'][$object])) {
unset($cache['READ_ONLY'][$object]);
}
/* Just a sanity check... */
if (empty($object) || empty($user)) {
throw new FusionDirectoryError(htmlescape(_('Error while adding a lock. Contact the developers!')));
}
/* Check for existing entries in lock area */
$ldap = $config->get_ldap_link();
$ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
$ldap->search('(&(objectClass=fdLockEntry)(fdUserDn='.ldap_escape_f($user).')(fdObjectDn='.base64_encode($object).'))',
['fdUserDn']);
if ($ldap->get_errno() == 32) {
/* No such object, means the locking branch is missing, create it */
$ldap->cd($config->current['BASE']);
try {
$ldap->create_missing_trees(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
} catch (FusionDirectoryError $error) {
$error->display();
}
$ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
$ldap->search('(&(objectClass=fdLockEntry)(fdUserDn='.ldap_escape_f($user).')(fdObjectDn='.base64_encode($object).'))',
['fdUserDn']);
}
if (!$ldap->success()) {
throw new FusionDirectoryError(
sprintf(
htmlescape(_('Cannot create locking information in LDAP tree. Please contact your administrator!')).
'<br><br>'.htmlescape(_('LDAP server returned: %s')),
'<br><br><i>'.htmlescape($ldap->get_error()).'</i>'
)
);
}
/* Add lock if none present */
if ($ldap->count() == 0) {
$attrs = [];
$name = md5($object);
$dn = 'cn='.$name.','.get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'];
$ldap->cd($dn);
$attrs = [
'objectClass' => 'fdLockEntry',
'cn' => $name,
'fdUserDn' => $user,
'fdObjectDn' => base64_encode($object),
'fdLockTimestamp' => LdapGeneralizedTime::toString(new DateTime('now')),
];
$ldap->add($attrs);
if (!$ldap->success()) {
throw new FusionDirectoryLdapError($dn, LDAP_ADD, $ldap->get_error(), $ldap->get_errno());
}
}
}
/*!
* \brief Remove a lock for object(s)
*
* Remove a lock for object(s)
*
* \param mixed $object object or array of objects for which a lock shall be removed
*/
public static function deleteByObject ($object)
{
global $config;
if (is_array($object)) {
foreach ($object as $obj) {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
static::deleteByObject($obj);
}
return;
}
/* Sanity check */
if ($object == '') {
return;
}
/* If this object was opened in read only mode then
skip removing the lock entry, there wasn't any lock created.
*/
if (session::is_set('LOCK_CACHE')) {
$cache = &session::get_ref('LOCK_CACHE');
if (isset($cache['READ_ONLY'][$object])) {
unset($cache['READ_ONLY'][$object]);
return;
}
}
/* Check for existance and remove the entry */
$ldap = $config->get_ldap_link();
$dn = get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'];
$ldap->cd($dn);
$ldap->search('(&(objectClass=fdLockEntry)(fdObjectDn='.base64_encode($object).'))', ['fdObjectDn']);
if (!$ldap->success()) {
throw new FusionDirectoryLdapError($dn, LDAP_SEARCH, $ldap->get_error(), $ldap->get_errno());
} elseif ($attrs = $ldap->fetch()) {
$ldap->rmdir($attrs['dn']);
if (!$ldap->success()) {
throw new FusionDirectoryLdapError($attrs['dn'], LDAP_DEL, $ldap->get_error(), $ldap->get_errno());
}
}
}
/*!
* \brief Remove all locks owned by a specific userdn
*
* For a given userdn remove all existing locks. This is usually
* called on logout.
*
* \param string $userdn the subject whose locks shall be deleted
*/
public static function deleteByUser (string $userdn)
{
global $config;
/* Get LDAP ressources */
$ldap = $config->get_ldap_link();
$ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
/* Remove all objects of this user, drop errors silently in this case. */
$ldap->search('(&(objectClass=fdLockEntry)(fdUserDn='.ldap_escape_f($userdn).'))', ['fdUserDn']);
while ($attrs = $ldap->fetch()) {
$ldap->rmdir($attrs['dn']);
}
}
/*!
* \brief Get locks for objects
*
* \param mixed $objects Array of dns for which a lock will be searched or dn of a single object
*
* \param boolean $allow_readonly TRUE if readonly access should be permitted,
* FALSE if not (default).
*
* \return A numbered array containing all found locks as an array with key 'object'
* and key 'user', or FALSE if an error occured.
*/
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
public static function get ($objects, bool $allow_readonly = FALSE): array
{
global $config;
if (is_array($objects) && (count($objects) == 1)) {
$objects = reset($objects);
}
if (is_array($objects)) {
if ($allow_readonly) {
throw new FusionDirectoryException('Read only is not possible for several objects');
}
$filter = '(&(objectClass=fdLockEntry)(|';
foreach ($objects as $obj) {
$filter .= '(fdObjectDn='.base64_encode($obj).')';
}
$filter .= '))';
} else {
if ($allow_readonly && isset($_POST['open_readonly'])) {
/* If readonly is allowed and asked and there is only one object, bypass lock detection */
return [];
}
$filter = '(&(objectClass=fdLockEntry)(fdObjectDn='.base64_encode($objects).'))';
}
/* Get LDAP link, check for presence of the lock entry */
$ldap = $config->get_ldap_link();
$dn = get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'];
$ldap->cd($dn);
$ldap->search($filter, ['fdUserDn','fdObjectDn', 'fdLockTimestamp']);
if (!$ldap->success()) {
throw new FusionDirectoryLdapError($dn, LDAP_SEARCH, $ldap->get_error(), $ldap->get_errno());
}
$locks = [];
$sessionLifetime = $config->get_cfg_value('sessionLifetime', 1800);
if ($sessionLifetime > 0) {
$expirationDate = (new DateTime())->sub(new DateInterval('PT'.$sessionLifetime.'S'));
}
while ($attrs = $ldap->fetch()) {
$date = LdapGeneralizedTime::fromString($attrs['fdLockTimestamp'][0]);
if (isset($expirationDate) && ($date < $expirationDate)) {
/* Delete expired locks */
$ldap->rmdir($attrs['dn']);
} else {
$locks[] = new Lock(
$attrs['dn'],
base64_decode($attrs['fdObjectDn'][0]),
$attrs['fdUserDn'][0],
$date
);
}
}
if (!is_array($objects) && (count($locks) > 1)) {
/* Hmm. We're removing broken LDAP information here and issue a warning. */
$warning = new FusionDirectoryWarning(htmlescape(_('Found multiple locks for object to be locked. This should not happen - cleaning up multiple references.')));
$warning->display();
/* Clean up these references now... */
foreach ($locks as $lock) {
$ldap->rmdir($lock->dn);
}
return [];
}
return $locks;
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
/*!
* \brief Add a lock for object(s) or fail
*
* Adds a lock by the specified user for one ore multiple objects.
* If the lock for that object already exists, waits a bit and retry.
* If a lock cannot be set, throws.
*
* \param array|string $object The object or array of objects to lock
*
* \param string $user The user who shall own the lock
*
* \param int $retries how many times we can retry (waiting a second each time)
*/
public static function addOrFail ($object, string $user = NULL, int $retries = 10)
{
$wait = $retries;
while (!empty($locks = Lock::get($object))) {
sleep(1);
/* Oups - timed out */
if ($wait-- == 0) {
throw new FusionDirectoryException(_('Timeout while waiting for lock!'));
}
}
Lock::add($object, $user);
}
/*!
* \brief Generate a lock message
*
* This message shows a warning to the user, that a certain object is locked
* and presents some choices how the user can proceed. By default this
* is 'Cancel' or 'Edit anyway', but depending on the function call
* its possible to allow readonly access, too.
*
* Example usage:
* \code
* if ($locks = Lock::get($this->dn)) {
* return Lock::genLockedMessage($locks, TRUE);
* }
* \endcode
*
* \param string $locks the locks as returned by Lock::get
*
* \param boolean $allowReadonly TRUE if readonly access should be permitted,
* FALSE if not (default).
*
* \param string $action Label of the action button, "Edit anyway" by default. Will be escaped.
*
*/
public static function genLockedMessage (array $locks, bool $allowReadonly = FALSE, string $action = NULL): string
{
/* Save variables from LOCK_VARS_TO_USE in session - for further editing */
if (session::is_set('LOCK_VARS_TO_USE') && count(session::get('LOCK_VARS_TO_USE'))) {
$LOCK_VARS_USED_GET = [];
$LOCK_VARS_USED_POST = [];
$LOCK_VARS_USED_REQUEST = [];
$LOCK_VARS_TO_USE = session::get('LOCK_VARS_TO_USE');
foreach ($LOCK_VARS_TO_USE as $name) {
if (empty($name)) {
continue;
}
foreach ($_POST as $Pname => $Pvalue) {
if (preg_match($name, $Pname)) {
$LOCK_VARS_USED_POST[$Pname] = $_POST[$Pname];
}
}
351352353354355356357358359360361362363364365366367368369370371372373374375376377378
foreach ($_GET as $Pname => $Pvalue) {
if (preg_match($name, $Pname)) {
$LOCK_VARS_USED_GET[$Pname] = $_GET[$Pname];
}
}
foreach ($_REQUEST as $Pname => $Pvalue) {
if (preg_match($name, $Pname)) {
$LOCK_VARS_USED_REQUEST[$Pname] = $_REQUEST[$Pname];
}
}
}
session::set('LOCK_VARS_TO_USE', []);
session::set('LOCK_VARS_USED_GET', $LOCK_VARS_USED_GET);
session::set('LOCK_VARS_USED_POST', $LOCK_VARS_USED_POST);
session::set('LOCK_VARS_USED_REQUEST', $LOCK_VARS_USED_REQUEST);
}
/* Prepare and show template */
$smarty = get_smarty();
$smarty->assign('allow_readonly', $allowReadonly);
$smarty->assign('action', ($action ?? _('Edit anyway')));
$smarty->assign('locks', $locks);
return $smarty->fetch(get_template_path('islocked.tpl'));
}
}