mirror of https://github.com/MISP/MISP
384 lines
13 KiB
PHP
384 lines
13 KiB
PHP
<?php
|
|
App::uses('AuditLog', 'Model');
|
|
|
|
class AuditLogBehavior extends ModelBehavior
|
|
{
|
|
/** @var array|null */
|
|
private $old;
|
|
|
|
/** @var AuditLog|null */
|
|
private $AuditLog;
|
|
|
|
/** @var bool */
|
|
private $enabled;
|
|
|
|
// Hash is faster that in_array
|
|
private $skipFields = [
|
|
'id' => true,
|
|
'lastpushedid' => true,
|
|
'timestamp' => true,
|
|
'revision' => true,
|
|
'modified' => true,
|
|
'date_modified' => true, // User
|
|
'current_login' => true, // User
|
|
'last_login' => true, // User
|
|
'newsread' => true, // User
|
|
'proposal_email_lock' => true, // Event
|
|
];
|
|
|
|
private $modelInfo = [
|
|
'Event' => 'info',
|
|
'User' => 'email',
|
|
'Object' => 'name',
|
|
'EventReport' => 'name',
|
|
'Server' => 'name',
|
|
'Feed' => 'name',
|
|
'Role' => 'name',
|
|
'SharingGroup' => 'name',
|
|
'Tag' => 'name',
|
|
'TagCollection' => 'name',
|
|
'Taxonomy' => 'namespace',
|
|
'Organisation' => 'name',
|
|
'SystemSetting' => 'setting',
|
|
'AdminSetting' => 'setting',
|
|
'UserSetting' => 'setting',
|
|
'Galaxy' => 'name',
|
|
'GalaxyCluster' => 'value',
|
|
'Warninglist' => 'name',
|
|
];
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->enabled = Configure::read('MISP.log_new_audit');
|
|
}
|
|
|
|
public function setup(Model $model, $config = [])
|
|
{
|
|
// Generate model info for attribute and proposals
|
|
$attributeInfo = function (array $new, array $old) {
|
|
$category = isset($new['category']) ? $new['category'] : $old['category'];
|
|
$type = isset($new['type']) ? $new['type'] : $old['type'];
|
|
$value1 = trim(isset($new['value1']) ? $new['value1'] : $old['value1']);
|
|
$value2 = trim(isset($new['value2']) ? $new['value2'] : $old['value2']);
|
|
$value = $value1 . (empty($value2) ? '' : '|' . $value2);
|
|
return "$category/$type $value";
|
|
};
|
|
$this->modelInfo['Attribute'] = $attributeInfo;
|
|
$this->modelInfo['ShadowAttribute'] = $attributeInfo;
|
|
$this->modelInfo['AuthKey'] = function (array $new, array $old) {
|
|
$start = isset($new['authkey_start']) ? $new['authkey_start'] : $old['authkey_start'];
|
|
$end = isset($new['authkey_end']) ? $new['authkey_end'] : $old['authkey_end'];
|
|
return "$start********************************$end";
|
|
};
|
|
}
|
|
|
|
public function beforeSave(Model $model, $options = [])
|
|
{
|
|
if (!$this->enabled) {
|
|
return true;
|
|
}
|
|
|
|
// Do not fetch old version when just few fields will be fetched
|
|
$fieldToFetch = [];
|
|
if (!empty($options['fieldList'])) {
|
|
foreach ($options['fieldList'] as $field) {
|
|
if (!isset($this->skipFields[$field])) {
|
|
$fieldToFetch[] = $field;
|
|
}
|
|
}
|
|
|
|
// For objects, that are assigned to event, we need to know event ID. So if data to save doesn't contain
|
|
// that ID, we need to fetch it from database.
|
|
if (isset($model->schema()['event_id']) && empty($model->data[$model->alias]['event_id']) && !in_array('event_id', $fieldToFetch, true)) {
|
|
$fieldToFetch[] = 'event_id';
|
|
}
|
|
|
|
if (empty($fieldToFetch)) {
|
|
$this->old = null;
|
|
return true;
|
|
}
|
|
}
|
|
if ($model->id) {
|
|
$this->old = $model->find('first', [
|
|
'conditions' => [$model->alias . '.' . $model->primaryKey => $model->id],
|
|
'recursive' => -1,
|
|
'callbacks' => false,
|
|
'fields' => $fieldToFetch,
|
|
]);
|
|
} else {
|
|
$this->old = null;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function afterSave(Model $model, $created, $options = [])
|
|
{
|
|
if (!$this->enabled) {
|
|
return;
|
|
}
|
|
|
|
$id = $model->id ?: 0;
|
|
$data = $model->data[$model->alias];
|
|
|
|
if ($created) {
|
|
$action = AuditLog::ACTION_ADD;
|
|
} else {
|
|
$action = AuditLog::ACTION_EDIT;
|
|
if (isset($data['deleted'])) {
|
|
if ($data['deleted']) {
|
|
$action = AuditLog::ACTION_SOFT_DELETE;
|
|
} else if (isset($this->old[$model->alias]['deleted']) && $this->old[$model->alias]['deleted']) {
|
|
$action = AuditLog::ACTION_UNDELETE;
|
|
}
|
|
}
|
|
}
|
|
|
|
$changedFields = $this->changedFields($model, $options['fieldList']);
|
|
if (empty($changedFields)) {
|
|
return;
|
|
}
|
|
|
|
if ($model->name === 'Event') {
|
|
$eventId = $id;
|
|
} else if (isset($data['event_id'])) {
|
|
$eventId = $data['event_id'];
|
|
} else if (isset($this->old[$model->alias]['event_id'])) {
|
|
$eventId = $this->old[$model->alias]['event_id'];
|
|
} else {
|
|
$eventId = null;
|
|
}
|
|
|
|
$modelTitle = null;
|
|
if (isset($this->modelInfo[$model->name])) {
|
|
$modelTitleField = $this->modelInfo[$model->name];
|
|
if (is_callable($modelTitleField)) {
|
|
$modelTitle = $modelTitleField($data, isset($this->old[$model->alias]) ? $this->old[$model->alias] : []);
|
|
} else if (isset($data[$modelTitleField])) {
|
|
$modelTitle = $data[$modelTitleField];
|
|
} else if ($this->old[$model->alias][$modelTitleField]) {
|
|
$modelTitle = $this->old[$model->alias][$modelTitleField];
|
|
}
|
|
}
|
|
|
|
$modelName = $model->name === 'MispObject' ? 'Object' : $model->name;
|
|
|
|
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
|
|
$isLocal = isset($data['local']) ? $data['local'] : false;
|
|
$action = $isLocal ? AuditLog::ACTION_TAG_LOCAL : AuditLog::ACTION_TAG;
|
|
$tagInfo = $this->getTagInfo($model, $data['tag_id']);
|
|
if ($tagInfo) {
|
|
$modelTitle = $tagInfo['tag_name'];
|
|
if ($tagInfo['is_galaxy']) {
|
|
$action = $isLocal ? AuditLog::ACTION_GALAXY_LOCAL : AuditLog::ACTION_GALAXY;
|
|
if ($tagInfo['galaxy_cluster_name']) {
|
|
$modelTitle = $tagInfo['galaxy_cluster_name'];
|
|
}
|
|
}
|
|
}
|
|
$id = $modelName === 'AttributeTag' ? $data['attribute_id'] : $data['event_id'];
|
|
$modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event';
|
|
} else if ($modelName === 'Event') {
|
|
if (isset($changedFields['published'][1]) && $changedFields['published'][1]) {
|
|
$action = AuditLog::ACTION_PUBLISH;
|
|
} else if (isset($changedFields['sighting_timestamp'][1]) && $changedFields['sighting_timestamp'][1]) {
|
|
$action = AuditLog::ACTION_PUBLISH_SIGHTINGS;
|
|
}
|
|
} else if ($modelName === 'SystemSetting') {
|
|
$id = 0;
|
|
}
|
|
|
|
$this->auditLog()->insert(['AuditLog' => [
|
|
'action' => $action,
|
|
'model' => $modelName,
|
|
'model_id' => $id,
|
|
'model_title' => $modelTitle,
|
|
'event_id' => $eventId,
|
|
'change' => $changedFields,
|
|
]]);
|
|
}
|
|
|
|
public function beforeDelete(Model $model, $cascade = true)
|
|
{
|
|
if (!$this->enabled) {
|
|
return true;
|
|
}
|
|
|
|
$this->old = $model->find('first', [
|
|
'conditions' => array($model->alias . '.' . $model->primaryKey => $model->id),
|
|
'recursive' => -1,
|
|
'callbacks' => false,
|
|
]);
|
|
return true;
|
|
}
|
|
|
|
public function afterDelete(Model $model)
|
|
{
|
|
if (!$this->enabled) {
|
|
return;
|
|
}
|
|
$model->data = $this->old;
|
|
$this->old = null;
|
|
if ($model->name === 'Event') {
|
|
$eventId = $model->id;
|
|
} else {
|
|
$eventId = isset($model->data[$model->alias]['event_id']) ? $model->data[$model->alias]['event_id'] : null;
|
|
}
|
|
|
|
$modelTitle = null;
|
|
if (isset($this->modelInfo[$model->name])) {
|
|
$modelTitleField = $this->modelInfo[$model->name];
|
|
if (is_callable($modelTitleField)) {
|
|
$modelTitle = $modelTitleField($model->data[$model->alias], []);
|
|
} else if (isset($model->data[$model->alias][$modelTitleField])) {
|
|
$modelTitle = $model->data[$model->alias][$modelTitleField];
|
|
}
|
|
}
|
|
|
|
$modelName = $model->name === 'MispObject' ? 'Object' : $model->name;
|
|
$action = AuditLog::ACTION_DELETE;
|
|
$id = $model->id;
|
|
|
|
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
|
|
$isLocal = isset($model->data[$model->alias]['local']) ? $model->data[$model->alias]['local'] : false;
|
|
$action = $isLocal ? AuditLog::ACTION_REMOVE_TAG_LOCAL : AuditLog::ACTION_REMOVE_TAG;
|
|
$tagInfo = $this->getTagInfo($model, $model->data[$model->alias]['tag_id']);
|
|
if ($tagInfo) {
|
|
$modelTitle = $tagInfo['tag_name'];
|
|
if ($tagInfo['is_galaxy']) {
|
|
$action = $isLocal ? AuditLog::ACTION_REMOVE_GALAXY_LOCAL : AuditLog::ACTION_REMOVE_GALAXY;
|
|
if ($tagInfo['galaxy_cluster_name']) {
|
|
$modelTitle = $tagInfo['galaxy_cluster_name'];
|
|
}
|
|
}
|
|
}
|
|
$id = $modelName === 'AttributeTag' ? $model->data[$model->alias]['attribute_id'] : $model->data[$model->alias]['event_id'];
|
|
$modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event';
|
|
} else if ($modelName === 'SystemSetting') {
|
|
$id = 0;
|
|
}
|
|
|
|
$this->auditLog()->insert(['AuditLog' => [
|
|
'action' => $action,
|
|
'model' => $modelName,
|
|
'model_id' => $id,
|
|
'model_title' => $modelTitle,
|
|
'event_id' => $eventId,
|
|
'change' => $this->changedFields($model),
|
|
]]);
|
|
}
|
|
|
|
/**
|
|
* @param Model $model
|
|
* @param int $tagId
|
|
* @return array|null
|
|
*/
|
|
private function getTagInfo(Model $model, $tagId)
|
|
{
|
|
$tag = $model->Tag->find('first', [
|
|
'conditions' => ['Tag.id' => $tagId],
|
|
'recursive' => -1,
|
|
'fields' => ['Tag.name', 'Tag.is_galaxy'],
|
|
'callbacks' => false, // disable Tag::afterFind callback
|
|
]);
|
|
if (empty($tag)) {
|
|
return null;
|
|
}
|
|
|
|
$galaxyClusterName = null;
|
|
if ($tag['Tag']['is_galaxy']) {
|
|
if (!isset($this->GalaxyCluster)) {
|
|
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
|
|
}
|
|
$galaxyCluster = $this->GalaxyCluster->find('first', [
|
|
'conditions' => ['GalaxyCluster.tag_name' => $tag['Tag']['name']],
|
|
'recursive' => -1,
|
|
'fields' => ['GalaxyCluster.value'],
|
|
]);
|
|
if (!empty($galaxyCluster)) {
|
|
$galaxyClusterName = $galaxyCluster['GalaxyCluster']['value'];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'tag_name' => $tag['Tag']['name'],
|
|
'is_galaxy' => $tag['Tag']['is_galaxy'],
|
|
'galaxy_cluster_name' => $galaxyClusterName,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param Model $model
|
|
* @param array|null $fieldsToSave
|
|
* @return array
|
|
*/
|
|
private function changedFields(Model $model, $fieldsToSave = null)
|
|
{
|
|
$dbFields = $model->schema();
|
|
$changedFields = [];
|
|
$hasPrimaryField = isset($model->data[$model->alias][$model->primaryKey]);
|
|
foreach ($model->data[$model->alias] as $key => $value) {
|
|
if (isset($this->skipFields[$key])) {
|
|
continue;
|
|
}
|
|
if (!isset($dbFields[$key])) {
|
|
continue;
|
|
}
|
|
if ($fieldsToSave && !in_array($key, $fieldsToSave, true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($hasPrimaryField && isset($this->old[$model->alias][$key])) {
|
|
$old = $this->old[$model->alias][$key];
|
|
} else {
|
|
$old = null;
|
|
}
|
|
|
|
// Normalize
|
|
if (is_bool($old)) {
|
|
$old = $old ? 1 : 0;
|
|
}
|
|
if (is_bool($value)) {
|
|
$value = $value ? 1 : 0;
|
|
}
|
|
$dbType = $dbFields[$key]['type'];
|
|
if ($dbType === 'integer' || $dbType === 'tinyinteger' || $dbType === 'biginteger' || $dbType === 'boolean') {
|
|
$value = (int)$value;
|
|
if ($old !== null) {
|
|
$old = (int)$old;
|
|
}
|
|
}
|
|
|
|
if ($value == $old) {
|
|
continue;
|
|
}
|
|
|
|
if ($key === 'password' || $key === 'authkey' || ($key === 'value' && $model->name === 'SystemSetting' && SystemSetting::isSensitive($model->data[$model->alias]['setting']))) {
|
|
$value = '*****';
|
|
if ($old !== null) {
|
|
$old = $value;
|
|
}
|
|
}
|
|
|
|
if ($old === null) {
|
|
$changedFields[$key] = $value;
|
|
} else {
|
|
$changedFields[$key] = [$old, $value];
|
|
}
|
|
}
|
|
|
|
return $changedFields;
|
|
}
|
|
|
|
/**
|
|
* @return AuditLog
|
|
*/
|
|
private function auditLog()
|
|
{
|
|
if ($this->AuditLog === null) {
|
|
$this->AuditLog = ClassRegistry::init('AuditLog');
|
|
}
|
|
return $this->AuditLog;
|
|
}
|
|
}
|