• Côme Chilliet's avatar
    :sparkles: feat(core) Big refactor of dialog system · 94eaa6ba
    Côme Chilliet authored
    This replaces save_object and execute methods by 3 methods:
    readPost - Reads POST data
    update - Update object state
    render - Render HTML UI
    
    The point is to avoid reading POST and rendering HTML when this is not
     needed (when doing stuff through the webservice for instance).
    
    It’s also more consisent across FD with all classes handling some kind
     of dialog implementing the new interface FusionDirectoryDialog which
     makes sure these 3 methods are implemented.
    
    issue #6072
    Unverified
    94eaa6ba
class_Lock.inc 12.43 KiB
<?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 ore 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')); } }