___('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[^\:]+)\:(?P[^ ]?)( (?P.*))?$/', $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 ); }