Verified Commit cfb3bebd authored by Côme Chilliet's avatar Côme Chilliet
Browse files

:ambulance: fix(webservice) Make webservice lock aware

issue #6082
Showing with 267 additions and 196 deletions
+267 -196
......@@ -371,44 +371,49 @@ class fdRestService extends fdRPCService
$this->checkAccess($type, $tab, $dn);
$tabobject = objects::open($dn, $type);
if ($tab === NULL) {
$object = $tabobject->getBaseObject();
} elseif (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
} else {
$object = $tabobject->by_object[$tab];
}
if (!is_subclass_of($object, 'simplePlugin')) {
throw new WebServiceError('Invalid tab', 501);
}
if ($tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
}
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
static::addLockOrThrow($dn);
try {
$tabobject = objects::open($dn, $type);
if ($tab === NULL) {
$object = $tabobject->getBaseObject();
} elseif (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
$object = $tabobject->by_object[$tab];
}
if (!is_subclass_of($object, 'simplePlugin')) {
throw new WebServiceError('Invalid tab', 501);
}
if ($tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
}
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
}
}
$error = $tabobject->by_object[$tab]->deserializeValues([$attribute => $input]);
if ($error !== TRUE) {
throw new WebServiceError($error);
}
}
$error = $tabobject->by_object[$tab]->deserializeValues([$attribute => $input]);
if ($error !== TRUE) {
throw new WebServiceError($error);
}
$tabobject->current = $tab;
$tabobject->update();
$tabobject->current = $tab;
$tabobject->update();
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
return $tabobject->dn;
} finally {
Lock::deleteByObject($dn);
}
}
protected function endpoint_objects_PATCH_5 (int &$responseCode, $input, string $type, string $dn, string $tab, string $attribute, string $values): string
......@@ -448,38 +453,43 @@ class fdRestService extends fdRPCService
$this->checkAccess($type, $tab, $dn);
$tabobject = objects::open($dn, $type);
static::addLockOrThrow($dn);
try {
$tabobject = objects::open($dn, $type);
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
}
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
}
$object = $tabobject->by_object[$tab];
$object = $tabobject->by_object[$tab];
if (!is_subclass_of($object, 'simplePlugin')) {
throw new WebServiceError('Invalid tab', 501);
}
if (!is_subclass_of($object, 'simplePlugin')) {
throw new WebServiceError('Invalid tab', 501);
}
if (!isset($object->attributesAccess[$attribute])) {
throw new WebServiceError('Unknown attribute', 404);
}
if (!isset($object->attributesAccess[$attribute])) {
throw new WebServiceError('Unknown attribute', 404);
}
if (!$object->isActive()) {
throw new WebServiceError('Inactive tab', 400);
}
if (!$object->isActive()) {
throw new WebServiceError('Inactive tab', 400);
}
if (!$object->acl_is_readable($object->attributesAccess[$attribute]->getAcl())) {
throw new WebServiceError('Not enough rights to read "'.$attribute.'"', 403);
}
if (!$object->acl_is_readable($object->attributesAccess[$attribute]->getAcl())) {
throw new WebServiceError('Not enough rights to read "'.$attribute.'"', 403);
}
$object->attributesAccess[$attribute]->resetToDefault();
$object->attributesAccess[$attribute]->resetToDefault();
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $object->attributesAccess[$attribute]->serializeValue();
return $object->attributesAccess[$attribute]->serializeValue();
} finally {
Lock::deleteByObject($dn);
}
}
protected function endpoint_objects_DELETE_5 (int &$responseCode, $input, string $type, string $dn, string $tab, string $attribute, string $values)
......
......@@ -21,9 +21,10 @@
class WebServiceError extends FusionDirectoryException
{
public function toArray ()
public function toArray (): array
{
return [
'class' => get_class($this),
'message' => $this->getMessage(),
'line' => $this->getLine(),
'file' => $this->getFile(),
......@@ -31,6 +32,29 @@ class WebServiceError extends FusionDirectoryException
}
}
class WebServiceLockError extends WebServiceError
{
protected $lockedDn;
protected $lockAuthor;
public function __construct (string $message, string $lockedDn, string $lockAuthor, int $code = 0, Throwable $previous = NULL)
{
$this->lockedDn = $lockedDn;
$this->lockAuthor = $lockAuthor;
}
public function toArray (): array
{
return array_merge(
parent::toArray(),
[
'dn' => $this->lockedDn,
'author' => $this->lockAuthor,
]
);
}
}
class WebServiceErrors extends FusionDirectoryException
{
protected $errors;
......@@ -205,6 +229,18 @@ class fdRPCService
}
}
/**
* @param string|array $dn
* @returns void
*/
protected static function addLockOrThrow ($dn, string $user = NULL)
{
if (!empty($locks = Lock::get($dn))) {
throw new WebServiceLockError(sprintf(_('Cannot delete %s. It has been locked by %s.'), $dn, $locks[0]->userDn), $dn, $locks[0]->userDn);
}
Lock::add($dn, $user);
}
/*!
* \brief Log out
*/
......@@ -302,30 +338,36 @@ class fdRPCService
protected function _removetab (string $type, string $dn, string $tab): string
{
$this->checkAccess($type, $tab, $dn);
$tabobject = objects::open($dn, $type);
$tabobject->current = $tab;
if (!is_subclass_of($tabobject->by_object[$tab], 'simplePlugin')) {
throw new WebServiceError('Tab '.$tab.' is not based on simplePlugin, can’t remove it', 501);
} elseif (!$tabobject->by_object[$tab]->isActivatable()) {
throw new WebServiceError('Tab '.$tab.' cannot be deactivated, can’t remove it');
} elseif (!$tabobject->by_object[$tab]->isActive()) {
throw new WebServiceError('Tab '.$tab.' is not activated on "'.$dn.'", can’t remove it');
} elseif (!$tabobject->by_object[$tab]->acl_is_removeable()) {
throw new WebServiceError('You don\'t have sufficient rights to disable tab "'.$tab.'"', 403);
} else {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
static::addLockOrThrow($dn);
try {
$tabobject = objects::open($dn, $type);
$tabobject->current = $tab;
if (!is_subclass_of($tabobject->by_object[$tab], 'simplePlugin')) {
throw new WebServiceError('Tab '.$tab.' is not based on simplePlugin, can’t remove it', 501);
} elseif (!$tabobject->by_object[$tab]->isActivatable()) {
throw new WebServiceError('Tab '.$tab.' cannot be deactivated, can’t remove it');
} elseif (!$tabobject->by_object[$tab]->isActive()) {
throw new WebServiceError('Tab '.$tab.' is not activated on "'.$dn.'", can’t remove it');
} elseif (!$tabobject->by_object[$tab]->acl_is_removeable()) {
throw new WebServiceError('You don\'t have sufficient rights to disable tab "'.$tab.'"', 403);
} else {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
}
}
$_POST = [$tab.'_modify_state' => 1];
$tabobject->readPost();
$tabobject->update();
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
} finally {
Lock::deleteByObject($dn);
}
$_POST = [$tab.'_modify_state' => 1];
$tabobject->readPost();
$tabobject->update();
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
}
/*!
......@@ -461,42 +503,51 @@ class fdRPCService
protected function _setfields (string $type, $dn, $values): string
{
$this->checkAccess($type, array_keys($values), $dn);
if ($dn === NULL) {
$tabobject = objects::create($type);
} else {
$tabobject = objects::open($dn, $type);
if ($dn !== NULL) {
static::addLockOrThrow($dn);
}
foreach ($values as $tab => $tabvalues) {
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
try {
if ($dn === NULL) {
$tabobject = objects::create($type);
} else {
$tabobject = objects::open($dn, $type);
}
if (is_subclass_of($tabobject->by_object[$tab], 'simplePlugin') &&
$tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
foreach ($values as $tab => $tabvalues) {
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
}
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
if (is_subclass_of($tabobject->by_object[$tab], 'simplePlugin') &&
$tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
list($disabled, , $htmlText) = $tabobject->by_object[$tab]->getDisplayHeaderInfos();
if ($disabled) {
throw new WebServiceError(htmlunescape($htmlText));
}
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
}
}
$error = $tabobject->by_object[$tab]->deserializeValues($tabvalues);
if ($error !== TRUE) {
throw new WebServiceError($error);
}
$tabobject->current = $tab;
$tabobject->update();
$tabobject->loadTabs();
}
$error = $tabobject->by_object[$tab]->deserializeValues($tabvalues);
if ($error !== TRUE) {
throw new WebServiceError($error);
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
} finally {
if ($dn !== NULL) {
Lock::deleteByObject($dn);
}
$tabobject->current = $tab;
$tabobject->update();
$tabobject->loadTabs();
}
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
}
/*!
......@@ -518,56 +569,62 @@ class fdRPCService
private function adddelvalues (string $type, $dn, $values, bool $add): string
{
$this->checkAccess($type, array_keys($values), $dn);
if ($dn === NULL) {
throw new WebServiceError('No DN provided');
} else {
$tabobject = objects::open($dn, $type);
}
foreach ($values as $tab => $tabvalues) {
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
}
if (is_subclass_of($tabobject->by_object[$tab], 'simplePlugin') &&
$tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
static::addLockOrThrow($dn);
try {
$tabobject = objects::open($dn, $type);
foreach ($values as $tab => $tabvalues) {
if (!isset($tabobject->by_object[$tab])) {
throw new WebServiceError('This tab does not exists: "'.$tab.'"', 404);
}
}
foreach ($tabvalues as $name => $newvalues) {
if (isset($tabobject->by_object[$tab]->attributesAccess[$name])) {
if ($tabobject->by_object[$tab]->attrIsWriteable($name)) {
$attrvalues = $tabobject->by_object[$tab]->attributesAccess[$name]->getValue();
if (!is_array($attrvalues)) {
throw new WebServiceError(sprintf(_('Field "%s" is not multi-valuated'), $name));
}
if (!is_array($newvalues)) {
$newvalues = [$newvalues];
}
if ($add) {
$attrvalues = array_merge($attrvalues, $newvalues);
if (is_subclass_of($tabobject->by_object[$tab], 'simplePlugin') &&
$tabobject->by_object[$tab]->isActivatable() &&
!$tabobject->by_object[$tab]->isActive()
) {
if ($tabobject->by_object[$tab]->acl_is_createable()) {
$tabobject->by_object[$tab]->is_account = TRUE;
} else {
throw new WebServiceError('You don\'t have sufficient rights to enable tab "'.$tab.'"', 403);
}
}
foreach ($tabvalues as $name => $newvalues) {
if (isset($tabobject->by_object[$tab]->attributesAccess[$name])) {
if ($tabobject->by_object[$tab]->attrIsWriteable($name)) {
$attrvalues = $tabobject->by_object[$tab]->attributesAccess[$name]->getValue();
if (!is_array($attrvalues)) {
throw new WebServiceError(sprintf(_('Field "%s" is not multi-valuated'), $name));
}
if (!is_array($newvalues)) {
$newvalues = [$newvalues];
}
if ($add) {
$attrvalues = array_merge($attrvalues, $newvalues);
} else {
$attrvalues = array_remove_entries($newvalues, $attrvalues);
}
$tabobject->by_object[$tab]->attributesAccess[$name]->setValue($attrvalues);
} else {
$attrvalues = array_remove_entries($newvalues, $attrvalues);
throw new WebServiceError(htmlunescape(msgPool::permModify($dn, $name)), 403);
}
$tabobject->by_object[$tab]->attributesAccess[$name]->setValue($attrvalues);
} else {
throw new WebServiceError(htmlunescape(msgPool::permModify($dn, $name)), 403);
throw new WebServiceError(sprintf(_('Unknown field "%s"'), $name), 404);
}
} else {
throw new WebServiceError(sprintf(_('Unknown field "%s"'), $name), 404);
}
$tabobject->current = $tab;
$tabobject->update();
}
$tabobject->current = $tab;
$tabobject->update();
}
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
$errors = $tabobject->save();
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
return $tabobject->dn;
} finally {
Lock::deleteByObject($dn);
}
return $tabobject->dn;
}
/*!
......@@ -614,19 +671,18 @@ class fdRPCService
throw new WebServiceError(htmlunescape(msgPool::permDelete($dn)), 403);
}
if (!empty($locks = Lock::get($dn))) {
throw new WebServiceError(sprintf(_('Cannot delete %s. It has been locked by %s.'), $dn, $locks[0]->userDn));
}
Lock::add($dn);
// Delete the object
$errors = objects::delete($dn, $type);
static::addLockOrThrow($dn);
// Remove the lock for the current object.
Lock::deleteByObject($dn);
try {
// Delete the object
$errors = objects::delete($dn, $type);
if (!empty($errors)) {
throw new WebServiceErrors($errors);
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
} finally {
// Remove the lock for the current object.
Lock::deleteByObject($dn);
}
}
......@@ -653,52 +709,57 @@ class fdRPCService
throw new WebServiceError(htmlunescape(msgPool::permModify($disallowed)), 403);
}
// Try to lock/unlock the entries.
$ldap = $config->get_ldap_link();
$errors = [];
foreach ($dns as $dn) {
$ldap->cat($dn, ['userPassword']);
if ($ldap->count() == 1) {
// We can't lock empty passwords.
$val = $ldap->fetch();
if (!isset($val['userPassword'])) {
$errors[] = sprintf(_('Failed to get password method for account "%s". It has not been locked!'), $dn);
continue;
}
// Detect the password method and try to lock/unlock.
$method = passwordMethod::get_method($val['userPassword'][0], $dn);
if ($method instanceOf passwordMethod) {
$success = TRUE;
if ($type == 'toggle') {
if ($method->is_locked($dn)) {
$success = $method->unlock_account($dn);
} else {
static::addLockOrThrow($dns);
try {
// Try to lock/unlock the entries.
$ldap = $config->get_ldap_link();
$errors = [];
foreach ($dns as $dn) {
$ldap->cat($dn, ['userPassword']);
if ($ldap->count() == 1) {
// We can't lock empty passwords.
$val = $ldap->fetch();
if (!isset($val['userPassword'])) {
$errors[] = sprintf(_('Failed to get password method for account "%s". It has not been locked!'), $dn);
continue;
}
// Detect the password method and try to lock/unlock.
$method = passwordMethod::get_method($val['userPassword'][0], $dn);
if ($method instanceOf passwordMethod) {
$success = TRUE;
if ($type == 'toggle') {
if ($method->is_locked($dn)) {
$success = $method->unlock_account($dn);
} else {
$success = $method->lock_account($dn);
}
} elseif ($type == 'lock' && !$method->is_locked($dn)) {
$success = $method->lock_account($dn);
} elseif ($type == 'unlock' && $method->is_locked($dn)) {
$success = $method->unlock_account($dn);
}
} elseif ($type == 'lock' && !$method->is_locked($dn)) {
$success = $method->lock_account($dn);
} elseif ($type == 'unlock' && $method->is_locked($dn)) {
$success = $method->unlock_account($dn);
}
// Check if everything went fine.
if (!$success) {
$hn = $method->get_hash_name();
if (is_array($hn)) {
$hn = $hn[0];
// Check if everything went fine.
if (!$success) {
$hn = $method->get_hash_name();
if (is_array($hn)) {
$hn = $hn[0];
}
$errors[] = sprintf(_('Password method "%s" failed locking. Account "%s" has not been locked!'), $hn, $dn);
}
$errors[] = sprintf(_('Password method "%s" failed locking. Account "%s" has not been locked!'), $hn, $dn);
} else {
// Can't lock unknown methods.
$errors[] = sprintf(_('Failed to get password method for account "%s". It has not been locked!'), $dn);
}
} else {
// Can't lock unknown methods.
$errors[] = sprintf(_('Failed to get password method for account "%s". It has not been locked!'), $dn);
$errors[] = sprintf(_('Could not find account "%s" in LDAP. It has not been locked!'), $dn);
}
} else {
$errors[] = sprintf(_('Could not find account "%s" in LDAP. It has not been locked!'), $dn);
}
}
if (!empty($errors)) {
throw new WebServiceErrors($errors);
if (!empty($errors)) {
throw new WebServiceErrors($errors);
}
} finally {
Lock::deleteByObject($dns);
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment