ldapsaisie/src/includes/addons/LSaddons.ppolicy.php

485 lines
14 KiB
PHP

<?php
/*******************************************************************************
* Copyright (C) 2007 Easter-eggs
* https://ldapsaisie.org
*
* Author: See AUTHORS file in top-level directory.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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.
******************************************************************************/
// Error messages
LSerror :: defineError('PPOLICY_SUPPORT_01',
___("Password policy Support : The constant %{const} is not defined.")
);
LSerror :: defineError('PPOLICY_SUPPORT_02',
___("Password policy Support : The global array %{array} is not defined.")
);
LSerror :: defineError('PPOLICY_01',
___("Password policy: An error occured generating CSV outfile memory space.")
);
LSerror :: defineError('PPOLICY_02',
___("Password policy: An error occured executing the search.")
);
LSerror :: defineError('PPOLICY_03',
___("Password policy: An error occured writing CSV header.")
);
LSerror :: defineError('PPOLICY_04',
___("Password policy: An error occured writing a CSV row.")
);
/**
* Check support of Ppolicy by LdapSaisie
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*
* @return boolean true if Ppolicy is totally supported, false in other case
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
function LSaddon_ppolicy_support() {
$retval = true;
$MUST_DEFINE_CONST = array(
'LS_PPOLICY_DEFAULT_DN',
'LS_PPOLICY_WARNING_EXPIRATION_THRESHOLD',
'LS_PPOLICY_CRITICAL_EXPIRATION_THRESHOLD',
'LS_PPOLICY_CSV_DELIMITER',
'LS_PPOLICY_CSV_ENCLOSURE',
'LS_PPOLICY_CSV_ESCAPE_CHAR',
);
foreach($MUST_DEFINE_CONST as $const) {
if (!defined($const)) {
LSerror :: addErrorCode('PPOLICY_SUPPORT_01', $const);
$retval = false;
}
}
$MUST_DEFINE_ARRAY= array(
'LS_PPOLICY_API_GRANTED_PROFILES',
'LS_PPOLICY_INFO_EXPORT_EXTRA_ATTRS',
);
foreach($MUST_DEFINE_ARRAY as $array) {
if ( !isset($GLOBALS[$array]) || !is_array($GLOBALS[$array])) {
LSerror :: addErrorCode('PPOLICY_SUPPORT_02', $array);
$retval = false;
}
}
// Init ppolicy objects cache
$GLOBALS['PPOLICY_OBJECTS_CACHE'] = array();
if ($retval) {
LSurl :: add_handler(
'#^api/1.0/exportPpolicyInfo/(?P<LSobject>[^/]+)/?$#',
'handle/export_api_LSobject_exportPpolicyInfo',
true, false, true);
}
return $retval;
}
/**
* Retrieve ppolicy object (from cache if already loaded)
*
* @param[in] $dn string The DN of the ppolicy object
*
* @return array|false Array of ppolicy object's attributes on success, false otherwise
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
function get_ppolicy_object($dn) {
if (array_key_exists($dn, $GLOBALS['PPOLICY_OBJECTS_CACHE']))
return $GLOBALS['PPOLICY_OBJECTS_CACHE'][$dn];
$GLOBALS['PPOLICY_OBJECTS_CACHE'][$dn] = LSldap :: getAttrs($dn, '(objectClass=pwdPolicy)');
return $GLOBALS['PPOLICY_OBJECTS_CACHE'][$dn];
}
/**
* Retrieve ppolicy password max age
*
* @param[in] $ppolicy_dn string Optional DN of the ppolicy object to use
*
* @return int|null|false The ppolicy password max age (in second) if defined, null if no password max age is defined or false in case of error
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
function get_ppolicy_password_max_age($ppolicy_dn=null) {
if (!$ppolicy_dn)
$ppolicy_dn = LS_PPOLICY_DEFAULT_DN;
if (!$ppolicy_dn)
return null;
$ppolicy = get_ppolicy_object($ppolicy_dn);
if (!$ppolicy)
return false;
if (isset($ppolicy['pwdMaxAge']))
return intval($ppolicy['pwdMaxAge']);
return null;
}
/**
* Format and return HTML code of a badge
*
* @param[in] $text string The text of the badge
* @param[in] $bg_color string The background color of the badge (optional, default: green)
* @param[in] $color string The text color of the badge (optional, default: white)
*
* @return string The HTML code of the badge
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
function _ppolicy_badge($text, $bg_color='green', $color='white') {
// Disable HTML formating on PHP cli
if (php_sapi_name() == 'cli') return $text;
return sprintf(
'<span style="
background-color: %s; color: %s;
padding: 0.2em; font-size: 0.8em; border-radius: 0.4em;"
>%s</span>',
$bg_color, $color, $text
);
}
/**
* Retrieve Ppolicy extraDisplayedColumn password expiration
*
* @param[in] $entry An LSsearchEntry object
*
* @return string Ppolicy extraDisplayedColumn password expiration
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
function ppolicy_extraDisplayColumn_password_expiration($entry) {
$change_time = $entry->pwdChangedTime;
if (!$change_time)
return _('Never');
$change_time = ldapDate2Timestamp($change_time);
$max_age = get_ppolicy_password_max_age($entry->pwdPolicySubentry);
if ($max_age === false)
return _ppolicy_badge(__('Unknown'), 'gray');
if (!$max_age)
return _('Never');
$expiration_date = $change_time + $max_age;
$now = time();
if ($expiration_date <= $now)
return _ppolicy_badge(
sprintf(_('Expired (since %s)'), date('Y-m-d H:i', $expiration_date)),
'black');
$delta = $expiration_date - $now;
if ($delta <= LS_PPOLICY_CRITICAL_EXPIRATION_THRESHOLD)
$badge_color = 'red';
elseif ($delta <= LS_PPOLICY_WARNING_EXPIRATION_THRESHOLD)
$badge_color = 'orange';
else
$badge_color = 'green';
return _ppolicy_badge(
sprintf(_('Expire on %s'), date('Y-m-d H:i', $expiration_date)),
$badge_color);
}
/**
* Write LSsearch result as CSV and force download of it.
*
* @param[in] $LSsearch The LSsearch object
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*
* @return boolean Void if CSV file is successfully generated and upload, false in other case
*/
function ppolicy_export_search_info($LSsearch, $as_csv=true, $return=false) {
if ($as_csv) {
$csv = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
if ($csv === false) {
LSerror :: addErrorCode('PPOLICY_01');
return false;
}
}
$attrs = array_merge(
array(
'pwdPolicySubentry', 'pwdChangedTime', 'pwdGraceUseTime', 'pwdFailureTime',
'pwdAccountLockedTime', 'pwdReset', 'pwdHistory'
),
$GLOBALS['LS_PPOLICY_INFO_EXPORT_EXTRA_ATTRS']
);
$LSsearch -> setParam('attributes', $attrs);
if (!$LSsearch -> run()) {
LSerror :: addErrorCode('PPOLICY_02');
return false;
}
if ($as_csv) {
$headers = array($LSsearch->label_objectName, 'DN');
foreach($attrs as $attr) {
$label = LSconfig::get("LSobjects.".$LSsearch->LSobject.".attrs.$attr.label", $attr, 'string');
$headers[] = __($label);
}
if (!_ppolicy_write_row_in_csv($csv, $headers)) {
LSerror :: addErrorCode('PPOLICY_03');
return false;
}
}
else {
$data = array();
}
foreach ($LSsearch -> getSearchEntries() as $e) {
$row = array(
'name' => $e -> displayName,
'dn' => $e -> dn,
);
foreach($attrs as $attr) {
if ($as_csv) {
$values = ensureIsArray($e -> get($attr));
if ($values) {
$row[] = ($as_json?$values:implode('|', $values));
}
else {
$no_value_label = LSconfig::get(
"LSobjects.".$LSsearch->LSobject.".attrs.$attr.no_value_label",
___('Not set'), 'string');
$row[] = __($no_value_label);
}
}
else {
$row[$attr] = ensureIsArray($e -> get($attr));
if (!LSconfig::get("LSobjects.".$LSsearch->LSobject.".attrs.$attr.multiple")) {
$row[$attr] = ($row[$attr]?$row[$attr][0]:null);
}
}
}
if ($as_csv) {
if (!_ppolicy_write_row_in_csv($csv, $row)) {
LSerror :: addErrorCode('PPOLICY_04');
return false;
}
}
else {
$data[] = $row;
}
}
if (!$as_csv) {
if ($return)
return $data;
header("Content-disposition: attachment; filename=ppolicy-".$LSsearch->LSobject.".json");
LSsession :: displayAjaxReturn($data);
exit();
}
rewind($csv);
if ($return) {
$data = stream_get_contents($csv);
@fclose($csv);
return $data;
}
header("Content-disposition: attachment; filename=ppolicy-".$LSsearch->LSobject.".csv");
header("Content-type: text/csv");
print stream_get_contents($csv);
@fclose($csv);
exit();
}
/**
* Write CSV row in file
*
* @param[in] $csv The CSV file description reference
* @param[in] $row An array of a CSV row fields to write
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*
* @retval boolean True if CSV row is successfully writed, false in other case
*/
function _ppolicy_write_row_in_csv(&$csv, &$row) {
if (!defined('PHP_VERSION_ID') or PHP_VERSION_ID < 50504) {
return (
fputcsv($csv, $row, LS_PPOLICY_CSV_DELIMITER, LS_PPOLICY_CSV_ENCLOSURE) !== false
);
}
return (
fputcsv(
$csv, $row, LS_PPOLICY_CSV_DELIMITER, LS_PPOLICY_CSV_ENCLOSURE,
LS_PPOLICY_CSV_ESCAPE_CHAR) !== false
);
}
/**
* Handle exportPpolicyInfo API request
*
* @param[in] $request LSurlRequest The request
*
* @return void
**/
function handle_api_LSobject_exportPpolicyInfo($request) {
$object = get_LSobject_from_API_request($request);
if (!$object)
return;
$container_dn = LSconfig::get(
"LSobjects.".$object->LSobject.".container_dn",
"", "string");
$whoami = LSsession :: whoami(
$container_dn?
$container_dn.','.LSsession::getTopDn():
LSsession::getTopDn()
);
if (!array_intersect($GLOBALS['LS_PPOLICY_API_GRANTED_PROFILES'], $whoami)) {
LSerror :: addErrorCode('LSsession_11');
LSsession :: displayAjaxReturn();
return false;
}
if (!LSsession :: loadLSclass('LSsearch')) {
LSerror :: addErrorCode('LSsession_05', 'LSsearch');
LSsession :: displayAjaxReturn();
return false;
}
$search = new LSsearch(
$request->LSobject,
'api'
);
$search -> setParam('onlyAccessible', True);
$data = ppolicy_export_search_info($search, false, true);
LSsession :: displayAjaxReturn($data);
}
if (php_sapi_name() != 'cli')
return true;
function cli_export_ppolicy_info($command_args) {
$objType = null;
$output = false;
$json = false;
$pretty = false;
for ($i=0; $i < count($command_args); $i++) {
switch($command_args[$i]) {
case '-o':
case '--output':
$i++;
$output = $command_args[$i];
break;
case '-j':
case '--json':
$json = true;
break;
case '-p':
case '--pretty':
$pretty = true;
break;
default:
if (is_null($objType))
$objType = $command_args[$i];
else
LScli :: usage("Invalid ".$command_args[$i]." parameter.");
break;
}
}
if (is_null($objType))
LScli :: usage('You must provide LSobject type.');
if (!LSsession :: loadLSobject($objType))
return false;
if (!LSsession :: loadLSclass('LSsearch')) {
LSerror :: addErrorCode('LSsession_05', 'LSsearch');
LSsession :: displayAjaxReturn();
return false;
}
$search = new LSsearch($objType, 'cli_export_ppolicy_info');
$search -> setParam('onlyAccessible', True);
$data = ppolicy_export_search_info($search, !$json, true);
if ($json)
$data = json_encode(
$data,
($pretty?JSON_PRETTY_PRINT:0)
);
if (!$output) {
print($data);
exit();
}
$fd = fopen($output, 'w') or LStemplate::fatal_error("Fail to open output file '$output'");
fwrite($fd, $data) or LStemplate::fatal_error("Fail to write result in output file '$output'");
@fclose($fd);
}
/**
* Args autocompleter for CLI export_ppolicy_info command
*
* @param[in] $command_args array List of already typed words of the command
* @param[in] $comp_word_num int The command word number to autocomplete
* @param[in] $comp_word string The command word to autocomplete
* @param[in] $opts array List of global available options
*
* @retval array List of available options for the word to autocomplete
**/
function cli_export_ppolicy_info_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) {
$opts = array_merge($opts, array ('-o', '--output', '-j', '--json', '-p', '--pretty'));
// Handle positional args
$objType = null;
$objType_arg_num = null;
for ($i=0; $i < count($command_args); $i++) {
if (!in_array($command_args[$i], $opts)) {
// If object type not defined
if (is_null($objType)) {
// Defined it
$objType = $command_args[$i];
LScli :: unquote_word($objType);
$objType_arg_num = $i;
// Check object type exists
$objTypes = LScli :: autocomplete_LSobject_types($objType);
// Load it if exist and not trying to complete it
if (in_array($objType, $objTypes) && $i != $comp_word_num) {
LSsession :: loadLSobject($objType, false);
}
}
}
}
// If objType not already choiced (or currently autocomplete), add LSobject types to available options
if (!$objType || $objType_arg_num == $comp_word_num)
$opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word));
return LScli :: autocomplete_opts($opts, $comp_word);
}
LScli :: add_command(
'export_ppolicy_info',
'cli_export_ppolicy_info',
'Export password policy info of a all objects of a specified object type',
'[object type] [-o|--output filepath] [-j|--json [-p|--pretty]]',
false, // long desc
true,
'cli_export_ppolicy_info_args_autocompleter'
);