File: /var/www/apklausos/application/core/plugins/AuditLog/AuditLog.php
<?php
class AuditLog extends \LimeSurvey\PluginManager\PluginBase
{
protected $storage = 'DbStorage';
protected static $description = 'Core: Create an audit log of changes';
protected static $name = 'auditlog';
/** @inheritdoc this plugin didn't have any public method */
public $allowedPublicMethods = array();
protected $settings = array(
'AuditLog_Log_UserSave' => array(
'type' => 'checkbox',
'label' => 'Log if a user was modified or created',
'default' => '1',
),
'AuditLog_Log_UserLogin' => array(
'type' => 'checkbox',
'label' => 'Log if a user has logged in successfully',
'default' => '1',
),
'AuditLog_Log_UserLogout' => array(
'type' => 'checkbox',
'label' => 'Log if user has logged out',
'default' => '1',
),
'AuditLog_Log_UserFailedLoginAttempt' => array(
'type' => 'checkbox',
'label' => 'Log if a user login has failed',
'default' => '1',
),
'AuditLog_Log_UserDelete' => array(
'type' => 'checkbox',
'label' => 'Log if a user was deleted',
'default' => '1',
),
'AuditLog_Log_DataEntryCreate' => array(
'type' => 'checkbox',
'label' => 'Log if a survey admin creates a response',
'default' => '1',
),
'AuditLog_Log_DataEntryUpdate' => array(
'type' => 'checkbox',
'label' => 'Log if a survey admin modifies a response',
'default' => '1',
),
'AuditLog_Log_DataEntryDelete' => array(
'type' => 'checkbox',
'label' => 'Log if a survey admin delete a response',
'default' => '1',
),
'AuditLog_Log_DataEntryImport' => array(
'type' => 'checkbox',
'label' => 'Log if a survey admin imports responses',
'default' => '1',
),
'AuditLog_Log_TokenSave' => array(
'type' => 'checkbox',
'label' => 'Log if a survey participant was modified or created',
'default' => '1',
),
'AuditLog_Log_TokenDelete' => array(
'type' => 'checkbox',
'label' => 'Log if a survey participant was deleted',
'default' => '1',
),
'AuditLog_Log_ParticipantSave' => array(
'type' => 'checkbox',
'label' => 'Log if a central database participant was modified or created',
'default' => '1',
),
'AuditLog_Log_ParticipantDelete' => array(
'type' => 'checkbox',
'label' => 'Log if a central database participant was deleted',
'default' => '1',
),
'AuditLog_Log_UserPermissionsChanged' => array(
'type' => 'checkbox',
'label' => 'Log if a user permissions changes',
'default' => '1',
),
'AuditLog_Log_SurveySettings' => array(
'type' => 'checkbox',
'label' => 'Log if a user changes survey settings',
'default' => '1',
),
);
public function init()
{
$this->subscribe('beforeSurveySettings');
$this->subscribe('newSurveySettings');
$this->subscribe('beforeSurveySettingsSave');
$this->subscribe('beforeActivate');
$this->subscribe('beforeUserSave');
$this->subscribe('beforeUserDelete');
$this->subscribe('beforePermissionSetSave');
$this->subscribe('beforeDataEntryCreate');
$this->subscribe('beforeDataEntryUpdate');
$this->subscribe('beforeDataEntryDelete');
$this->subscribe('beforeDataEntryImport');
$this->subscribe('beforeTokenSave');
$this->subscribe('beforeTokenDelete');
$this->subscribe('beforeTokenDeleteMany');
$this->subscribe('beforeParticipantSave');
$this->subscribe('beforeParticipantDelete');
$this->subscribe('beforeLogout');
$this->subscribe('afterSuccessfulLogin');
$this->subscribe('afterFailedLoginAttempt');
}
/**
* check for setting for a single operation event, login user, save or delete
* @return boolean
*/
private function checkSetting($settingName)
{
$pluginsettings = $this->getPluginSettings(true);
// Logging will done if setted to true
return $pluginsettings[$settingName]['current'] == 1;
}
/**
* User logout to the audit log
* @return unknown_type
*/
public function beforeLogout()
{
if (!$this->checkSetting('AuditLog_Log_UserLogout')) {
return;
}
$oUser = $this->api->getCurrentUser();
if ($oUser != false) {
$iUserID = $oUser->uid;
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $iUserID;
$oAutoLog->entity = 'user';
$oAutoLog->entityid = $iUserID;
$oAutoLog->action = 'beforeLogout';
$oAutoLog->save();
}
}
/**
* Successfull login to the audit log
* @return unknown_type
*/
public function afterSuccessfulLogin()
{
if (!$this->checkSetting('AuditLog_Log_UserLogin')) {
return;
}
$iUserID = $this->api->getCurrentUser()->uid;
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $iUserID;
$oAutoLog->entity = 'user';
$oAutoLog->entityid = $iUserID;
$oAutoLog->action = 'afterSuccessfulLogin';
$oAutoLog->save();
}
/**
* Failed login attempt to the audit log
* @return unknown_type
*/
public function afterFailedLoginAttempt()
{
if (!$this->checkSetting('AuditLog_Log_UserFailedLoginAttempt')) {
return;
}
$event = $this->getEvent();
$identity = $event->get('identity');
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->entity = 'user';
$oAutoLog->action = 'afterFailedLoginAttempt';
$aUsername['username'] = $identity->username;
$oAutoLog->newvalues = json_encode($aUsername);
$oAutoLog->save();
}
/**
* Saves permissions changes to the audit log
*/
public function beforePermissionSetSave()
{
if (!$this->checkSetting('AuditLog_Log_UserPermissionsChanged')) {
return;
}
$event = $this->getEvent();
$aNewPermissions = $event->get('aNewPermissions');
$iSurveyID = $event->get('iSurveyID');
$iUserID = $event->get('iUserID');
$oCurrentUser = $this->api->getCurrentUser();
$oOldPermission = $this->api->getPermissionSet($iUserID, $iSurveyID, 'Survey');
$sAction = 'update'; // Permissions are in general only updated (either you have a permission or you don't)
if (count(array_diff_assoc_recursive($aNewPermissions, $oOldPermission))) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'permission';
$oAutoLog->entityid = $iSurveyID;
$oAutoLog->action = $sAction;
$oAutoLog->oldvalues = json_encode(array_diff_assoc_recursive($oOldPermission, $aNewPermissions));
$oAutoLog->newvalues = json_encode(array_diff_assoc_recursive($aNewPermissions, $oOldPermission));
$oAutoLog->fields = implode(',', array_keys(array_diff_assoc_recursive($aNewPermissions, $oOldPermission)));
$oAutoLog->save();
}
}
/**
* Function catches if a response was created
* @return unknown_type
*/
public function beforeDataEntryCreate()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_DataEntryCreate') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
$currentUID = $oCurrentUser ? $oCurrentUser->uid : null;
$aValues = $event->get('oModel')->getAttributes();
if (count($aValues)) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $currentUID;
$oAutoLog->entity = 'survey_' . $iSurveyID;
$oAutoLog->action = "create";
$oAutoLog->newvalues = json_encode($aValues);
$oAutoLog->save();
}
}
/**
* Function catches if a response was modified
* @return unknown_type
*/
public function beforeDataEntryUpdate()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_DataEntryUpdate') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
$currentUID = $oCurrentUser ? $oCurrentUser->uid : null;
$oldvalues = $this->api->getResponse($iSurveyID, $event->get('iResponseID'), false);
$aDiffOld = array();
$aDiffNew = array();
foreach ($oldvalues->attributes as $aFieldName => $sValue) {
$oldValue = $sValue;
$newValue = App()->request->getPost($aFieldName);
if ($oldValue != $newValue) {
$aDiffOld[$aFieldName] = $oldValue;
$aDiffNew[$aFieldName] = $newValue;
}
}
if (count($aDiffOld)) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $currentUID;
$oAutoLog->entity = 'survey_' . $iSurveyID;
$oAutoLog->action = "update";
$oAutoLog->entityid = $event->get('iResponseID');
$oAutoLog->oldvalues = json_encode($aDiffOld);
$oAutoLog->newvalues = json_encode($aDiffNew);
$oAutoLog->fields = implode(',', array_keys($aDiffOld));
$oAutoLog->save();
}
}
/**
* Function catches if a response was deleted
* @return unknown_type
*/
public function beforeDataEntryDelete()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_DataEntryDelete') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
$currentUID = $oCurrentUser ? $oCurrentUser->uid : null;
$oldvalues = $this->api->getResponse($iSurveyID, $event->get('iResponseID'), true);
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $currentUID;
$oAutoLog->entity = 'survey_' . $iSurveyID;
$oAutoLog->action = "delete";
$oAutoLog->entityid = $event->get('iResponseID');
$oAutoLog->oldvalues = json_encode($oldvalues);
$oAutoLog->save();
}
/**
* Log import responses
* @return unknown_type
*/
public function beforeDataEntryImport()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_DataEntryImport') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
$currentUID = $oCurrentUser ? $oCurrentUser->uid : null;
$oModel = $this->getEvent()->get('oModel');
$aValues = $oModel->getAttributes();
if (count($aValues)) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $currentUID;
$oAutoLog->entity = 'survey_' . $iSurveyID;
$oAutoLog->action = "import";
$oAutoLog->newvalues = json_encode($aValues);
$oAutoLog->fields = implode(',', array_keys($aValues));
$oAutoLog->save();
}
}
/**
* Function catches if a participant of a particular survey was modified or created
* All data is saved - only the password hash is anonymized for security reasons
*/
public function beforeTokenSave()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_TokenSave') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oNewParticipant = $this->getEvent()->get('model');
$oCurrentUser = $this->api->getCurrentUser();
$currentUID = $oCurrentUser ? $oCurrentUser->uid : null;
if ($oNewParticipant->isNewRecord) {
$sAction = 'create';
$oldvalues = array();
} else {
$sAction = 'update';
$oldvalues = $this->api->getTokenById($iSurveyID, $oNewParticipant->tid)->getAttributes();
}
$newValues = $oNewParticipant->getAttributes();
if (count(array_diff_assoc($newValues, $oldvalues))) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $currentUID;
$oAutoLog->entity = 'token_' . $iSurveyID;
$oAutoLog->action = $sAction;
$oAutoLog->entityid = $newValues['tid'];
$oAutoLog->oldvalues = json_encode(array_diff_assoc($oldvalues, $newValues));
$oAutoLog->newvalues = json_encode(array_diff_assoc($newValues, $oldvalues));
$oAutoLog->fields = implode(',', array_keys(array_diff_assoc($newValues, $oldvalues)));
$oAutoLog->save();
}
}
/**
* Function catches if a participant of a particular survey was deleted
* All data is saved
*/
public function beforeTokenDelete()
{
$event = $this->getEvent();
$iSurveyID = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_TokenDelete') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
// beforeTokenDelete mutated through time.
// At the very begining, the event was dispatched with an sTokenIds parameter.
// Then, dynamic model events were introduced, and this event mutated its interface.
// The code below accepts both kinds of interface.
$sTokenIds = $event->get('sTokenIds');
if (!empty($sTokenIds)) {
$aTokenIds = explode(',', (string) $sTokenIds);
} else {
// If sTokenIds is empty, assume we're dealing with a dynamic model event.
// In this case, the dynamicId parameter contains the token ID.
$aTokenIds = [$event->get('dynamicId')];
}
if (empty($aTokenIds)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
foreach ($aTokenIds as $tokenId) {
$token = Token::model($iSurveyID)->find('tid=' . $tokenId);
if (!is_null($token)) {
$aValues = $token->getAttributes();
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'token';
$oAutoLog->action = 'delete';
$oAutoLog->entityid = $aValues['tid'];
$oAutoLog->oldvalues = json_encode($aValues);
$oAutoLog->fields = implode(',', array_keys($aValues));
$oAutoLog->save();
}
}
}
/**
* Function catches if multiple participants of a particular survey were deleted
* All data is saved
*/
public function beforeTokenDeleteMany()
{
$event = $this->getEvent();
$surveyId = $event->get('iSurveyID');
if (!$this->checkSetting('AuditLog_Log_TokenDelete') || !$this->get('auditing', 'Survey', $surveyId, true)) {
return;
}
$filterCriteria = $event->get('filterCriteria');
// We need to "fix" (update) the criteria given by parameter.
// - SELECT queries are built with the table alias.
// - DELETE queries are not.
// We are given a DELETE query criteria and need to use it on a SELECT query.
// Add the alias.
$tableName = Token::model($surveyId)->getTableSchema()->name;
$filterCriteria->alias = $tableName;
$tokens = Token::model($surveyId)->findAll($filterCriteria);
$oCurrentUser = $this->api->getCurrentUser();
foreach ($tokens as $token) {
$aValues = $token->getAttributes();
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'token';
$oAutoLog->action = 'delete';
$oAutoLog->entityid = $aValues['tid'];
$oAutoLog->oldvalues = json_encode($aValues);
$oAutoLog->fields = implode(',', array_keys($aValues));
$oAutoLog->save();
}
}
/**
* Function catches if a central database participant was modified or created
* All data is saved - only the password hash is anonymized for security reasons
*/
public function beforeParticipantSave()
{
if (!$this->checkSetting('AuditLog_Log_ParticipantSave')) {
return;
}
$oNewParticipant = $this->getEvent()->get('model');
if ($oNewParticipant->isNewRecord) {
$sAction = 'create';
$aOldValues = array();
} else {
$sAction = 'update';
$aOldValues = $this->api->getParticipant($oNewParticipant->participant_id)->getAttributes();
}
$oCurrentUser = $this->api->getCurrentUser();
$aNewValues = $oNewParticipant->getAttributes();
if (count(array_diff_assoc($aNewValues, $aOldValues))) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'participant';
$oAutoLog->action = $sAction;
$oAutoLog->entityid = $aNewValues['participant_id'];
$oAutoLog->oldvalues = json_encode(array_diff_assoc($aOldValues, $aNewValues));
$oAutoLog->newvalues = json_encode(array_diff_assoc($aNewValues, $aOldValues));
$oAutoLog->fields = implode(',', array_keys(array_diff_assoc($aNewValues, $aOldValues)));
$oAutoLog->save();
}
}
/**
* Function catches if a central database participant was modified or created
* All data is saved - only the password hash is anonymized for security reasons
*/
public function beforeParticipantDelete()
{
if (!$this->checkSetting('AuditLog_Log_ParticipantDelete')) {
return;
}
$oNewParticipant = $this->getEvent()->get('model');
$oCurrentUser = $this->api->getCurrentUser();
$aValues = $oNewParticipant->getAttributes();
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'participant';
$oAutoLog->action = 'delete';
$oAutoLog->entityid = $aValues['participant_id'];
$oAutoLog->oldvalues = json_encode($aValues);
$oAutoLog->fields = implode(',', array_keys($aValues));
$oAutoLog->save();
}
/**
* Function catches if a user was modified or created
* All data is saved - only the password hash is anonymized for security reasons
*/
public function beforeUserSave()
{
if (!$this->checkSetting('AuditLog_Log_UserSave')) {
return;
}
$oUserData = $this->getEvent()->get('model');
$oCurrentUser = $this->api->getCurrentUser();
$aNewValues = $oUserData->getAttributes();
if (!isset($oUserData->uid)) {
$sAction = 'create';
$aOldValues = array();
// Indicate the password has changed but assign fake hash
$aNewValues['password'] = '*MASKED*PASSWORD*';
} else {
$oOldUser = $this->api->getUser($oUserData->uid);
$sAction = 'update';
$aOldValues = $oOldUser->getAttributes();
// Postgres delivers bytea fields as streams
if (gettype($aOldValues['password']) == 'resource') {
$aOldValues['password'] = stream_get_contents($aOldValues['password']);
}
// If the password has changed then indicate that it has changed but assign fake hashes
if ($aNewValues['password'] != $aOldValues['password']) {
$aOldValues['password'] = '*MASKED*OLD*PASSWORD*';
$aNewValues['password'] = '*MASKED*NEW*PASSWORD*';
}
}
if (count(array_diff_assoc($aNewValues, $aOldValues))) {
$oAutoLog = $this->api->newModel($this, 'log');
if ($oCurrentUser) {
$oAutoLog->uid = $oCurrentUser->uid;
} else {
$oAutoLog->uid = 'Automatic creation';
}
$oAutoLog->entity = 'user';
if ($sAction == 'update') {
$oAutoLog->entityid = $oOldUser['uid'];
}
$oAutoLog->action = $sAction;
$oAutoLog->oldvalues = json_encode(array_diff_assoc($aOldValues, $aNewValues));
$oAutoLog->newvalues = json_encode(array_diff_assoc($aNewValues, $aOldValues));
$oAutoLog->fields = implode(',', array_keys(array_diff_assoc($aNewValues, $aOldValues)));
$oAutoLog->save();
}
}
/**
* Function catches if a user was deleted
* All data is saved - only the password hash is anonymized for security reasons
*/
public function beforeUserDelete()
{
if (!$this->checkSetting('AuditLog_Log_UserDelete')) {
return;
}
$oUserData = $this->getEvent()->get('model');
$oCurrentUser = $this->api->getCurrentUser();
$oOldUser = $this->api->getUser($oUserData->uid);
if ($oOldUser) {
$aOldValues = $oOldUser->getAttributes();
unset($aOldValues['password']);
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'user';
$oAutoLog->entityid = $oOldUser['uid'];
$oAutoLog->action = 'delete';
$oAutoLog->oldvalues = json_encode($aOldValues);
$oAutoLog->fields = implode(',', array_keys($aOldValues));
$oAutoLog->save();
}
}
public function beforeActivate()
{
if (!$this->api->tableExists($this, 'log')) {
$options = '';
if (Yii::app()->db->driverName == 'mysqli' || Yii::app()->db->driverName == 'mysql') {
$options .= sprintf(" ENGINE = %s ", Yii::app()->getConfig('mysqlEngine'));
}
$this->api->createTable($this, 'log', array('id' => 'pk',
'created' => 'datetime',
'uid' => 'string',
'entity' => 'string',
'entityid' => 'string',
'action' => 'string',
'fields' => 'text',
'oldvalues' => 'text',
'newvalues' => 'text'), $options);
}
}
/**
* This event is fired by the administration panel to gather extra settings
* available for a survey.
* The plugin should return setting meta data.
*/
public function beforeSurveySettings()
{
$pluginsettings = $this->getPluginSettings(true);
$event = $this->getEvent();
$event->set("surveysettings.{$this->id}", array(
'name' => get_class($this),
'settings' => array(
'auditing' => array(
'type' => 'select',
'options' => array(0 => 'No',
1 => 'Yes'),
'default' => 1,
'tab' => 'notification', // @todo: Setting no used yet
'category' => 'Auditing for person-related data', // @todo: Setting no used yet
'label' => 'Audit log for this survey:',
'current' => $this->get('auditing', 'Survey', $event->get('survey'))
)
)
));
}
public function newSurveySettings()
{
$event = $this->getEvent();
foreach ($event->get('settings') as $name => $value) {
$this->set($name, $value, 'Survey', $event->get('survey'));
}
}
public function beforeSurveySettingsSave()
{
$event = $this->getEvent();
$oModifiedSurvey = $event->get('modifiedSurvey');
$iSurveyID = $oModifiedSurvey->sid;
if (!$this->checkSetting('AuditLog_Log_SurveySettings') || !$this->get('auditing', 'Survey', $iSurveyID, true)) {
return;
}
$oCurrentUser = $this->api->getCurrentUser();
if (!is_null($oModifiedSurvey)) {
$newAttributes = $oModifiedSurvey->getAttributes();
$oldSurvey = Survey::model()->find('sid = :sid', array(':sid' => $iSurveyID));
$oldAttributes = $oldSurvey->getAttributes();
$diff = array_diff_assoc($newAttributes, $oldAttributes);
if (count($diff) > 0) {
$oAutoLog = $this->api->newModel($this, 'log');
$oAutoLog->uid = $oCurrentUser->uid;
$oAutoLog->entity = 'survey';
$oAutoLog->entityid = $iSurveyID;
$oAutoLog->action = 'update';
$oAutoLog->oldvalues = json_encode(array_diff_assoc($oldAttributes, $newAttributes));
$oAutoLog->newvalues = json_encode($diff);
#$oAutoLog->fields=json_encode($diff);
$oAutoLog->fields = implode(',', array_keys($diff));
$oAutoLog->save();
}
}
}
}