2018-09-23 18:12:27 +00:00
|
|
|
<?php
|
|
|
|
namespace LAM\TOOLS\IMPORT_EXPORT;
|
|
|
|
use \htmlStatusMessage;
|
|
|
|
use \LAMException;
|
|
|
|
/*
|
|
|
|
|
|
|
|
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
|
|
|
|
Copyright (C) 2018 Roland Gruber
|
|
|
|
|
|
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* LDIF export.
|
|
|
|
*
|
|
|
|
* @author Roland Gruber
|
|
|
|
* @package tools
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** LDAP handle */
|
|
|
|
include_once('ldap.inc');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates LDAP accounts for file upload.
|
|
|
|
*
|
|
|
|
* @author Roland Gruber
|
|
|
|
* @package tools
|
|
|
|
*/
|
|
|
|
class Exporter {
|
|
|
|
|
|
|
|
const DATA = 'data';
|
|
|
|
const STATUS = 'status';
|
2018-10-04 19:07:55 +00:00
|
|
|
const FILE = 'file';
|
|
|
|
const OUTPUT = 'output';
|
2018-09-23 18:12:27 +00:00
|
|
|
|
|
|
|
private $baseDn = null;
|
|
|
|
private $searchScope = null;
|
|
|
|
private $filter = null;
|
|
|
|
private $attributes = null;
|
|
|
|
private $includeSystem = false;
|
|
|
|
private $saveAsFile = false;
|
|
|
|
private $format = null;
|
|
|
|
private $ending = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*
|
|
|
|
* @param string $baseDn base DN
|
|
|
|
* @param string $searchScope search scope (base, one, sub)
|
|
|
|
* @param string $filter search filter
|
|
|
|
* @param string $attributes attributes to return
|
|
|
|
* @param bool $includeSystem include system attributes
|
|
|
|
* @param bool $saveAsFile return as file
|
|
|
|
* @param string $format output format (LDIF, CSV)
|
|
|
|
* @param string $ending line endings (windows, unix)
|
|
|
|
*/
|
|
|
|
public function __construct($baseDn, $searchScope, $filter, $attributes, $includeSystem, $saveAsFile, $format, $ending) {
|
|
|
|
$this->baseDn = $baseDn;
|
|
|
|
$this->searchScope = $searchScope;
|
|
|
|
$this->filter = $filter;
|
|
|
|
$this->attributes = $attributes;
|
|
|
|
$this->includeSystem = $includeSystem;
|
|
|
|
$this->saveAsFile = $saveAsFile;
|
|
|
|
$this->format = $format;
|
|
|
|
$this->ending = $ending;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts the export process.
|
|
|
|
*
|
|
|
|
* @return string JSON result
|
|
|
|
*/
|
|
|
|
public function doExport() {
|
|
|
|
try {
|
|
|
|
$this->checkParameters();
|
2018-10-04 19:07:55 +00:00
|
|
|
$results = $this->getLDAPData();
|
|
|
|
return $this->writeDataAndReturnJson($results);
|
2018-09-23 18:12:27 +00:00
|
|
|
}
|
|
|
|
catch (LAMException $e) {
|
|
|
|
$data = Exporter::formatMessage('ERROR', $e->getTitle(), $e->getMessage());
|
|
|
|
$status = array(
|
|
|
|
Exporter::STATUS => 'failed',
|
|
|
|
Exporter::DATA => $data
|
|
|
|
);
|
|
|
|
return json_encode($status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the HTML for an error message.
|
|
|
|
*
|
|
|
|
* @param string $type message type (e.g. INFO)
|
|
|
|
* @param string $title title
|
|
|
|
* @param string $message message
|
|
|
|
* @return string HTML
|
|
|
|
*/
|
|
|
|
public static function formatMessage($type, $title, $message) {
|
|
|
|
$msg = new htmlStatusMessage($type, $title, $message);
|
|
|
|
$tabindex = 0;
|
|
|
|
ob_start();
|
|
|
|
$msg->generateHTML(null, array($msg), array(), true, $tabindex, 'user');
|
|
|
|
$data = ob_get_contents();
|
|
|
|
ob_clean();
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks the input parameters for validity.
|
|
|
|
*
|
|
|
|
* @throws LAMException in case of errors
|
|
|
|
*/
|
|
|
|
private function checkParameters() {
|
|
|
|
if (!get_preg($this->baseDn, 'dn')) {
|
|
|
|
throw new LAMException(_('Please enter a valid DN in the field:'), _('Base DN'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 19:07:55 +00:00
|
|
|
/**
|
|
|
|
* 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(
|
2018-10-06 08:45:44 +00:00
|
|
|
Exporter::OUTPUT => htmlspecialchars($output, ENT_NOQUOTES),
|
2018-10-04 19:07:55 +00:00
|
|
|
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) {
|
2018-10-06 08:45:44 +00:00
|
|
|
$attributeNames = array();
|
|
|
|
foreach ($entries as $entry) {
|
|
|
|
$entryAttributeNames = array_keys($entry);
|
|
|
|
foreach ($entryAttributeNames as $name) {
|
|
|
|
if (!in_array($name, $attributeNames)) {
|
|
|
|
$attributeNames[] = $name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$attributeNames = array_delete(array('dn'), $attributeNames);
|
|
|
|
sort($attributeNames);
|
|
|
|
array_unshift($attributeNames, 'dn');
|
|
|
|
|
|
|
|
$attributeNamesQuoted = array_map(array($this, 'escapeCsvAndAddQuotes'), $attributeNames);
|
|
|
|
$output = '';
|
|
|
|
// header
|
|
|
|
$output .= implode(',', $attributeNamesQuoted) . $lineEnding;
|
|
|
|
// content
|
|
|
|
foreach ($entries as $entry) {
|
|
|
|
$values = array();
|
|
|
|
foreach ($attributeNames as $name) {
|
|
|
|
if (!isset($entry[$name])) {
|
|
|
|
$values[] = $this->escapeCsvAndAddQuotes('');
|
|
|
|
}
|
|
|
|
elseif (is_array($entry[$name])) {
|
|
|
|
$values[] = $this->escapeCsvAndAddQuotes(implode(' | ', $entry[$name]));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$values[] = $this->escapeCsvAndAddQuotes($entry[$name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$output .= implode(',', $values) . $lineEnding;
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-02-24 19:08:28 +00:00
|
|
|
* Escapes a CSV value and adds quotes around it.
|
2018-10-06 08:45:44 +00:00
|
|
|
*
|
|
|
|
* @param string $value CSV value
|
|
|
|
* @return string escaped and quoted value
|
|
|
|
*/
|
|
|
|
private function escapeCsvAndAddQuotes($value) {
|
|
|
|
return '"' . str_replace('"', '""', $value) . '"';
|
2018-10-04 19:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2018-09-23 18:12:27 +00:00
|
|
|
}
|