mirror of
https://gitlab.easter-eggs.com/ee/ldapsaisie.git
synced 2024-11-22 09:59:06 +01:00
LSaddon accesslog: global improvments and add self logging feature
This commit is contained in:
parent
80a50f98f1
commit
13d83dbf75
4 changed files with 286 additions and 69 deletions
|
@ -23,3 +23,14 @@
|
|||
// Accesslog base DN
|
||||
define('LS_ACCESSLOG_BASEDN', 'cn=ldapsaisie-accesslog');
|
||||
|
||||
/*
|
||||
* Enable logging write events on LDAP entries managed by LdapSaisie in the accesslog base
|
||||
*
|
||||
* The feature permit to LdapSaisie to write it self log entries in accesslog base about
|
||||
* modifications made with its system account with the right author DN.
|
||||
*
|
||||
* Note: If your LDAP directory ACL permit to your users to do them self modifications on LDAP
|
||||
* directory, the recommanded way is to use authz proxy authentication (see useAuthzProxyControl
|
||||
* configuration parameter).
|
||||
*/
|
||||
define('LS_ACCESSLOG_LOG_WRITE_EVENTS', false);
|
||||
|
|
36
src/css/default/showObjectAccessLogs.css
Normal file
36
src/css/default/showObjectAccessLogs.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
table.objectAccessLogs tbody tr {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
table.objectAccessLogs table.mods {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.objectAccessLogs table.mods td, table.objectAccessLogs table.mods th {
|
||||
border-left: 1px dotted;
|
||||
}
|
||||
|
||||
table.objectAccessLogs table.mods tr {
|
||||
border: none!important;
|
||||
}
|
||||
|
||||
table.objectAccessLogs .col-attr {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
table.objectAccessLogs .col-op {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
table.objectAccessLogs .col-value {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
table.objectAccessLogs .col-value div {
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -25,30 +25,37 @@ LSerror :: defineError('ACCESSLOG_SUPPORT_01',
|
|||
);
|
||||
|
||||
$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'),
|
||||
'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'),
|
||||
'+' => ___('Add'),
|
||||
'-' => ___('Delete'),
|
||||
'=' => ___('Replace'),
|
||||
'' => ___('Replace'),
|
||||
'#' => ___('Increment'),
|
||||
);
|
||||
|
||||
function LSaddon_accesslog_support() {
|
||||
if (!defined('LS_ACCESSLOG_BASEDN')) {
|
||||
LSerror :: addErrorCode('ACCESSLOG_SUPPORT_01', 'LS_ACCESSLOG_BASEDN');
|
||||
$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',
|
||||
|
@ -57,10 +64,18 @@ function LSaddon_accesslog_support() {
|
|||
'[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'));
|
||||
|
@ -73,21 +88,23 @@ function mapAccessLogEntry(&$entry) {
|
|||
$entry['type'] = LSldap::getAttr($attrs, 'reqType');
|
||||
$entry['result'] = ldap_err2str(LSldap::getAttr($attrs, 'reqResult'));
|
||||
$entry['message'] = LSldap::getAttr($attrs, 'reqMessage');
|
||||
if ($entry['type'] === 'modify' && LSldap::getAttr($attrs, 'reqMod', true)) {
|
||||
$mods = array();
|
||||
foreach(LSldap::getAttr($attrs, 'reqMod', true) as $mod) {
|
||||
if (preg_match('/^([^\:]+)\:([^ ]?) (.*)$/', $mod, $m)) {
|
||||
$attr = $m[1];
|
||||
$op = $m[2];
|
||||
$value = $m[3];
|
||||
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(
|
||||
'mods' => array(),
|
||||
'changes' => array(),
|
||||
'old_values' => array(),
|
||||
);
|
||||
}
|
||||
$mods[$attr]['changes'][] = array(
|
||||
'op' => array_key_exists($op, $GLOBALS['accesslog_modOps']) ? $GLOBALS['accesslog_modOps'][$op] : $op,
|
||||
'op' => (
|
||||
array_key_exists($op, $GLOBALS['accesslog_modOps'])?
|
||||
_($GLOBALS['accesslog_modOps'][$op]): $op
|
||||
),
|
||||
'value' => $value,
|
||||
);
|
||||
}
|
||||
|
@ -99,23 +116,39 @@ function mapAccessLogEntry(&$entry) {
|
|||
}
|
||||
}
|
||||
}
|
||||
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']];
|
||||
$entry['type'] = _($GLOBALS['accesslog_reqTypes'][$entry['type']]);
|
||||
}
|
||||
}
|
||||
|
||||
function sortLogEntryByDate($a, $b) {
|
||||
return ($a['start'] === $b['start']) ? 0 : ($a['start'] < $b['start']) ? -1 : 1;
|
||||
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) {
|
||||
$data = LSldap::search(
|
||||
Net_LDAP2_Filter::create('reqDn', 'equals', $dn),
|
||||
function getEntryAccessLog($dn, $start_date=null) {
|
||||
$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));
|
||||
}
|
||||
$entries = LSldap::search(
|
||||
$filter,
|
||||
LS_ACCESSLOG_BASEDN,
|
||||
array(
|
||||
'attributes' => array(
|
||||
'reqDN',
|
||||
'reqStart',
|
||||
'reqEnd',
|
||||
'reqAuthzID',
|
||||
|
@ -124,22 +157,33 @@ function getEntryAccessLog($dn) {
|
|||
'reqMessage',
|
||||
'reqMod',
|
||||
'reqOld',
|
||||
'reqNewRDN',
|
||||
'reqNewSuperior',
|
||||
),
|
||||
)
|
||||
);
|
||||
if (!is_array($data)) {
|
||||
if (!is_array($entries)) {
|
||||
return;
|
||||
}
|
||||
usort($entries, 'sortLogEntriesByDate');
|
||||
$logs = array();
|
||||
foreach($data as $entry) {
|
||||
foreach($entry['attrs'] as $attr => $values) {
|
||||
$entry['attrs'][$attr] = ensureIsArray($values);
|
||||
}
|
||||
$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;
|
||||
}
|
||||
usort($logs, 'sortLogEntryByDate');
|
||||
return array_reverse($logs);
|
||||
}
|
||||
if ($new_dn) {
|
||||
$next_logs = getEntryAccessLog($new_dn, $rename_date);
|
||||
if (is_array($next_logs))
|
||||
$logs = array_merge($logs, $next_logs);
|
||||
}
|
||||
return $start_date?$logs:array_reverse($logs);
|
||||
}
|
||||
|
||||
function getEntryAccessLogPage($dn, $page = false, $nbByPage = 30) {
|
||||
|
@ -183,11 +227,136 @@ function showObjectAccessLogs($obj) {
|
|||
'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_type, $password.$salt).$salt);
|
||||
}
|
||||
return '[not logged]';
|
||||
}
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<h1>{$pagetitle}</h1>
|
||||
{include file='ls:LSview_actions.tpl'}
|
||||
|
||||
<table class='LStable'>
|
||||
<table class='LStable objectAccessLogs'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{tr msg="Date"}</th>
|
||||
|
@ -22,23 +22,23 @@
|
|||
<td class="center">{$log.result}{if $log.message} <img class='LStips' src="{img name='help'}" alt="?" title='{$log.message|escape:quotes}'/>{/if}</td>
|
||||
<td>
|
||||
{if $log.mods}
|
||||
<table style='margin: auto'>
|
||||
<table class="mods">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{tr msg="Attribute"}</th>
|
||||
<th>{tr msg="Operation"}</th>
|
||||
<th>{tr msg="Value"}</th>
|
||||
<th>{tr msg="Old value(s)"}</th>
|
||||
<th class="col-op">{tr msg="Operation"}</th>
|
||||
<th class="col-attr">{tr msg="Attribute"}</th>
|
||||
<th class="col-value">{tr msg="Old value(s)"}</th>
|
||||
<th class="col-value">{tr msg="Value"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $log.mods as $attr => $info}
|
||||
<tr>
|
||||
<td class="center" {if count($info.changes)>1}rowspan={$info.changes|count}{/if}>{$attr}</td>
|
||||
<td class="center">{$info.changes.0.op|escape:htmlall}</td>
|
||||
<td>{$info.changes.0.value|escape:htmlall}</td>
|
||||
<td {if count($info.changes)>1}rowspan={$info.changes|count}{/if}>
|
||||
<td class="col-op center" {if count($info.changes)>1}rowspan={$info.changes|count}{/if}>{$info.changes.0.op|escape:htmlall}</td>
|
||||
<td class="col-attr center" {if count($info.changes)>1}rowspan={$info.changes|count}{/if}>{$attr}</td>
|
||||
<td class="col-value" {if count($info.changes)>1}rowspan={$info.changes|count}{/if}>
|
||||
{if $info.old_values}
|
||||
<div class="copyable copyable-no-btn">
|
||||
{if count($info.old_values) == 1}
|
||||
{$info.old_values[0]|escape:'htmlall'}
|
||||
{else}
|
||||
|
@ -48,14 +48,15 @@
|
|||
{/foreach}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="col-value"><div class="copyable copyable-no-btn">{$info.changes.0.value|escape:htmlall}</div></td>
|
||||
</tr>
|
||||
{if count($info.changes) > 1}
|
||||
{section name=change loop=$info.changes step=1 start=1}
|
||||
<tr>
|
||||
<td>{$info.changes[change].op|escape:htmlall}</td>
|
||||
<td>{$info.changes[change].value|escape:htmlall}</td>
|
||||
<td class="col-value"><div class="copyable copyable-no-btn">{$info.changes[change].value|escape:htmlall}</div></td>
|
||||
</tr>
|
||||
{/section}
|
||||
{/if}
|
||||
|
|
Loading…
Reference in a new issue