Merge remote-tracking branch 'origin/develop' into 413-update-dependencies

This commit is contained in:
Roland Gruber 2025-03-14 07:47:02 +01:00
commit 45baad01cf
12 changed files with 3896 additions and 3330 deletions

View file

@ -14,7 +14,8 @@
"ext-xmlreader": "*",
"ext-zip": "*",
"ext-gd": "*",
"ext-imagick": "*"
"ext-imagick": "*",
"ext-gettext": "*"
},
"scripts": {
"test": "vendor/bin/phpunit"

View file

@ -1,8 +1,8 @@
ldap-account-manager (9.1.RC1-1) unstable; urgency=medium
ldap-account-manager (9.1-1) unstable; urgency=medium
* new upstream release
-- Roland Gruber <post@rolandgruber.de> Tue, 25 Feb 2024 07:36:27 +0200
-- Roland Gruber <post@rolandgruber.de> Thu, 13 Mar 2025 07:36:27 +0200
ldap-account-manager (9.0-1) unstable; urgency=medium

View file

@ -29,7 +29,7 @@
FROM debian:bookworm-slim
LABEL maintainer="Roland Gruber <post@rolandgruber.de>"
ARG LAM_RELEASE=9.1.RC1
ARG LAM_RELEASE=9.1
EXPOSE 80
ENV \

View file

@ -3,7 +3,7 @@ services:
ldap-account-manager:
build:
context: .
image: ldapaccountmanager/lam:9.1.RC1
image: ldapaccountmanager/lam:9.1
restart: unless-stopped
ports:
- "8080:80"

View file

@ -1,16 +1,20 @@
March 2025 9.1
June 2025 9.2
- Active Directory: allow to restore deleted entries in tree view (415)
13.03.2025 9.1
- Usability improvements (347, 348, 360, 403)
- Active Directory: deleted entries in "CN=Deleted Objects" can be shown (option in server profile, advanced settings)
- Security: LAM no longer ships with any default passwords, main configuration password is requested on login if not yet set (390)
- Docker: support to read e.g. configuration password from file to support Docker swarm
- LAM Pro:
-> Added support to manage DNS entries of bind-dyndb-ldap (361)
-> Unix users: support to create a group with same name for rfc2307bis (404)
- Fixed bugs:
-> Ambiguous tooltip on profile editor for Shadow users (394)
-> Self service photo file enhancements (396)
-> Tree view: delete does not work in French (406)
-> Cron job mails: show all values for multi-value attribute wildcards (411)
- LAM Pro:
-> Added support to manage DNS entries of bind-dyndb-ldap (361)
-> Unix users: support to create a group with same name for rfc2307bis (404)
17.12.2024 9.0

View file

@ -1 +1 @@
9.1.RC1
9.1

View file

@ -543,10 +543,7 @@
<para>Show deleted entries: This is for Active Directory and Samba 4
only. It will unhide LDAP entries in "CN=Deleted Objects,DC=...". You
can use this to browse these entries in tree view. To restore an entry
run "Restore-ADObject -Identity GUID" in PowerShell where GUID is the
value of the "objectGUID" attribute (you might need to base64 decode
it).</para>
can use this to browse and restore these entries in tree view.</para>
<para>Referential integrity overlay: Activate this checkbox if you
have any server side extension for referential integrity in place. In

View file

@ -70,6 +70,7 @@ include_once __DIR__ . '/tools/treeview.inc';
* @package LAM\TOOLS\TREEVIEW
*/
class TreeView {
const AD_DELETED_DELIMITER = '\0ADEL:';
/**
* @var array schema attributes
@ -124,6 +125,10 @@ class TreeView {
$this->ensureWriteAccess();
$this->validateDn($dn);
return $this->deleteNode($dn);
case 'restoreNode':
$this->ensureWriteAccess();
$this->validateDn($dn);
return $this->restoreNode($dn);
case 'search':
$this->validateDn($dn);
return $this->search($dn);
@ -366,10 +371,10 @@ class TreeView {
$row->addVerticalSpacer('1rem');
}
$row->add(new htmlTitle(unescapeLdapSpecialCharacters($dn)));
$this->addActionBar($row, $dn);
$attributes = ldapGetDN($dn, ['*']);
$this->addActionBar($row, $dn, $attributes);
$row->add(new htmlDiv('ldap_actionarea_messages', new htmlOutputText('')));
$row->add(new htmlSubTitle(_('Attributes')));
$attributes = ldapGetDN($dn, ['*']);
unset($attributes['dn']);
ksort($attributes);
logNewMessage(LOG_DEBUG, 'LDAP attributes for ' . $dn . ': ' . print_r($attributes, true));
@ -471,11 +476,13 @@ class TreeView {
*
* @param htmlResponsiveRow $row container
* @param string $dn entry DN
* @param array $attributes LDAP attributes
*/
private function addActionBar(htmlResponsiveRow $row, string $dn): void {
private function addActionBar(htmlResponsiveRow $row, string $dn, array $attributes): void {
$buttonGroup = new htmlGroup();
$buttonSize = '16px';
$buttonClasses = ['clickable', 'lam-margin-small', 'icon'];
$isRestorable = isset($attributes['isdeleted'][0]) && ($attributes['isdeleted'][0] === 'TRUE') && str_contains($dn, '\0ADEL');
if (checkIfWriteAccessIsAllowed()) {
$createButton = new htmlImage('../../graphics/add.svg', $buttonSize, $buttonSize, _('Create a child entry'), _('Create a child entry'));
@ -484,13 +491,15 @@ class TreeView {
$createButton->setCSSClasses($buttonClasses);
$buttonGroup->addElement($createButton);
$deleteButton = new htmlImage('../../graphics/delete.svg', $buttonSize, $buttonSize, _('Delete'), _('Delete'));
$deleteButton->setOnClick('window.lam.treeview.deleteNode(\'' . getSecurityTokenName() . '\', '
. '\'' . getSecurityTokenValue() . '\', \'' . base64_encode($dn) . '\', \'' . base64_encode(htmlspecialchars(extractRDN($dn))) . '\', '
. '\'' . addslashes(_('Delete')) . '\', \'' . addslashes(_('Cancel')) . '\', \'' . addslashes(_('Delete this entry')) . '\', \'' . addslashes(_('Ok')) . '\', '
. '\'' . addslashes(_('Error')) . '\');');
$deleteButton->setCSSClasses($buttonClasses);
$buttonGroup->addElement($deleteButton);
if (!$isRestorable) {
$deleteButton = new htmlImage('../../graphics/delete.svg', $buttonSize, $buttonSize, _('Delete'), _('Delete'));
$deleteButton->setOnClick('window.lam.treeview.deleteNode(\'' . getSecurityTokenName() . '\', '
. '\'' . getSecurityTokenValue() . '\', \'' . base64_encode($dn) . '\', \'' . base64_encode(htmlspecialchars(extractRDN($dn))) . '\', '
. '\'' . addslashes(_('Delete')) . '\', \'' . addslashes(_('Cancel')) . '\', \'' . addslashes(_('Delete this entry')) . '\', \'' . addslashes(_('Ok')) . '\', '
. '\'' . addslashes(_('Error')) . '\');');
$deleteButton->setCSSClasses($buttonClasses);
$buttonGroup->addElement($deleteButton);
}
}
$refreshButton = new htmlImage('../../graphics/refresh.svg', $buttonSize, $buttonSize, _('Refresh'), _('Refresh'));
@ -534,7 +543,29 @@ class TreeView {
$exportButton->setCSSClasses($buttonClasses);
$buttonGroup->addElement($exportButton);
}
$row->add($buttonGroup);
if (checkIfWriteAccessIsAllowed() && $isRestorable) {
$row->addVerticalSpacer('1rem');
$row->add(new htmlSubTitle(_('Restore')));
$targetDn = extractDNSuffix(extractDNSuffix($dn));
if (!empty($attributes['lastknownparent'][0]) && !str_contains($attributes['lastknownparent'][0], self::AD_DELETED_DELIMITER)) {
$targetDn = $attributes['lastknownparent'][0];
}
$restoreTargetDn = new htmlResponsiveInputField(_('Target DN'), 'treeview-restore-dn', $targetDn);
$restoreTargetDn->showDnSelection();
$row->add($restoreTargetDn);
$row->addVerticalSpacer('0.5rem');
$restoreButton = new htmlButton('restorebutton', _('Restore'));
$rdn = explode(self::AD_DELETED_DELIMITER, $dn)[0];
$restoreButton->setOnClick('window.lam.treeview.restoreNode(\'' . getSecurityTokenName() . '\', '
. '\'' . getSecurityTokenValue() . '\', \'' . base64_encode($dn) . '\', \'' . base64_encode(htmlspecialchars($rdn)) . '\', '
. '\'' . addslashes(_('Restore')) . '\', \'' . addslashes(_('Cancel')) . '\', \'' . addslashes(_('Restore this entry')) . '\', \'' . addslashes(_('Ok')) . '\', '
. '\'' . addslashes(_('Error')) . '\');');
$restoreButton->setCSSClasses(['lam-secondary']);
$row->add($restoreButton, 12, 12, 12, 'text-center');
}
}
/**
@ -1359,6 +1390,38 @@ class TreeView {
return json_encode([]);
}
/**
* Restores a node in LDAP.
*
* @param string $dn DN
* @return string JSON
*/
private function restoreNode(string $dn): string {
$rdn = explode(self::AD_DELETED_DELIMITER, $dn)[0];
$targetDn = $_POST['targetDn'];
$changes = [
[
'attrib' => 'isDeleted',
'modtype' => LDAP_MODIFY_BATCH_REMOVE_ALL
],
[
'attrib' => 'distinguishedName',
'modtype' => LDAP_MODIFY_BATCH_REPLACE,
'values' => [$rdn . ',' . $targetDn]
],
];
$success = ldap_modify_batch(getLDAPServerHandle(), $dn, $changes, getCommonLdapControls());
if ($success) {
return json_encode([]);
}
$error = getDefaultLDAPErrorString(getLDAPServerHandle());
logNewMessage(LOG_ERR, 'Tree view restore node failed: ' . $error);
return json_encode([
'errorTitle' => htmlspecialchars(sprintf(_('Was unable to restore %s.'), $rdn)),
'errorText' => $error
]);
}
/**
* Stops processing if DN is invalid.
*

File diff suppressed because it is too large Load diff

View file

@ -2497,6 +2497,64 @@ window.lam.treeview.deleteNode = function (tokenName, tokenValue, dn, text, okTe
});
}
/**
* Restores a node in tree view.
*
* @param tokenName security token name
* @param tokenValue security token value
* @param dn base64 encoded DN
* @param text text for dialog body
* @param okText text for OK button
* @param cancelText text for cancel button
* @param title dialog title
* @param errorOkText text for OK button in error dialog
* @param errorTitle dialog title in case of error
*/
window.lam.treeview.restoreNode = function (tokenName, tokenValue, dn, text, okText, cancelText, title, errorOkText, errorTitle) {
const textSpan = document.querySelector('.treeview-restore-entry');
textSpan.innerText = window.atob(text);
const dialogContent = document.getElementById('treeview_restore_dlg').cloneNode(true);
dialogContent.classList.remove('hidden');
const targetDn = document.getElementById('treeview-restore-dn').value;
Swal.fire({
title: title,
confirmButtonText: okText,
cancelButtonText: cancelText,
showCancelButton: true,
html: dialogContent.outerHTML,
width: '48em'
}).then(result => {
if (result.isConfirmed) {
let data = new FormData();
data.append(tokenName, tokenValue);
data.append('dn', dn)
data.append('targetDn', targetDn)
fetch("../misc/ajax.php?function=treeview&command=restoreNode", {
method: 'POST',
body: data
})
.then(async response => {
const jsonData = await response.json();
window.lam.treeview.checkSession(jsonData);
const tree = mar10.Wunderbaum.getTree("#ldap_tree");
const parentNode = tree.findKey(dn).parent;
await parentNode.setActive();
parentNode.loadLazy(true);
window.lam.treeview.getNodeContent(tokenName, tokenValue, parentNode.key);
if (jsonData['errorTitle']) {
const errTextTitle = jsonData['errorTitle'];
const textSpanErrorTitle = document.querySelector('.treeview-error-title');
textSpanErrorTitle.innerText = errTextTitle;
const errText = jsonData['errorText'];
const textSpanErrorText = document.querySelector('.treeview-error-text');
textSpanErrorText.innerText = errText;
window.lam.dialog.showSimpleDialog(errorTitle, null, errorOkText, null, 'treeview_error_dlg');
}
});
}
});
}
/**
* Returns the node content in tree view action area.
*

View file

@ -2,6 +2,7 @@
namespace LAM\TOOLS\TREEVIEW;
use htmlDiv;
use htmlForm;
use htmlInputField;
use htmlJavaScript;
use htmlOutputText;
use htmlResponsiveInputField;
@ -142,10 +143,20 @@ function showTree(): void {
$deleteDialogDiv = new htmlDiv('treeview_delete_dlg', $deleteDialogContent, ['hidden']);
$row->add($deleteDialogDiv);
$restoreDialogContent = new htmlResponsiveRow();
$restoreDialogContent->add(new htmlOutputText(_('Do you really want to restore this entry?')));
$restoreDialogContent->addVerticalSpacer('0.5rem');
$restoreDialogEntryText = new htmlOutputText('');
$restoreDialogEntryText->setCSSClasses(['treeview-restore-entry']);
$restoreDialogContent->add($restoreDialogEntryText);
$restoreDialogDiv = new htmlDiv('treeview_restore_dlg', $restoreDialogContent, ['hidden']);
$row->add($restoreDialogDiv);
$errorDialogContent = new htmlResponsiveRow();
$errorDialogEntryTitle = new htmlOutputText('');
$errorDialogEntryTitle->setCSSClasses(['treeview-error-title']);
$errorDialogContent->add($errorDialogEntryTitle);
$errorDialogContent->addVerticalSpacer('0.5rem');
$errorDialogEntryText = new htmlOutputText('');
$errorDialogEntryText->setCSSClasses(['treeview-error-text']);
$errorDialogContent->add($errorDialogEntryText);