From 1d7db3794bc3917ae17e913e17d40ecc5e592d2f Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 4 Oct 2018 21:07:55 +0200 Subject: [PATCH] LDIF export --- lam/help/help.inc | 8 ++ lam/lib/account.inc | 26 +++++ lam/lib/export.inc | 151 +++++++++++++++++++++++++++ lam/templates/lib/500_lam.js | 13 +++ lam/templates/tools/importexport.php | 17 ++- 5 files changed, 205 insertions(+), 10 deletions(-) diff --git a/lam/help/help.inc b/lam/help/help.inc index f3cc2d19..c9d29e82 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -342,6 +342,14 @@ $helpArray = array ( // import/export "750" => array ("Headline" => _('LDIF data'), "Text" => _('The input data must be formatted in LDIF format.')), + "751" => array ("Headline" => _('Base DN'), + "Text" => _('The export will read entries of this DN.')), + "752" => array ("Headline" => _('Search filter'), + "Text" => _('Please enter an LDAP filter to specifiy the exported entries.')), + "753" => array ("Headline" => _('Attributes'), + "Text" => _('Please enter a comma separated list of attributes to export. Using "*" will export all attributes.')), + "754" => array ("Headline" => _('Include system attributes'), + "Text" => _('Activate this option to export internal attributes that are not visible by default.')), // 800 - 899 // jobs '800' => array( diff --git a/lam/lib/account.inc b/lam/lib/account.inc index d43a17b9..92ce6196 100644 --- a/lam/lib/account.inc +++ b/lam/lib/account.inc @@ -854,6 +854,32 @@ function ldapGetDN($dn, $attributes = array('dn'), $handle = null) { return $return; } +/** + * Returns the DN and children of a given DN. + * + * @param String $dn DN + * @param String $filter LDAP filter + * @param array $attributes list of attributes to fetch + * @param handle $handle LDAP handle (optional for admin interface pages) + * @return array attributes or null if not found + */ +function ldapListDN($dn, $filter = '(objectclass=*)', $attributes = array('dn'), $handle = null) { + if ($handle == null) { + $handle = $_SESSION['ldap']->server(); + } + $return = null; + $sr = @ldap_list($handle, escapeDN($dn), $filter, $attributes, 0, 0, 0, LDAP_DEREF_NEVER); + if ($sr) { + $entries = ldap_get_entries($handle, $sr); + if ($entries) { + cleanLDAPResult($entries); + $return = $entries; + } + @ldap_free_result($sr); + } + return $return; +} + /** * Deletes a DN and all child entries. * diff --git a/lam/lib/export.inc b/lam/lib/export.inc index f084db75..7d53a646 100644 --- a/lam/lib/export.inc +++ b/lam/lib/export.inc @@ -43,6 +43,8 @@ class Exporter { const DATA = 'data'; const STATUS = 'status'; + const FILE = 'file'; + const OUTPUT = 'output'; private $baseDn = null; private $searchScope = null; @@ -84,6 +86,8 @@ class Exporter { public function doExport() { try { $this->checkParameters(); + $results = $this->getLDAPData(); + return $this->writeDataAndReturnJson($results); } catch (LAMException $e) { $data = Exporter::formatMessage('ERROR', $e->getTitle(), $e->getMessage()); @@ -124,4 +128,151 @@ class Exporter { } } + /** + * Returns the LDAP entries + * + * @return array[] LDAP entries + */ + private function getLDAPData() { + $attributes = preg_split('/,[ ]*/', $this->attributes); + if ($this->includeSystem) { + $attributes = array_merge($attributes, array('+', 'passwordRetryCount', 'accountUnlockTime', 'nsAccountLock', + 'nsRoleDN', 'passwordExpirationTime', 'pwdChangedTime')); + } + $attributes = array_unique($attributes); + switch ($this->searchScope) { + case 'base': + return array(ldapGetDN($this->baseDn, $attributes)); + break; + case 'one': + return ldapListDN($this->baseDn, $this->filter, $attributes); + break; + case 'sub': + return searchLDAP($this->baseDn, $this->filter, $attributes); + break; + default: + throw new LAMException('Invalid scope'); + break; + } + } + + /** + * Writes the entries to file/response and prints JSON. + * + * @param array $entries LDAP entries + */ + private function writeDataAndReturnJson(&$entries) { + $lineEnding = ($this->ending === 'windows') ? "\r\n" : "\n"; + if ($this->format === 'csv') { + $output = $this->getCsvOutput($entries, $lineEnding); + } + elseif ($this->format === 'ldif') { + $output = $this->getLdifOutput($entries, $lineEnding); + } + else { + throw new LAMException('Invalid format'); + } + if ($this->saveAsFile) { + $filename = '../../tmp/' . getRandomNumber() . time() .'.' . $this->format; + $handle = fopen($filename, 'w'); + chmod($filename, 0640); + fwrite($handle, $output); + fclose($handle); + return json_encode(array( + Exporter::FILE => $filename, + Exporter::STATUS => 'done' + )); + } + return json_encode(array( + Exporter::OUTPUT => htmlspecialchars($output), + Exporter::STATUS => 'done' + )); + } + + /** + * Converts the given LDAP entries to CSV format. + * + * @param string $entries entries + * @param string $lineEnding line ending + */ + private function getCsvOutput(&$entries, $lineEnding) { + return 'CSV'; + } + + /** + * Converts the given LDAP entries to LDIF format. + * + * @param string $entries entries + * @param string $lineEnding line ending + */ + private function getLdifOutput(&$entries, $lineEnding) { + $output = ''; + $output .= '#' . $lineEnding; + $output .= '# ' . _('Base DN') . ': ' . $this->baseDn . $lineEnding; + $output .= '# ' . _('Search scope') . ': ' . $this->searchScope . $lineEnding; + $output .= '# ' . _('Search filter') . ': ' . $this->filter . $lineEnding; + $output .= '# ' . _('Total entries') . ': ' . sizeof($entries) . $lineEnding; + $output .= '#' . $lineEnding; + $output .= '# Generated by LDAP Account Manager on ' . date('Y-m-d H:i:s') . $lineEnding; + $output .= $lineEnding; + $output .= $lineEnding; + $output .= 'version: 1'; + $output .= $lineEnding; + $output .= $lineEnding; + foreach ($entries as $entry) { + $output .= 'dn: ' . $entry['dn'] . $lineEnding; + unset($entry['dn']); + ksort($entry); + foreach ($entry as $attributeName => $values) { + foreach ($values as $value) { + if ($this->isPlainAscii($value)) { + $output .= $this->wrapLdif($attributeName . ': ' . $value, $lineEnding) . $lineEnding; + } + else { + $output .= $this->wrapLdif($attributeName . ':: ' . base64_encode($value), $lineEnding) . $lineEnding; + } + } + } + $output .= $lineEnding; + } + + return $output; + } + + /** + * Splits the LDIF line if needed. + * + * @param string $content line content + * @param string $lineEnding line ending + */ + private function wrapLdif($content, $lineEnding) { + $line_length = 76; + if (strlen($content) <= $line_length) { + return $content; + } + $wrappedContent = substr($content, 0, $line_length) . $lineEnding; + $contentLeft = substr($content, $line_length); + $line_length = $line_length - 1; + $lines = str_split($contentLeft, $line_length); + foreach ($lines as $line) { + $wrappedContent .= ' ' . $line . $lineEnding; + } + return trim($wrappedContent); + } + + /** + * Checks if the value is plain ASCII. + * + * @param string $content content to check + * @return bool is plain ASCII + */ + private function isPlainAscii($content) { + for ($i=0; $i < strlen($content); $i++) { + if (ord($content[$i]) < 32 || ord($content[$i]) > 127) { + return false; + } + } + return true; + } + } diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index d77f2343..db88a714 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -986,6 +986,12 @@ window.lam.importexport.startExport = function(tokenName, tokenValue) { jQuery('#statusExportInprogress').hide(); jQuery('#statusExportDone').show(); jQuery('.newexport').show(); + if (jsonData.output) { + jQuery('#exportResults > pre').text(jsonData.output); + } + else if (jsonData.file) { + window.open(jsonData.file, '_blank'); + } } else { jQuery('#progressbarExport').hide(); @@ -994,6 +1000,13 @@ window.lam.importexport.startExport = function(tokenName, tokenValue) { jQuery('#statusExportFailed').show(); jQuery('.newexport').show(); } + }) + .fail(function() { + jQuery('#progressbarExport').hide(); + jQuery('#btn_submitExportCancel').hide(); + jQuery('#statusExportInprogress').hide(); + jQuery('#statusExportFailed').show(); + jQuery('.newexport').show(); }); }); }; diff --git a/lam/templates/tools/importexport.php b/lam/templates/tools/importexport.php index 0e3f4f67..f0b974f1 100644 --- a/lam/templates/tools/importexport.php +++ b/lam/templates/tools/importexport.php @@ -262,12 +262,7 @@ function printExportTabContent(&$tabindex) { $container = new htmlResponsiveRow(); $container->add(new htmlTitle(_("Export")), 12); - $container->addLabel(new htmlOutputText(_('Base DN'), true, true)); - $baseDnGroup = new htmlGroup(); - $baseDnInput = new htmlInputField('baseDn', getDefaultBaseDn()); - $baseDnInput->setRequired(true); - $baseDnGroup->addElement($baseDnInput); - $container->addField($baseDnGroup); + $container->add(new htmlResponsiveInputField(_('Base DN'), 'baseDn', getDefaultBaseDn(), '751', true), 12); $searchScopes = array( _('Base (base dn only)') => 'base', @@ -278,9 +273,9 @@ function printExportTabContent(&$tabindex) { $searchScopeSelect->setHasDescriptiveElements(true); $searchScopeSelect->setSortElements(false); $container->add($searchScopeSelect, 12); - $container->add(new htmlResponsiveInputField(_('Search filter'), 'filter', '(objectClass=*)'), 12); - $container->add(new htmlResponsiveInputField(_('Attributes'), 'attributes', '*'), 12); - $container->add(new htmlResponsiveInputCheckbox('includeSystem', false, _('Include system attributes')), 12); + $container->add(new htmlResponsiveInputField(_('Search filter'), 'filter', '(objectClass=*)', '752'), 12); + $container->add(new htmlResponsiveInputField(_('Attributes'), 'attributes', '*', '753'), 12); + $container->add(new htmlResponsiveInputCheckbox('includeSystem', false, _('Include system attributes'), '754'), 12); $container->add(new htmlResponsiveInputCheckbox('saveAsFile', false, _('Save as file')), 12); $formats = array( @@ -375,7 +370,9 @@ function printExportTabProcessing(&$tabindex) { $container->addVerticalSpacer('3rem'); - $container->add(new htmlDiv('exportResults', new htmlOutputText('')), 12); + $exportText = new htmlOutputText(''); + $exportText->setPreformatted(true); + $container->add(new htmlDiv('exportResults', $exportText), 12); $container->add(new htmlJavaScript( 'window.lam.importexport.startExport(\'' . getSecurityTokenName() . '\', \'' . getSecurityTokenValue() . '\');' ), 12);