mirror of
https://gitlab.easter-eggs.com/ee/ldapsaisie.git
synced 2024-05-05 22:05:08 +02:00
399 lines
13 KiB
PHP
399 lines
13 KiB
PHP
<?php
|
|
/*******************************************************************************
|
|
* Copyright (C) 2022 Easter-eggs
|
|
* http://ldapsaisie.labs.libre-entreprise.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.
|
|
|
|
******************************************************************************/
|
|
|
|
LSerror :: defineError('ACCESSLOG_SUPPORT_01',
|
|
___("accesslog Support : The constant %{const} is not defined.")
|
|
);
|
|
|
|
$GLOBALS['accesslog_reqTypes'] = array(
|
|
'add' => ___('Add'),
|
|
'bind' => ___('Log in'),
|
|
'compare' => ___('Compare'),
|
|
'delete' => ___('Delete'),
|
|
'extended' => ___('Extended'),
|
|
'modify' => ___('Modify'),
|
|
'modrdn' => ___('Modify RDN'),
|
|
'search' => ___('Search'),
|
|
'unbind' => ___('Log out'),
|
|
);
|
|
|
|
$GLOBALS['accesslog_modOps'] = array(
|
|
'+' => ___('Add'),
|
|
'-' => ___('Delete'),
|
|
'=' => ___('Replace'),
|
|
'' => ___('Replace'),
|
|
'#' => ___('Increment'),
|
|
);
|
|
|
|
function LSaddon_accesslog_support() {
|
|
$MUST_DEFINE_CONST= array(
|
|
'LS_ACCESSLOG_BASEDN',
|
|
'LS_ACCESSLOG_LOG_WRITE_EVENTS',
|
|
);
|
|
|
|
foreach($MUST_DEFINE_CONST as $const) {
|
|
if (!defined($const) || is_empty(constant($const))) {
|
|
LSerror :: addErrorCode('ACCESSLOG_SUPPORT_01', $const);
|
|
return false;
|
|
}
|
|
}
|
|
if (php_sapi_name() === 'cli') {
|
|
LScli::add_command(
|
|
'getEntryAccessLog',
|
|
'cli_getEntryAccessLog',
|
|
'Get entry access log',
|
|
'[entry DN] [page]'
|
|
);
|
|
}
|
|
elseif (LS_ACCESSLOG_LOG_WRITE_EVENTS && LSsession :: loadLSclass('LSldap')) {
|
|
LSldap :: addEvent('updated', 'onEntryUpdated');
|
|
LSldap :: addEvent('moved', 'onEntryMoved');
|
|
LSldap :: addEvent('user_password_updated', 'onEntryUserPasswordUpdated');
|
|
LSldap :: addEvent('deleted', 'onEntryDeleted');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function mapAccessLogEntry(&$entry) {
|
|
foreach($entry['attrs'] as $attr => $values)
|
|
$entry['attrs'][$attr] = ensureIsArray($values);
|
|
$attrs = $entry['attrs'];
|
|
$entry['start'] = LSldap::parseDate(LSldap::getAttr($attrs, 'reqStart'));
|
|
$entry['end'] = LSldap::parseDate(LSldap::getAttr($attrs, 'reqEnd'));
|
|
$entry['author_dn'] = LSldap::getAttr($attrs, 'reqAuthzID');
|
|
$entry['author_rdn'] = (
|
|
$entry['author_dn']?
|
|
explode('=', explode(',', $entry['author_dn'])[0])[1]:
|
|
null
|
|
);
|
|
$entry['type'] = LSldap::getAttr($attrs, 'reqType');
|
|
$entry['result'] = ldap_err2str(LSldap::getAttr($attrs, 'reqResult'));
|
|
$entry['message'] = LSldap::getAttr($attrs, 'reqMessage');
|
|
$mods = array();
|
|
foreach(LSldap::getAttr($attrs, 'reqMod', true) as $mod) {
|
|
if (preg_match('/^(?P<attr>[^\:]+)\:(?P<op>[^ ]?)( (?P<value>.*))?$/', $mod, $m)) {
|
|
$attr = $m['attr'];
|
|
$op = $m['op'];
|
|
$value = isset($m['value'])?$m['value']:null;
|
|
if (!array_key_exists($attr, $mods)) {
|
|
$mods[$attr] = array(
|
|
'changes' => array(),
|
|
'old_values' => array(),
|
|
);
|
|
}
|
|
$mods[$attr]['changes'][] = array(
|
|
'op' => (
|
|
array_key_exists($op, $GLOBALS['accesslog_modOps'])?
|
|
_($GLOBALS['accesslog_modOps'][$op]): $op
|
|
),
|
|
'value' => $value,
|
|
);
|
|
}
|
|
}
|
|
if (LSldap::getAttr($attrs, 'reqOld', true)) {
|
|
foreach(LSldap::getAttr($attrs, 'reqOld', true) as $old) {
|
|
if (preg_match('/^([^\:]+)\: (.*)$/', $old, $m) && array_key_exists($m[1], $mods)) {
|
|
$mods[$m[1]]['old_values'][] = $m[2];
|
|
}
|
|
}
|
|
}
|
|
if ($mods)
|
|
$entry['mods'] = $mods;
|
|
if ($entry['type'] === 'modrdn') {
|
|
$new_rdn = LSldap::getAttr($attrs, 'reqNewRDN', false);
|
|
$superior_dn = LSldap::getAttr($attrs, 'reqNewSuperior', false);
|
|
if (!$superior_dn) {
|
|
$superior_dn = parentDn(LSldap::getAttr($attrs, 'reqDN', false));
|
|
}
|
|
$entry['new_dn'] = "$new_rdn,$superior_dn";
|
|
}
|
|
if (array_key_exists($entry['type'], $GLOBALS['accesslog_reqTypes'])) {
|
|
$entry['type'] = _($GLOBALS['accesslog_reqTypes'][$entry['type']]);
|
|
}
|
|
}
|
|
|
|
function sortLogEntriesByDate(&$a, &$b) {
|
|
$astart = LSldap::getAttr($a['attrs'], 'reqStart');
|
|
$bstart = LSldap::getAttr($b['attrs'], 'reqStart');
|
|
return ($astart === $bstart) ? 0 : ($astart < $bstart) ? -1 : 1;
|
|
}
|
|
|
|
function getEntryAccessLog($dn, $start_date=null, $include_ldapsaisie=true) {
|
|
$filter = Net_LDAP2_Filter::create('reqDn', 'equals', $dn);
|
|
if ($start_date) {
|
|
$date_filter = Net_LDAP2_Filter::create('reqStart', 'greaterOrEqual', $start_date);
|
|
$filter = Net_LDAP2_Filter::combine('and', array($filter, $date_filter));
|
|
}
|
|
if (!$include_ldapsaisie) {
|
|
$not_ldapsaisie_filter = Net_LDAP2_Filter::combine('not', array(
|
|
Net_LDAP2_Filter::create(
|
|
'reqAuthzID', 'equals',
|
|
LSconfig::get('ldap_servers.'.LSsession::get('ldap_server_id').'.ldap_config.binddn')
|
|
)
|
|
));
|
|
$filter = Net_LDAP2_Filter::combine('and', array($filter, $not_ldapsaisie_filter));
|
|
}
|
|
$entries = LSldap::search(
|
|
$filter,
|
|
LS_ACCESSLOG_BASEDN,
|
|
array(
|
|
'attributes' => array(
|
|
'reqDN',
|
|
'reqStart',
|
|
'reqEnd',
|
|
'reqAuthzID',
|
|
'reqType',
|
|
'reqResult',
|
|
'reqMessage',
|
|
'reqMod',
|
|
'reqOld',
|
|
'reqNewRDN',
|
|
'reqNewSuperior',
|
|
),
|
|
)
|
|
);
|
|
if (!is_array($entries)) {
|
|
return;
|
|
}
|
|
usort($entries, 'sortLogEntriesByDate');
|
|
$logs = array();
|
|
$new_dn = null;
|
|
$rename_date = null;
|
|
foreach($entries as $entry) {
|
|
mapAccessLogEntry($entry);
|
|
$logs[] = $entry;
|
|
if (isset($entry['new_dn'])) {
|
|
$new_dn = $entry['new_dn'];
|
|
$rename_date = LSldap::formatDate($entry['start']);
|
|
break;
|
|
}
|
|
}
|
|
if ($new_dn) {
|
|
$next_logs = getEntryAccessLog($new_dn, $rename_date, $include_ldapsaisie);
|
|
if (is_array($next_logs))
|
|
$logs = array_merge($logs, $next_logs);
|
|
}
|
|
return $start_date?$logs:array_reverse($logs);
|
|
}
|
|
|
|
function getEntryAccessLogPage($dn, $page = false, $refresh=false, $include_ldapsaisie=true, $nbByPage = null) {
|
|
$nbByPage = is_null($nbByPage)?30:intval($nbByPage);
|
|
if (!isset($_SESSION['entryAccessLogPages'])) {
|
|
$_SESSION['entryAccessLogPages'] = array();
|
|
}
|
|
if (!isset($_SESSION['entryAccessLogPages'][$dn]) || $refresh) {
|
|
$_SESSION['entryAccessLogPages'][$dn] = getEntryAccessLog($dn, null, $include_ldapsaisie);
|
|
}
|
|
if (!is_int($page)) {
|
|
$page = 1;
|
|
}
|
|
return array(
|
|
'nb' => $page,
|
|
'nbPages' => ceil(count($_SESSION['entryAccessLogPages'][$dn]) / $nbByPage),
|
|
'logs' => array_slice(
|
|
$_SESSION['entryAccessLogPages'][$dn],
|
|
$page > 1 ? (($page - 1) * $nbByPage) - 1 : 0,
|
|
$nbByPage
|
|
)
|
|
);
|
|
}
|
|
|
|
function showObjectAccessLogs($obj) {
|
|
$refresh = isset($_REQUEST['refresh']);
|
|
$include_ldapsaisie = !LS_ACCESSLOG_LOG_WRITE_EVENTS;
|
|
if (isset($_REQUEST['include_ldapsaisie'])) {
|
|
$include_ldapsaisie = boolval($_REQUEST['include_ldapsaisie']);
|
|
$refresh = true;
|
|
}
|
|
elseif (isset($_SESSION['accesslog_include_ldapsaisie']))
|
|
$include_ldapsaisie = $_SESSION['accesslog_include_ldapsaisie'];
|
|
$_SESSION['accesslog_include_ldapsaisie'] = $include_ldapsaisie;
|
|
$pageNb = isset($_REQUEST['page']) ? intval($_REQUEST['page']) : 1;
|
|
$dn = $obj->getDn();
|
|
$page = getEntryAccessLogPage($dn, $pageNb, $refresh, $include_ldapsaisie);
|
|
if (!is_array($page)) {
|
|
return;
|
|
}
|
|
LStemplate::assign('page', $page);
|
|
$LSview_actions = array();
|
|
$LSview_actions['include_ldapsaisie'] = array (
|
|
'label' => $include_ldapsaisie?_('Hide LdapSaisie modifications'):_('Show LdapSaisie modifications'),
|
|
'url' => 'object/'.$obj->getType().'/'.urlencode($dn).'/customAction/showObjectAccessLogs?include_ldapsaisie='.intval(!$include_ldapsaisie),
|
|
'action' => $include_ldapsaisie?'hide':'view',
|
|
);
|
|
$LSview_actions['refresh'] = array (
|
|
'label' => _('Refresh'),
|
|
'url' => 'object/'.$obj->getType().'/'.urlencode($dn).'/customAction/showObjectAccessLogs?refresh',
|
|
'action' => 'refresh',
|
|
);
|
|
$LSview_actions['return'] = array (
|
|
'label' => _('Go back'),
|
|
'url' => 'object/'.$obj->getType().'/'.urlencode($dn),
|
|
'action' => 'view',
|
|
);
|
|
LStemplate::assign('LSview_actions', $LSview_actions);
|
|
LStemplate::addCSSFile('showObjectAccessLogs.css');
|
|
LSsession::setTemplate('showObjectAccessLogs.tpl');
|
|
LSsession::displayTemplate();
|
|
exit();
|
|
}
|
|
|
|
function onEntryUpdated($data) {
|
|
$now = LSldap::formatDate();
|
|
$dn = "reqStart=$now,".LS_ACCESSLOG_BASEDN;
|
|
$new_entry = $data['original_entry']->isNew();
|
|
$attrs = array(
|
|
'reqStart' => array($now),
|
|
'reqEnd' => array($now),
|
|
'reqType' => array($new_entry?"add":"modify"),
|
|
'reqSession' => array("1024"),
|
|
'reqAuthzID' => array(LSsession :: get('authenticated_user_dn')),
|
|
'reqDN' => array($data["dn"]),
|
|
'reqResult' => array("0"),
|
|
);
|
|
|
|
// Compute modifications
|
|
$mods = array();
|
|
$olds = array();
|
|
if ($new_entry)
|
|
foreach(ensureIsArray($data['entry']->getValue('objectClass', 'all')) as $value)
|
|
$mods[] = "objectClass:+ $value";
|
|
foreach($data['changes'] as $attr => $values) {
|
|
if (strtolower($attr) == 'userpassword')
|
|
foreach(array_keys($values) as $idx)
|
|
$values[$idx] = hashPasswordForLogs($values[$idx]);
|
|
if ($values) {
|
|
foreach($values as $value)
|
|
$mods[] = $new_entry?"$attr:+ $value":"$attr:= $value";
|
|
}
|
|
else if (!$new_entry) {
|
|
$mods[] = "$attr:=";
|
|
}
|
|
if (!$new_entry)
|
|
foreach(ensureIsArray($data['original_entry']->getValue($attr, 'all')) as $value)
|
|
$olds[] = "$attr: $value";
|
|
}
|
|
|
|
if (!$mods) return true;
|
|
$attrs['reqMod'] = $mods;
|
|
if ($olds)
|
|
$attrs['reqOld'] = $olds;
|
|
|
|
LSldap::getNewEntry($dn, array($new_entry?'auditAdd':'auditModify'), $attrs, true);
|
|
}
|
|
|
|
function onEntryUserPasswordUpdated($data) {
|
|
$now = LSldap::formatDate();
|
|
$dn = "reqStart=$now,".LS_ACCESSLOG_BASEDN; $mods = array();
|
|
|
|
|
|
// Compute modifications
|
|
$mods = array();
|
|
|
|
// Retreive fresh entry to retreive hased/stored password
|
|
$attrs = LSldap :: getAttrs($data['dn'], null, array('userPassword'));
|
|
$new_passwords = $data["new_passwords"];
|
|
if ($attrs)
|
|
$new_passwords = LSldap :: getAttr($attrs, 'userPassword', true);
|
|
if ($new_passwords) {
|
|
foreach($new_passwords as $password)
|
|
$mods[] = "userPassword:= ".hashPasswordForLogs($password);
|
|
}
|
|
else
|
|
$mods[] = "userPassword:=";
|
|
$attrs = array(
|
|
'reqStart' => array($now),
|
|
'reqEnd' => array($now),
|
|
'reqType' => array("modify"),
|
|
'reqSession' => array("1024"),
|
|
'reqAuthzID' => array(LSsession :: get('authenticated_user_dn')),
|
|
'reqDN' => array($data["dn"]),
|
|
'reqResult' => array("0"),
|
|
'reqMod' => $mods,
|
|
);
|
|
LSldap::getNewEntry($dn, array('auditModify'), $attrs, true);
|
|
}
|
|
|
|
function onEntryMoved($data) {
|
|
$now = LSldap::formatDate();
|
|
$dn = "reqStart=$now,".LS_ACCESSLOG_BASEDN;
|
|
$attrs = array(
|
|
'reqStart' => array($now),
|
|
'reqEnd' => array($now),
|
|
'reqType' => array("modrdn"),
|
|
'reqSession' => array("1024"),
|
|
'reqAuthzID' => array(LSsession :: get('authenticated_user_dn')),
|
|
'reqDN' => array($data["old"]),
|
|
'reqNewRDN' => array(getRdn($data["new"])),
|
|
'reqResult' => array("0"),
|
|
);
|
|
$new_superior = parentDn($data["new"]);
|
|
$old_superior = parentDn($data["old"]);
|
|
if ($new_superior != $old_superior)
|
|
$attrs['reqNewSuperior'] = array($new_superior);
|
|
LSldap::getNewEntry($dn, array('auditModRDN'), $attrs, true);
|
|
}
|
|
|
|
function onEntryDeleted($data) {
|
|
$now = LSldap::formatDate();
|
|
$dn = "reqStart=$now,".LS_ACCESSLOG_BASEDN;
|
|
$attrs = array(
|
|
'reqStart' => array($now),
|
|
'reqEnd' => array($now),
|
|
'reqType' => array("delete"),
|
|
'reqSession' => array("1024"),
|
|
'reqAuthzID' => array(LSsession :: get('authenticated_user_dn')),
|
|
'reqDN' => array($data["dn"]),
|
|
'reqResult' => array("0"),
|
|
);
|
|
LSldap::getNewEntry($dn, array('auditDelete'), $attrs, true);
|
|
}
|
|
|
|
function hashPasswordForLogs($password) {
|
|
if (preg_match('/^{[^}]+}.*/', $password))
|
|
// Already hashed
|
|
return $password;
|
|
if(defined('PASSWORD_ARGON2I'))
|
|
return '{ARGON2}'.password_hash($password, PASSWORD_ARGON2I);
|
|
if(defined('MHASH_SHA512') && function_exists('mhash') && function_exists('mhash_keygen_s2k')) {
|
|
mt_srand( (double) microtime() * 1000000 );
|
|
$salt = mhash_keygen_s2k(MHASH_SHA512, $password, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
|
|
return "{SSHA512}".base64_encode(mhash(MHASH_SHA512, $password.$salt).$salt);
|
|
}
|
|
return '[not logged]';
|
|
}
|
|
if (php_sapi_name() !== 'cli') {
|
|
return true;
|
|
}
|
|
|
|
function cli_getEntryAccessLog($command_args) {
|
|
if (count($command_args) < 1) {
|
|
LSlog::fatal('You must specify entry DN as first parameter');
|
|
}
|
|
$dn = $command_args[0];
|
|
$page = isset($command_args[1]) ? intval($command_args[1]) : 1;
|
|
echo json_encode(
|
|
getEntryAccessLogPage($dn, $page),
|
|
JSON_PRETTY_PRINT
|
|
);
|
|
}
|