2017-06-13 12:08:26 +02:00
< ? php
App :: uses ( 'AppModel' , 'Model' );
2020-06-19 16:21:53 +02:00
App :: uses ( 'TmpFileTool' , 'Tools' );
2021-10-28 10:16:20 +02:00
App :: uses ( 'AttributeValidationTool' , 'Tools' );
2021-10-30 20:32:04 +02:00
App :: uses ( 'FileAccessTool' , 'Tools' );
2017-06-13 12:08:26 +02:00
2020-07-20 10:10:47 +02:00
/**
* @ property Event $Event
2020-07-24 21:53:28 +02:00
* @ property SharingGroup $SharingGroup
2020-09-20 18:46:38 +02:00
* @ property Attribute $Attribute
2021-06-29 13:38:33 +02:00
* @ property ObjectReference $ObjectReference
2021-10-09 17:28:44 +02:00
* @ property ObjectTemplate $ObjectTemplate
2020-07-20 10:10:47 +02:00
*/
2018-07-19 11:48:22 +02:00
class MispObject extends AppModel
{
public $name = 'Object' ;
public $alias = 'Object' ;
2017-07-05 14:25:09 +02:00
2018-07-19 11:48:22 +02:00
public $useTable = 'objects' ;
2017-07-06 15:04:01 +02:00
2018-07-19 11:48:22 +02:00
public $actsAs = array (
2021-01-22 13:01:23 +01:00
'AuditLog' ,
2024-01-28 18:12:05 +01:00
'Containable' ,
'SysLogLogable.SysLogLogable' => array ( // TODO Audit, logable
'userModel' => 'User' ,
'userKey' => 'user_id' ,
'change' => 'full' ),
'AnalystDataParent'
2018-07-19 11:48:22 +02:00
);
2017-07-05 14:25:09 +02:00
2018-07-19 11:48:22 +02:00
public $belongsTo = array (
'Event' => array (
'className' => 'Event' ,
'foreignKey' => 'event_id'
),
'SharingGroup' => array (
'className' => 'SharingGroup' ,
'foreignKey' => 'sharing_group_id'
),
'ObjectTemplate' => array (
'className' => 'ObjectTemplate' ,
'foreignKey' => false ,
'dependent' => false ,
'conditions' => array ( 'MispObject.template_uuid' => 'ObjectTemplate.uuid' )
)
);
2017-06-13 12:08:26 +02:00
2018-07-19 11:48:22 +02:00
public $hasMany = array (
'Attribute' => array (
'className' => 'Attribute' ,
'dependent' => true ,
),
'ObjectReference' => array (
'className' => 'ObjectReference' ,
'dependent' => true ,
'foreignKey' => 'object_id'
),
);
2017-07-05 09:02:16 +02:00
2020-02-29 08:57:32 +01:00
public $validFormats = array (
'json' => array ( 'json' , 'JsonExport' , 'json' )
);
2019-06-13 09:16:34 +02:00
public $shortDist = array ( 0 => 'Organisation' , 1 => 'Community' , 2 => 'Connected' , 3 => 'All' , 4 => ' Sharing Group' , 5 => 'Inherit' );
2018-07-19 11:48:22 +02:00
public $validate = array (
2018-11-23 14:11:33 +01:00
'uuid' => array (
'uuid' => array (
2020-09-01 19:05:06 +02:00
'rule' => 'uuid' ,
2020-09-18 10:55:52 +02:00
'message' => 'Please provide a valid RFC 4122 UUID'
2018-11-23 14:11:33 +01:00
),
'unique' => array (
'rule' => 'isUnique' ,
'message' => 'The UUID provided is not unique' ,
2021-09-07 08:47:43 +02:00
'on' => 'create'
2020-01-27 16:29:28 +01:00
),
),
2021-09-29 14:54:08 +02:00
'event_id' => [ 'numeric' ],
2020-01-27 16:29:28 +01:00
'first_seen' => array (
'rule' => array ( 'datetimeOrNull' ),
'required' => false ,
'message' => array ( 'Invalid ISO 8601 format' )
),
'last_seen' => array (
2021-05-06 15:01:58 +02:00
'datetimeOrNull' => array (
'rule' => array ( 'datetimeOrNull' ),
'required' => false ,
'message' => array ( 'Invalid ISO 8601 format' )
),
'validateLastSeenValue' => array (
'rule' => array ( 'validateLastSeenValue' ),
'required' => false ,
'message' => array ( 'Last seen value should be greater than first seen value' )
),
2021-05-06 15:53:57 +02:00
),
'name' => array (
'stringNotEmpty' => array (
2021-09-07 08:47:43 +02:00
'rule' => array ( 'stringNotEmpty' ),
'required' => true ,
'on' => 'create'
2021-05-06 15:53:57 +02:00
),
),
'meta-category' => array (
'stringNotEmpty' => array (
2021-09-07 08:47:43 +02:00
'rule' => array ( 'stringNotEmpty' ),
'required' => true ,
'on' => 'create'
2021-05-06 15:53:57 +02:00
),
),
'description' => array (
'stringNotEmpty' => array (
2023-09-01 14:57:01 +02:00
'rule' => array ( 'stringNotEmpty' ),
'on' => 'create'
2021-05-06 15:53:57 +02:00
),
),
'template_uuid' => array (
'uuid' => array (
'rule' => 'uuid' ,
2021-09-07 08:47:43 +02:00
'message' => 'Please provide a valid RFC 4122 UUID' ,
'required' => true ,
'on' => 'create'
2021-05-06 15:53:57 +02:00
),
),
'template_version' => array (
'numeric' => array (
'rule' => 'naturalNumber' ,
2021-09-07 08:47:43 +02:00
'required' => true ,
'on' => 'create'
2021-05-06 15:53:57 +02:00
)
),
2018-07-19 11:48:22 +02:00
);
2017-06-13 12:08:26 +02:00
2021-01-20 15:10:16 +01:00
private $__objectDuplicationCheckCache = [];
2020-09-20 18:18:46 +02:00
public function buildFilterConditions ( & $params )
2020-02-29 08:57:32 +01:00
{
2020-09-20 18:18:46 +02:00
$conditions = [];
2020-02-29 08:57:32 +01:00
if ( isset ( $params [ 'wildcard' ])) {
$temp = array ();
$options = array (
'filter' => 'wildcard' ,
'scope' => 'Object' ,
'pop' => false ,
'context' => 'Event'
);
$conditions [ 'AND' ][] = array ( 'OR' => $this -> Event -> set_filter_wildcard_attributes ( $params , $temp , $options ));
} else {
if ( isset ( $params [ 'ignore' ])) {
$params [ 'to_ids' ] = array ( 0 , 1 );
$params [ 'published' ] = array ( 0 , 1 );
}
$simple_params = array (
'Object' => array (
'object_name' => array ( 'function' => 'set_filter_object_name' ),
'object_template_uuid' => array ( 'function' => 'set_filter_object_template_uuid' ),
'object_template_version' => array ( 'function' => 'set_filter_object_template_version' ),
2024-01-19 18:11:28 +01:00
'first_seen' => array ( 'function' => 'set_filter_seen' ),
'last_seen' => array ( 'function' => 'set_filter_seen' ),
2020-02-29 08:57:32 +01:00
'deleted' => array ( 'function' => 'set_filter_deleted' )
),
'Event' => array (
'eventid' => array ( 'function' => 'set_filter_eventid' ),
'eventinfo' => array ( 'function' => 'set_filter_eventinfo' ),
'ignore' => array ( 'function' => 'set_filter_ignore' ),
'from' => array ( 'function' => 'set_filter_timestamp' ),
'to' => array ( 'function' => 'set_filter_timestamp' ),
'date' => array ( 'function' => 'set_filter_date' ),
'tags' => array ( 'function' => 'set_filter_tags' ),
'last' => array ( 'function' => 'set_filter_timestamp' , 'pop' => true ),
'timestamp' => array ( 'function' => 'set_filter_timestamp' , 'pop' => true ),
'event_timestamp' => array ( 'function' => 'set_filter_timestamp' , 'pop' => true ),
'publish_timestamp' => array ( 'function' => 'set_filter_timestamp' ),
'org' => array ( 'function' => 'set_filter_org' ),
'uuid' => array ( 'function' => 'set_filter_uuid' ),
'published' => array ( 'function' => 'set_filter_published' )
),
'Attribute' => array (
'value' => array ( 'function' => 'set_filter_value' ),
'category' => array ( 'function' => 'set_filter_simple_attribute' ),
'type' => array ( 'function' => 'set_filter_simple_attribute' ),
'object_relation' => array ( 'function' => 'set_filter_simple_attribute' ),
'tags' => array ( 'function' => 'set_filter_tags' , 'pop' => true ),
'uuid' => array ( 'function' => 'set_filter_uuid' ),
'deleted' => array ( 'function' => 'set_filter_deleted' ),
'timestamp' => array ( 'function' => 'set_filter_timestamp' ),
'attribute_timestamp' => array ( 'function' => 'set_filter_timestamp' ),
2024-01-19 18:11:28 +01:00
//'first_seen' => array('function' => 'set_filter_seen'),
//'last_seen' => array('function' => 'set_filter_seen'),
2020-02-29 08:57:32 +01:00
'to_ids' => array ( 'function' => 'set_filter_to_ids' ),
'comment' => array ( 'function' => 'set_filter_comment' )
)
);
foreach ( $params as $param => $paramData ) {
foreach ( $simple_params as $scope => $simple_param_scoped ) {
if ( isset ( $simple_param_scoped [ $param ]) && isset ( $params [ $param ]) && $params [ $param ] !== false ) {
$options = array (
'filter' => $param ,
'scope' => $scope ,
'pop' => ! empty ( $simple_param_scoped [ $param ][ 'pop' ]),
'context' => 'Attribute'
);
2020-06-14 20:23:48 +02:00
if ( $scope === 'Attribute' ) {
$subQueryOptions = array (
'fields' => [ 'Attribute.object_id' ],
'group' => 'Attribute.object_id' ,
'recursive' => - 1 ,
'conditions' => array (
'Attribute.object_id NOT' => 0 ,
$this -> Event -> { $simple_param_scoped [ $param ][ 'function' ]}( $params , $conditions , $options )
)
);
$conditions [ 'AND' ][] = $this -> subQueryGenerator ( $this -> Attribute , $subQueryOptions , 'Object.id' );
} else {
$conditions = $this -> Event -> { $simple_param_scoped [ $param ][ 'function' ]}( $params , $conditions , $options );
}
2020-02-29 08:57:32 +01:00
}
}
}
}
return $conditions ;
}
2020-01-27 16:29:28 +01:00
// check whether the variable is null or datetime
2021-10-17 16:33:35 +02:00
public function datetimeOrNull ( $fields )
{
$seen = array_values ( $fields )[ 0 ];
if ( $seen === null ) {
return true ;
}
return strtotime ( $seen ) !== false ;
}
2020-01-27 16:29:28 +01:00
2021-05-06 15:01:58 +02:00
public function validateLastSeenValue ( $fields )
{
$ls = $fields [ 'last_seen' ];
2021-10-17 16:33:35 +02:00
if ( ! isset ( $this -> data [ 'Object' ][ 'first_seen' ]) || $ls === null ) {
2021-05-06 15:01:58 +02:00
return true ;
}
$converted = $this -> Attribute -> ISODatetimeToUTC ([ 'Object' => [
'first_seen' => $this -> data [ 'Object' ][ 'first_seen' ],
'last_seen' => $ls
]], 'Object' );
2021-05-11 11:22:19 +02:00
if ( $converted [ 'Object' ][ 'first_seen' ] > $converted [ 'Object' ][ 'last_seen' ]) {
2021-05-06 15:01:58 +02:00
return false ;
}
return true ;
}
2019-06-13 09:16:34 +02:00
public function afterFind ( $results , $primary = false )
{
2021-10-28 13:19:20 +02:00
foreach ( $results as & $v ) {
$object = & $v [ 'Object' ];
if ( ! empty ( $object [ 'first_seen' ])) {
$object [ 'first_seen' ] = $this -> microTimestampToIso ( $object [ 'first_seen' ]);
}
if ( ! empty ( $object [ 'last_seen' ])) {
$object [ 'last_seen' ] = $this -> microTimestampToIso ( $object [ 'last_seen' ]);
}
2019-06-13 09:16:34 +02:00
}
return $results ;
}
2021-10-17 21:48:38 +02:00
public function beforeSave ( $options = array ())
{
// generate UUID if it doesn't exist
if ( empty ( $this -> data [ 'Object' ][ 'uuid' ])) {
$this -> data [ 'Object' ][ 'uuid' ] = CakeText :: uuid ();
}
2019-12-16 10:47:07 +01:00
$this -> data = $this -> Attribute -> ISODatetimeToUTC ( $this -> data , $this -> alias );
2019-06-13 09:16:34 +02:00
}
2018-07-19 11:48:22 +02:00
public function beforeValidate ( $options = array ())
{
2021-10-17 16:44:15 +02:00
$object = & $this -> data [ 'Object' ];
if ( empty ( $object [ 'comment' ])) {
$object [ 'comment' ] = " " ;
2018-07-19 11:48:22 +02:00
}
// generate timestamp if it doesn't exist
2021-10-17 16:44:15 +02:00
if ( empty ( $object [ 'timestamp' ])) {
$object [ 'timestamp' ] = time ();
2018-07-19 11:48:22 +02:00
}
2019-06-13 09:16:34 +02:00
// parse first_seen different formats
2021-10-17 16:44:15 +02:00
if ( isset ( $object [ 'first_seen' ])) {
$object [ 'first_seen' ] = $object [ 'first_seen' ] === '' ? null : $object [ 'first_seen' ];
2019-06-13 09:16:34 +02:00
}
// parse last_seen different formats
2021-10-17 16:44:15 +02:00
if ( isset ( $object [ 'last_seen' ])) {
$object [ 'last_seen' ] = $object [ 'last_seen' ] === '' ? null : $object [ 'last_seen' ];
2019-06-13 09:16:34 +02:00
}
2021-10-17 16:44:15 +02:00
if ( empty ( $object [ 'template_version' ])) {
$object [ 'template_version' ] = 1 ;
2018-07-19 11:48:22 +02:00
}
2021-10-17 16:44:15 +02:00
if ( isset ( $object [ 'deleted' ]) && empty ( $object [ 'deleted' ])) {
$object [ 'deleted' ] = 0 ;
2018-07-19 11:48:22 +02:00
}
2021-10-17 16:44:15 +02:00
if ( ! isset ( $object [ 'distribution' ]) || $object [ 'distribution' ] != 4 ) {
$object [ 'sharing_group_id' ] = 0 ;
2018-07-19 11:48:22 +02:00
}
2021-10-17 16:44:15 +02:00
if ( ! isset ( $object [ 'distribution' ])) {
$object [ 'distribution' ] = 5 ;
2018-07-19 11:48:22 +02:00
}
return true ;
}
2017-06-13 12:08:26 +02:00
2018-07-19 11:48:22 +02:00
public function afterSave ( $created , $options = array ())
{
2022-07-31 23:48:38 +02:00
if ( ! Configure :: read ( 'MISP.completely_disable_correlation' ) && ! $created ) {
$object = $this -> data [ 'Object' ];
2022-10-13 09:51:12 +02:00
$this -> Attribute -> Correlation -> updateContainedCorrelations ( $object , 'object' , $options );
2022-07-31 23:48:38 +02:00
}
2022-08-04 19:55:20 +02:00
if ( ! empty ( $this -> data [ 'Object' ][ 'deleted' ]) && ! $created ) {
2022-08-04 08:19:54 +02:00
$attributes_to_delete = $this -> Attribute -> find ( 'all' , [
'recursive' => - 1 ,
'conditions' => [
'Attribute.object_id' => $this -> id ,
'Attribute.deleted' => 0
]
]);
foreach ( $attributes_to_delete as & $attribute_to_delete ) {
$attribute_to_delete [ 'Attribute' ][ 'deleted' ] = 1 ;
unset ( $attribute_to_delete [ 'Attribute' ][ 'timestamp' ]);
}
$this -> Attribute -> saveMany ( $attributes_to_delete );
}
2022-06-24 13:42:59 +02:00
$workflowErrors = [];
$logging = [
'model' => 'Object' ,
2022-07-27 10:43:09 +02:00
'action' => $created ? 'add' : 'edit' ,
2022-08-01 09:59:44 +02:00
'id' => $this -> data [ 'Object' ][ 'id' ],
2022-06-24 13:42:59 +02:00
];
2022-08-01 09:59:44 +02:00
$this -> executeTrigger ( 'object-after-save' , $this -> data , $workflowErrors , $logging );
2021-10-15 21:43:49 +02:00
$pubToZmq = $this -> pubToZmq ( 'object' ) && empty ( $this -> data [ 'Object' ][ 'skip_zmq' ]);
2021-10-10 18:41:44 +02:00
$kafkaTopic = $this -> kafkaTopic ( 'object' );
$pubToKafka = $kafkaTopic && empty ( $this -> data [ 'Object' ][ 'skip_kafka' ]);
2019-03-05 12:24:56 +01:00
if ( $pubToZmq || $pubToKafka ) {
$object = $this -> find ( 'first' , array (
'conditions' => array ( 'Object.id' => $this -> id ),
'recursive' => - 1
));
$action = $created ? 'add' : 'edit' ;
if ( ! empty ( $this -> data [ 'Object' ][ 'deleted' ])) {
$action = 'soft-delete' ;
}
if ( $pubToZmq ) {
2018-07-19 11:48:22 +02:00
$pubSubTool = $this -> getPubSubTool ();
$pubSubTool -> object_save ( $object , $action );
}
2019-03-05 12:24:56 +01:00
if ( $pubToKafka ) {
$kafkaPubTool = $this -> getKafkaPubTool ();
$kafkaPubTool -> publishJson ( $kafkaTopic , $object , $action );
}
2018-07-19 11:48:22 +02:00
}
return true ;
}
2017-07-02 22:42:44 +02:00
2018-07-19 11:48:22 +02:00
public function beforeDelete ( $cascade = true )
{
if ( ! empty ( $this -> data [ 'Object' ][ 'id' ])) {
2021-10-15 21:43:49 +02:00
$pubToZmq = $this -> pubToZmq ( 'object' );
$kafkaTopic = $this -> kafkaTopic ( 'object' );
if ( $pubToZmq || $kafkaTopic ) {
2018-07-19 11:48:22 +02:00
$object = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array ( 'Object.id' => $this -> data [ 'Object' ][ 'id' ])
));
2019-03-05 12:24:56 +01:00
if ( $pubToZmq ) {
$pubSubTool = $this -> getPubSubTool ();
$pubSubTool -> object_save ( $object , 'delete' );
}
2021-10-15 21:43:49 +02:00
if ( $kafkaTopic ) {
2019-03-05 12:24:56 +01:00
$kafkaPubTool = $this -> getKafkaPubTool ();
$kafkaPubTool -> publishJson ( $kafkaTopic , $object , 'delete' );
}
2018-07-19 11:48:22 +02:00
}
}
}
2017-10-27 09:10:46 +02:00
2018-07-19 11:48:22 +02:00
public function afterDelete ()
{
if ( ! empty ( $this -> data [ $this -> alias ][ 'id' ])) {
$this -> ObjectReference -> deleteAll (
array (
'ObjectReference.referenced_type' => 1 ,
'ObjectReference.referenced_id' => $this -> data [ $this -> alias ][ 'id' ],
),
false
);
}
}
2017-10-27 09:10:46 +02:00
2022-05-07 10:54:07 +02:00
/**
* @ param array $object
* @ param int $eventId
* @ param int $duplicatedObjectId
* @ param string $duplicateObjectUuid
* @ return bool
*/
private function checkForDuplicateObjects ( $object , $eventId , & $duplicatedObjectId , & $duplicateObjectUuid )
2018-07-19 11:48:22 +02:00
{
2020-02-10 14:30:34 +01:00
$newObjectAttributes = array ();
2021-01-20 15:10:16 +01:00
if ( isset ( $object [ 'Object' ][ 'Attribute' ])) {
$attributeArray = $object [ 'Object' ][ 'Attribute' ];
} else {
$attributeArray = $object [ 'Attribute' ];
}
foreach ( $attributeArray as $attribute ) {
2023-03-23 15:03:58 +01:00
$newObjectAttributes [] = $this -> getObjectAttributeHash ( $attribute );
2020-02-10 14:30:34 +01:00
}
$newObjectAttributeCount = count ( $newObjectAttributes );
2021-02-16 00:15:18 +01:00
if ( ! empty ( $this -> __objectDuplicationCheckCache [ 'new' ][ $object [ 'Object' ][ 'template_uuid' ]])) {
foreach ( $this -> __objectDuplicationCheckCache [ 'new' ][ $object [ 'Object' ][ 'template_uuid' ]] as $previousNewObject ) {
if ( $newObjectAttributeCount === count ( $previousNewObject )) {
if ( empty ( array_diff ( $previousNewObject , $newObjectAttributes ))) {
2022-05-07 10:54:07 +02:00
$duplicatedObjectId = $previousNewObject [ 'Object' ][ 'id' ];
2021-11-16 19:20:21 +01:00
$duplicateObjectUuid = $previousNewObject [ 'Object' ][ 'uuid' ];
2021-02-16 00:15:18 +01:00
return true ;
}
}
}
}
$this -> __objectDuplicationCheckCache [ 'new' ][ $object [ 'Object' ][ 'template_uuid' ]][] = $newObjectAttributes ;
2021-01-20 15:10:16 +01:00
if ( ! isset ( $this -> __objectDuplicationCheckCache [ $object [ 'Object' ][ 'template_uuid' ]])) {
$this -> __objectDuplicationCheckCache [ $object [ 'Object' ][ 'template_uuid' ]] = $this -> find ( 'all' , array (
'recursive' => - 1 ,
'contain' => array (
'Attribute' => array (
'fields' => array ( 'value' , 'type' , 'category' , 'object_relation' ),
'conditions' => array ( 'Attribute.deleted' => 0 )
)
),
2023-03-23 15:03:58 +01:00
'fields' => array ( 'template_uuid' , 'uuid' ),
2021-01-20 15:10:16 +01:00
'conditions' => array ( 'template_uuid' => $object [ 'Object' ][ 'template_uuid' ], 'Object.deleted' => 0 , 'event_id' => $eventId )
));
}
2021-09-07 16:47:55 +02:00
foreach ( $this -> __objectDuplicationCheckCache [ $object [ 'Object' ][ 'template_uuid' ]] as $existingObject ) {
2020-02-10 14:39:58 +01:00
$temp = array ();
2021-09-07 16:47:55 +02:00
if ( ! empty ( $existingObject [ 'Attribute' ]) && $newObjectAttributeCount === count ( $existingObject [ 'Attribute' ])) {
2020-02-10 14:30:34 +01:00
foreach ( $existingObject [ 'Attribute' ] as $existingAttribute ) {
2023-03-23 15:03:58 +01:00
$temp [] = $this -> getObjectAttributeHash ( $existingAttribute );
2020-02-10 14:39:58 +01:00
}
2023-03-23 15:03:58 +01:00
2020-02-10 14:39:58 +01:00
if ( empty ( array_diff ( $temp , $newObjectAttributes ))) {
2022-05-07 10:54:07 +02:00
$duplicatedObjectId = $existingObject [ 'Object' ][ 'id' ];
2023-03-23 15:03:58 +01:00
$duplicateObjectUuid = $existingObject [ 'Object' ][ 'uuid' ];
2020-02-10 14:39:58 +01:00
return true ;
2020-02-10 14:30:34 +01:00
}
}
}
return false ;
}
2022-10-03 15:31:56 +02:00
/**
* @ param array $object
* @ param array $template
* @ return array
*/
public function fillObjectDataFromTemplate ( array $object , array $template )
{
$templateFields = array (
'name' => 'name' ,
'meta-category' => 'meta-category' ,
'description' => 'description' ,
'template_version' => 'version' ,
'template_uuid' => 'uuid'
);
foreach ( $templateFields as $objectField => $templateField ) {
$object [ 'Object' ][ $objectField ] = $template [ 'ObjectTemplate' ][ $templateField ];
}
return $object ;
}
/**
* @ param array $object
* @ param int $eventId
* @ param array $template
* @ param array $user
* @ param string $errorBehaviour
* @ param bool $breakOnDuplicate
* @ return array | array [] | bool | int | mixed | string
* @ throws Exception
*/
public function saveObject ( array $object , $eventId , $template = false , array $user , $errorBehaviour = 'drop' , $breakOnDuplicate = false )
2020-02-10 14:30:34 +01:00
{
2018-07-19 11:48:22 +02:00
$templateFields = array (
'name' => 'name' ,
'meta-category' => 'meta-category' ,
'description' => 'description' ,
'template_version' => 'version' ,
'template_uuid' => 'uuid'
);
if ( $template ) {
foreach ( $templateFields as $k => $v ) {
$object [ 'Object' ][ $k ] = $template [ 'ObjectTemplate' ][ $v ];
}
} else {
foreach ( $templateFields as $k => $v ) {
if ( ! isset ( $object [ 'Object' ][ $k ])) {
2020-11-17 11:37:40 +01:00
return array ( 'template' => array ( __ ( 'No valid template found and object lacking template information. (%s)' , $k )));
2018-07-19 11:48:22 +02:00
}
}
}
$object [ 'Object' ][ 'event_id' ] = $eventId ;
2021-07-14 11:30:31 +02:00
if ( $breakOnDuplicate ) {
2022-05-07 10:54:07 +02:00
$duplicatedObjectId = null ;
2021-11-16 19:20:21 +01:00
$duplicateObjectUuid = null ;
2022-05-07 10:54:07 +02:00
$duplicate = $this -> checkForDuplicateObjects ( $object , $eventId , $duplicatedObjectId , $duplicateObjectUuid );
2021-07-14 11:30:31 +02:00
if ( $duplicate ) {
2022-05-07 10:54:07 +02:00
return array ( 'value' => array ( __ ( 'Duplicate object found (id: %s, uuid: %s). Since breakOnDuplicate is set the object will not be added.' , $duplicatedObjectId , $duplicateObjectUuid )));
2021-07-14 11:30:31 +02:00
}
}
$this -> create ();
2024-04-29 14:11:04 +02:00
$saveResult = $this -> save ( $object );
if ( $saveResult ) {
2018-07-19 11:48:22 +02:00
$result = $this -> id ;
foreach ( $object [ 'Attribute' ] as $k => $attribute ) {
$object [ 'Attribute' ][ $k ][ 'object_id' ] = $this -> id ;
2019-07-04 14:05:47 +02:00
if (
( ! array_key_exists ( 'first_seen' , $object [ 'Attribute' ][ $k ]) || is_null ( $object [ 'Attribute' ][ $k ][ 'first_seen' ])) &&
2019-07-05 10:40:00 +02:00
( array_key_exists ( 'first_seen' , $object [ 'Object' ]) && ! is_null ( $object [ 'Object' ][ 'first_seen' ]))
2019-07-04 14:05:47 +02:00
) {
$object [ 'Attribute' ][ $k ][ 'first_seen' ] = $object [ 'Object' ][ 'first_seen' ];
2019-07-04 13:52:29 +02:00
}
2019-07-04 14:05:47 +02:00
if (
( ! array_key_exists ( 'last_seen' , $object [ 'Attribute' ][ $k ]) || is_null ( $object [ 'Attribute' ][ $k ][ 'last_seen' ])) &&
2019-07-05 10:40:00 +02:00
( array_key_exists ( 'last_seen' , $object [ 'Object' ]) && ! is_null ( $object [ 'Object' ][ 'last_seen' ]))
2019-07-04 14:05:47 +02:00
) {
$object [ 'Attribute' ][ $k ][ 'last_seen' ] = $object [ 'Object' ][ 'last_seen' ];
2019-07-04 13:52:29 +02:00
}
2018-07-19 11:48:22 +02:00
}
2020-07-24 14:30:41 +02:00
$this -> Attribute -> saveAttributes ( $object [ 'Attribute' ], $user );
2024-04-29 14:11:04 +02:00
$this -> Event -> captureAnalystData ( $user , $object [ 'Object' ], 'Object' , $saveResult [ 'Object' ][ 'uuid' ]);
2018-07-19 11:48:22 +02:00
} else {
$result = $this -> validationErrors ;
}
return $result ;
}
2017-09-17 12:26:06 +02:00
2020-09-20 18:15:30 +02:00
public function buildConditions ( array $user )
2018-07-19 11:48:22 +02:00
{
2020-09-21 11:33:19 +02:00
if ( $user [ 'Role' ][ 'perm_site_admin' ]) {
return [];
2018-07-19 11:48:22 +02:00
}
2020-09-21 11:33:19 +02:00
2022-05-22 17:09:00 +02:00
$sgids = $this -> SharingGroup -> authorizedIds ( $user );
2020-09-21 11:33:19 +02:00
return [
'AND' => [
'OR' => [
'Event.org_id' => $user [ 'org_id' ], // if event is owned by current user org, allow access to all objects
'AND' => [
$this -> Event -> createEventConditions ( $user ),
'OR' => [
'Object.distribution' => array ( 1 , 2 , 3 , 5 ),
'AND' => [
'Object.distribution' => 4 ,
'Object.sharing_group_id' => $sgids ,
]
]
]
]
]
];
2018-07-19 11:48:22 +02:00
}
2017-06-13 12:08:26 +02:00
2021-06-30 09:35:01 +02:00
public function fetchObjectSimple ( array $user , $options = array ())
2020-09-20 18:16:11 +02:00
{
$params = array (
'conditions' => $this -> buildConditions ( $user ),
'fields' => array (),
);
if ( isset ( $options [ 'conditions' ])) {
$params [ 'conditions' ][ 'AND' ][] = $options [ 'conditions' ];
}
if ( isset ( $options [ 'fields' ])) {
$params [ 'fields' ] = $options [ 'fields' ];
2019-06-13 09:16:34 +02:00
}
2024-01-26 16:05:35 +01:00
$contain = [];
if ( isset ( $options [ 'contain' ])) {
$contain = $options [ 'contain' ];
}
if ( empty ( $contain [ 'Event' ])) {
$contain = [ 'Event' => [ 'distribution' , 'id' , 'user_id' , 'orgc_id' , 'org_id' ]];
}
2020-09-20 18:16:11 +02:00
$results = $this -> find ( 'all' , array (
'conditions' => $params [ 'conditions' ],
'recursive' => - 1 ,
'fields' => $params [ 'fields' ],
2024-01-26 16:05:35 +01:00
'contain' => $contain ,
2020-09-20 18:16:11 +02:00
'sort' => false
));
return $results ;
}
2017-06-13 12:08:26 +02:00
2018-07-19 11:48:22 +02:00
// Method that fetches all objects
// very flexible, it's basically a replacement for find, with the addition that it restricts access based on user
// options:
// fields
// contain
// conditions
// order
// group
2022-09-30 09:43:10 +02:00
public function fetchObjects ( array $user , array $options = array ())
2018-07-19 11:48:22 +02:00
{
$attributeConditions = array ();
if ( ! $user [ 'Role' ][ 'perm_site_admin' ]) {
2022-05-22 17:09:00 +02:00
$sgids = $this -> SharingGroup -> authorizedIds ( $user );
2018-07-19 11:48:22 +02:00
$attributeConditions = array (
'OR' => array (
array (
'(SELECT events.org_id FROM events WHERE events.id = Attribute.event_id)' => $user [ 'org_id' ]
),
array (
'OR' => array (
'Attribute.distribution' => array ( 1 , 2 , 3 , 5 ),
array (
'Attribute.distribution' => 4 ,
2020-09-20 18:43:07 +02:00
'Attribute.sharing_group_id' => $sgids ,
2018-07-19 11:48:22 +02:00
)
)
)
)
);
}
$params = array (
'conditions' => $this -> buildConditions ( $user ),
'recursive' => - 1 ,
'contain' => array (
'Event' => array (
'fields' => array ( 'id' , 'info' , 'org_id' , 'orgc_id' ),
),
'Attribute' => array (
'conditions' => $attributeConditions ,
//'ShadowAttribute',
'AttributeTag' => array (
'Tag'
)
)
2020-02-29 08:57:32 +01:00
),
2018-07-19 11:48:22 +02:00
);
2020-05-06 09:32:44 +02:00
if ( ! empty ( $options [ 'metadata' ])) {
unset ( $params [ 'contain' ][ 'Attribute' ]);
}
if ( empty ( $options [ 'metadata' ]) && empty ( $options [ 'includeAllTags' ])) {
2018-07-19 11:48:22 +02:00
$params [ 'contain' ][ 'Attribute' ][ 'AttributeTag' ][ 'Tag' ][ 'conditions' ][ 'exportable' ] = 1 ;
}
if ( isset ( $options [ 'contain' ])) {
$params [ 'contain' ] = array_merge_recursive ( $params [ 'contain' ], $options [ 'contain' ]);
}
2020-05-06 09:32:44 +02:00
if (
empty ( $options [ 'metadata' ]) &&
Configure :: read ( 'MISP.proposals_block_attributes' ) &&
isset ( $options [ 'conditions' ][ 'AND' ][ 'Attribute.to_ids' ]) &&
$options [ 'conditions' ][ 'AND' ][ 'Attribute.to_ids' ] == 1
) {
2018-07-19 11:48:22 +02:00
$this -> Attribute -> bindModel ( array ( 'hasMany' => array ( 'ShadowAttribute' => array ( 'foreignKey' => 'old_id' ))));
$proposalRestriction = array (
'ShadowAttribute' => array (
'conditions' => array (
'AND' => array (
'ShadowAttribute.deleted' => 0 ,
'OR' => array (
'ShadowAttribute.proposal_to_delete' => 1 ,
'ShadowAttribute.to_ids' => 0
)
)
),
'fields' => array ( 'ShadowAttribute.id' )
)
);
$params [ 'contain' ] = array_merge ( $params [ 'contain' ][ 'Attribute' ], $proposalRestriction );
}
if ( isset ( $options [ 'conditions' ])) {
$params [ 'conditions' ][ 'AND' ][] = $options [ 'conditions' ];
}
if ( isset ( $options [ 'order' ])) {
$params [ 'order' ] = $options [ 'order' ];
}
if ( ! isset ( $options [ 'withAttachments' ])) {
$options [ 'withAttachments' ] = false ;
} else ( $params [ 'order' ] = array ());
if ( ! isset ( $options [ 'enforceWarninglist' ])) {
$options [ 'enforceWarninglist' ] = false ;
}
2020-05-06 09:32:44 +02:00
if ( empty ( $options [ 'metadata' ]) && ( ! $user [ 'Role' ][ 'perm_sync' ] || ! isset ( $options [ 'deleted' ]) || ! $options [ 'deleted' ])) {
2018-07-19 11:48:22 +02:00
$params [ 'contain' ][ 'Attribute' ][ 'conditions' ][ 'AND' ][ 'Attribute.deleted' ] = 0 ;
}
if ( isset ( $options [ 'group' ])) {
$params [ 'group' ] = array_merge ( array ( 'Object.id' ), $options [ 'group' ]);
}
2020-02-29 08:57:32 +01:00
if ( isset ( $options [ 'limit' ])) {
$params [ 'limit' ] = $options [ 'limit' ];
if ( isset ( $options [ 'page' ])) {
$params [ 'page' ] = $options [ 'page' ];
}
}
2018-07-19 11:48:22 +02:00
$results = $this -> find ( 'all' , $params );
2020-09-03 15:24:03 +02:00
if ( $options [ 'enforceWarninglist' ] && ! isset ( $this -> Warninglist )) {
2018-07-19 11:48:22 +02:00
$this -> Warninglist = ClassRegistry :: init ( 'Warninglist' );
}
$proposals_block_attributes = Configure :: read ( 'MISP.proposals_block_attributes' );
2020-05-06 09:32:44 +02:00
if ( empty ( $options [ 'metadata' ])) {
2020-09-20 18:46:38 +02:00
foreach ( $results as $key => $object ) {
foreach ( $object [ 'Attribute' ] as $key2 => $attribute ) {
2020-09-07 16:37:38 +02:00
if ( $options [ 'enforceWarninglist' ] && ! $this -> Warninglist -> filterWarninglistAttribute ( $attribute [ 'Attribute' ])) {
2018-07-19 11:48:22 +02:00
unset ( $results [ $key ][ $key2 ]);
2020-05-06 09:32:44 +02:00
continue ;
2018-07-19 11:48:22 +02:00
}
2020-05-06 09:32:44 +02:00
if ( $proposals_block_attributes ) {
if ( ! empty ( $attribute [ 'ShadowAttribute' ])) {
unset ( $results [ $key ][ $key2 ]);
} else {
unset ( $results [ $key ][ $key2 ][ 'ShadowAttribute' ]);
}
}
if ( $options [ 'withAttachments' ]) {
2020-09-20 18:46:38 +02:00
if ( $this -> Attribute -> typeIsAttachment ( $attribute [ 'type' ])) {
$encodedFile = $this -> Attribute -> base64EncodeAttachment ( $attribute );
$results [ $key ][ 'Attribute' ][ $key2 ][ 'data' ] = $encodedFile ;
2020-05-06 09:32:44 +02:00
}
2018-07-19 11:48:22 +02:00
}
}
}
}
return $results ;
}
2017-06-13 12:08:26 +02:00
2022-09-23 15:03:46 +02:00
/**
2018-07-19 11:48:22 +02:00
* Prepare the template form view ' s data , setting defaults , sorting elements
2022-09-23 15:03:46 +02:00
* @ param array $template
* @ param array $request
* @ return array
2018-07-19 11:48:22 +02:00
*/
2022-09-23 15:03:46 +02:00
public function prepareTemplate ( array $template , array $request = array ())
2018-07-19 11:48:22 +02:00
{
usort ( $template [ 'ObjectTemplateElement' ], function ( $a , $b ) {
return $a [ 'ui-priority' ] < $b [ 'ui-priority' ];
});
$request_rearranged = array ();
$template_object_elements = $template [ 'ObjectTemplateElement' ];
unset ( $template [ 'ObjectTemplateElement' ]);
if ( ! empty ( $request [ 'Attribute' ])) {
foreach ( $request [ 'Attribute' ] as $attribute ) {
$request_rearranged [ $attribute [ 'object_relation' ]][] = $attribute ;
}
}
2022-09-23 15:03:46 +02:00
$typeDefinitions = $this -> Event -> Attribute -> typeDefinitions ;
$categoryDefinitions = $this -> Event -> Attribute -> categoryDefinitions ;
foreach ( $template_object_elements as $v ) {
2018-07-19 11:48:22 +02:00
if ( empty ( $request_rearranged [ $v [ 'object_relation' ]])) {
2022-09-23 15:03:46 +02:00
if ( isset ( $typeDefinitions [ $v [ 'type' ]])) {
$v [ 'default_category' ] = $typeDefinitions [ $v [ 'type' ]][ 'default_category' ];
$v [ 'to_ids' ] = $typeDefinitions [ $v [ 'type' ]][ 'to_ids' ];
2018-07-19 11:48:22 +02:00
if ( empty ( $v [ 'categories' ])) {
$v [ 'categories' ] = array ();
2022-09-23 15:03:46 +02:00
foreach ( $categoryDefinitions as $catk => $catv ) {
if ( in_array ( $v [ 'type' ], $catv [ 'types' ], true )) {
2018-07-19 11:48:22 +02:00
$v [ 'categories' ][] = $catk ;
}
}
}
$template [ 'ObjectTemplateElement' ][] = $v ;
} else {
2022-09-23 15:03:46 +02:00
$template [ 'warnings' ][] = 'Missing attribute type "' . $v [ 'type' ] . '" found. Omitted template element ("' . $v [ 'object_relation' ] . '") that would not pass validation due to this.' ;
2018-07-19 11:48:22 +02:00
}
} else {
foreach ( $request_rearranged [ $v [ 'object_relation' ]] as $request_item ) {
2022-09-23 15:03:46 +02:00
if ( isset ( $typeDefinitions [ $v [ 'type' ]])) {
2018-07-19 11:48:22 +02:00
$v [ 'default_category' ] = $request_item [ 'category' ];
$v [ 'value' ] = $request_item [ 'value' ];
$v [ 'to_ids' ] = $request_item [ 'to_ids' ];
$v [ 'comment' ] = $request_item [ 'comment' ];
if ( ! empty ( $request_item [ 'uuid' ])) {
$v [ 'uuid' ] = $request_item [ 'uuid' ];
}
if ( isset ( $request_item [ 'data' ])) {
$v [ 'data' ] = $request_item [ 'data' ];
}
if ( empty ( $v [ 'categories' ])) {
$v [ 'categories' ] = array ();
2022-09-23 15:03:46 +02:00
foreach ( $categoryDefinitions as $catk => $catv ) {
if ( in_array ( $v [ 'type' ], $catv [ 'types' ], true )) {
2018-07-19 11:48:22 +02:00
$v [ 'categories' ][] = $catk ;
}
}
}
2018-11-21 15:25:03 +01:00
$v [ 'disable_correlation' ] = $request_item [ 'disable_correlation' ];
2018-07-19 11:48:22 +02:00
$template [ 'ObjectTemplateElement' ][] = $v ;
2019-08-22 15:28:25 +02:00
unset ( $v [ 'uuid' ]); // force creating a new attribute if template element entry gets reused
2018-07-19 11:48:22 +02:00
} else {
2022-09-23 15:03:46 +02:00
$template [ 'warnings' ][] = 'Missing attribute type "' . $v [ 'type' ] . '" found. Omitted template element ("' . $v [ 'object_relation' ] . '") that would not pass validation due to this.' ;
2018-07-19 11:48:22 +02:00
}
}
}
}
return $template ;
}
2017-07-02 00:05:15 +02:00
2020-10-29 09:02:50 +01:00
/**
2018-07-19 11:48:22 +02:00
* Clean the attribute list up from artifacts introduced by the object form
2020-10-29 09:02:50 +01:00
* @ param array $attributes
2022-10-03 09:55:52 +02:00
* @ return array
2021-10-30 20:32:04 +02:00
* @ throws InternalErrorException
* @ throws Exception
2018-07-19 11:48:22 +02:00
*/
2021-11-24 14:36:08 +01:00
public function attributeCleanup ( array $attributes )
2018-07-19 11:48:22 +02:00
{
if ( empty ( $attributes [ 'Attribute' ])) {
2019-08-05 10:50:39 +02:00
return $attributes ;
2018-07-19 11:48:22 +02:00
}
foreach ( $attributes [ 'Attribute' ] as $k => $attribute ) {
if ( isset ( $attribute [ 'save' ]) && $attribute [ 'save' ] == 0 ) {
unset ( $attributes [ 'Attribute' ][ $k ]);
continue ;
}
if ( isset ( $attribute [ 'value_select' ])) {
if ( $attribute [ 'value_select' ] !== 'Enter value manually' ) {
$attributes [ 'Attribute' ][ $k ][ 'value' ] = $attribute [ 'value_select' ];
}
unset ( $attributes [ 'Attribute' ][ $k ][ 'value_select' ]);
}
if ( isset ( $attribute [ 'Attachment' ])) {
// Check if there were problems with the file upload
// only keep the last part of the filename, this should prevent directory attacks
if (( isset ( $attribute [ 'Attachment' ][ 'error' ]) && $attribute [ 'Attachment' ][ 'error' ] == 0 ) ||
( ! empty ( $attribute [ 'Attachment' ][ 'tmp_name' ]) && $attribute [ 'Attachment' ][ 'tmp_name' ] != 'none' )
) {
2021-10-30 20:32:04 +02:00
if ( ! is_uploaded_file ( $attribute [ 'Attachment' ][ 'tmp_name' ])) {
2018-07-19 11:48:22 +02:00
throw new InternalErrorException ( 'PHP says file was not uploaded. Are you attacking me?' );
}
} else {
2021-10-30 20:32:04 +02:00
throw new InternalErrorException ( 'Issues with the file attachment for the ' . $attribute [ 'object_relation' ] . ' attribute. The error code returned is ' . $attribute [ 'Attachment' ][ 'error' ]);
2018-07-19 11:48:22 +02:00
}
$attributes [ 'Attribute' ][ $k ][ 'value' ] = $attribute [ 'Attachment' ][ 'name' ];
unset ( $attributes [ 'Attribute' ][ $k ][ 'Attachment' ]);
2021-10-30 20:32:04 +02:00
$attributes [ 'Attribute' ][ $k ][ 'encrypt' ] = $attribute [ 'type' ] === 'malware-sample' ? 1 : 0 ;
$attributes [ 'Attribute' ][ $k ][ 'data' ] = base64_encode ( FileAccessTool :: readAndDelete ( $attribute [ 'Attachment' ][ 'tmp_name' ]));
2018-07-19 11:48:22 +02:00
}
2019-12-16 15:00:30 +01:00
if ( ! isset ( $attributes [ 'Attribute' ][ $k ][ 'first_seen' ])) {
$attributes [ 'Attribute' ][ $k ][ 'first_seen' ] = null ;
}
if ( ! isset ( $attributes [ 'Attribute' ][ $k ][ 'last_seen' ])) {
$attributes [ 'Attribute' ][ $k ][ 'last_seen' ] = null ;
}
2018-07-19 11:48:22 +02:00
unset ( $attributes [ 'Attribute' ][ $k ][ 'save' ]);
}
return $attributes ;
}
2017-07-02 20:32:30 +02:00
2022-10-03 12:20:18 +02:00
/**
* @ param array $user
* @ param int $eventId
* @ param array $attributes
* @ param array $template
* @ param int $threshold
* @ return array
*/
public function findSimilarObjects ( array $user , $eventId , array $attributes , array $template , $threshold = 15 )
{
$attributeValues = array_column ( $attributes , 'value' );
$conditions = array (
'event_id' => $eventId ,
'value1' => $attributeValues ,
'object_id !=' => 0 ,
);
$similarObjects = $this -> Attribute -> find ( 'all' , array (
'conditions' => $conditions ,
'recursive' => - 1 ,
'fields' => 'object_id, count(object_id) as similarity_amount' ,
'group' => 'object_id' ,
'order' => 'similarity_amount DESC'
));
if ( empty ( $similarObjects )) {
2022-10-03 17:28:43 +02:00
return [ 0 , [], [], []];
2022-10-03 12:20:18 +02:00
}
$similar_object_ids = array ();
$similar_object_similarity_amount = array ();
foreach ( $similarObjects as $obj ) {
$similar_object_ids [] = $obj [ 'Attribute' ][ 'object_id' ];
2022-10-03 17:28:43 +02:00
$similar_object_similarity_amount [ $obj [ 'Attribute' ][ 'object_id' ]] = ( int ) $obj [ 0 ][ 'similarity_amount' ];
2022-10-03 12:20:18 +02:00
}
$similar_objects_count = count ( $similar_object_ids );
$similar_object_ids = array_slice ( $similar_object_ids , 0 , $threshold ); // slice to honor the threshold
$similar_objects = $this -> fetchObjects ( $user , array (
'conditions' => array (
'Object.id' => $similar_object_ids ,
'Object.template_uuid' => $template [ 'ObjectTemplate' ][ 'uuid' ]
)
));
foreach ( $similar_objects as $key => $obj ) {
$similar_objects [ $key ][ 'Object' ][ 'similarity_amount' ] = $similar_object_similarity_amount [ $obj [ 'Object' ][ 'id' ]]; // sorting function cannot use external variables
}
usort ( $similar_objects , function ( $a , $b ) { // fetch Object returns object sorted by IDs, force the sort by the similarity amount
if ( $a [ 'Object' ][ 'similarity_amount' ] == $b [ 'Object' ][ 'similarity_amount' ]) {
return 0 ;
}
return ( $a [ 'Object' ][ 'similarity_amount' ] > $b [ 'Object' ][ 'similarity_amount' ]) ? - 1 : 1 ;
});
2022-10-03 17:28:43 +02:00
$simple_flattened_attribute = [];
$simple_flattened_attribute_noval = [];
foreach ( $attributes as $k => $attribute ) {
$curFlat = $attribute [ 'object_relation' ] . '.' . $attribute [ 'type' ] . '.' . $attribute [ 'value' ];
$simple_flattened_attribute [ $curFlat ] = $k ;
$curFlatNoval = $attribute [ 'object_relation' ] . '.' . $attribute [ 'type' ];
$simple_flattened_attribute_noval [ $curFlatNoval ] = $k ;
}
return [ $similar_objects_count , $similar_objects , $simple_flattened_attribute , $simple_flattened_attribute_noval ];
2022-10-03 12:20:18 +02:00
}
2019-10-28 15:45:33 +01:00
// Set Object's *-seen (and ObjectAttribute's *-seen and ObjectAttribute's value if requested) to the provided *-seen value
// Therefore, synchronizing the 3 values
public function syncObjectAndAttributeSeen ( $object , $forcedSeenOnElements , $applyOnAttribute = True ) {
if ( empty ( $forcedSeenOnElements )) {
return $object ;
}
if ( isset ( $forcedSeenOnElements [ 'first_seen' ])) {
$object [ 'Object' ][ 'first_seen' ] = $forcedSeenOnElements [ 'first_seen' ];
}
if ( isset ( $forcedSeenOnElements [ 'last_seen' ])) {
$object [ 'Object' ][ 'last_seen' ] = $forcedSeenOnElements [ 'last_seen' ];
}
if ( $applyOnAttribute ) {
if ( isset ( $object [ 'Attribute' ])) {
$attributes = $object [ 'Attribute' ];
} else {
$attributes = $this -> find ( 'first' , array (
'conditions' => array ( 'id' => $object [ 'Object' ][ 'id' ]),
'contain' => array ( 'Attribute' )
))[ 'Attribute' ];
}
foreach ( $attributes as $i => $attribute ) {
if ( isset ( $forcedSeenOnElements [ 'first_seen' ])) {
$attributes [ $i ][ 'first_seen' ] = $forcedSeenOnElements [ 'first_seen' ];
if ( $attribute [ 'object_relation' ] == 'first-seen' ) {
$attributes [ $i ][ 'value' ] = $forcedSeenOnElements [ 'first_seen' ];
2019-06-13 09:16:34 +02:00
}
2019-10-28 15:45:33 +01:00
} elseif ( isset ( $forcedSeenOnElements [ 'last_seen' ])) {
$attributes [ $i ][ 'last_seen' ] = $forcedSeenOnElements [ 'last_seen' ];
if ( $attribute [ 'object_relation' ] == 'last-seen' ) {
$attributes [ $i ][ 'value' ] = $forcedSeenOnElements [ 'last_seen' ];
2019-06-13 09:16:34 +02:00
}
}
}
2019-10-28 15:45:33 +01:00
$object [ 'Attribute' ] = $attributes ;
2019-06-13 09:16:34 +02:00
}
2019-10-28 15:45:33 +01:00
return $object ;
2019-06-13 09:16:34 +02:00
}
2022-09-28 10:11:57 +02:00
/**
* @ param array $object
* @ param array $objectToSave
* @ param bool $onlyAddNewAttribute
* @ param array $user
* @ return array | int
* @ throws JsonException
*/
2021-11-24 14:36:08 +01:00
public function deltaMerge ( array $object , array $objectToSave , $onlyAddNewAttribute = false , array $user )
2018-07-19 11:48:22 +02:00
{
if ( ! isset ( $objectToSave [ 'Object' ])) {
$dataToBackup = array ( 'ObjectReferences' , 'Attribute' , 'ShadowAttribute' );
$backup = array ();
foreach ( $dataToBackup as $dtb ) {
if ( isset ( $objectToSave [ $dtb ])) {
$backup [ $dtb ] = $objectToSave [ $dtb ];
unset ( $objectToSave [ $dtb ]);
}
}
$objectToSave = array ( 'Object' => $objectToSave );
foreach ( $dataToBackup as $dtb ) {
if ( isset ( $backup [ $dtb ])) {
$objectToSave [ $dtb ] = $backup [ $dtb ];
}
}
unset ( $dataToBackup );
}
2019-06-13 09:16:34 +02:00
if ( isset ( $objectToSave [ 'Object' ][ 'comment' ])) {
$object [ 'Object' ][ 'comment' ] = $objectToSave [ 'Object' ][ 'comment' ];
}
2020-07-29 15:46:20 +02:00
if ( isset ( $objectToSave [ 'Object' ][ 'template_version' ])) {
$object [ 'Object' ][ 'template_version' ] = $objectToSave [ 'Object' ][ 'template_version' ];
}
2019-06-13 09:16:34 +02:00
if ( isset ( $objectToSave [ 'Object' ][ 'distribution' ])) {
$object [ 'Object' ][ 'distribution' ] = $objectToSave [ 'Object' ][ 'distribution' ];
if ( $object [ 'Object' ][ 'distribution' ] == 4 ) {
$object [ 'Object' ][ 'sharing_group_id' ] = $objectToSave [ 'Object' ][ 'sharing_group_id' ];
}
2018-07-19 11:48:22 +02:00
}
2021-11-24 14:36:08 +01:00
$time = time ();
$object [ 'Object' ][ 'timestamp' ] = $time ;
2019-10-28 15:45:33 +01:00
$forcedSeenOnElements = array ();
2019-06-13 09:16:34 +02:00
if ( isset ( $objectToSave [ 'Object' ][ 'first_seen' ])) {
2019-10-28 15:45:33 +01:00
$forcedSeenOnElements [ 'first_seen' ] = $objectToSave [ 'Object' ][ 'first_seen' ];
2019-06-13 09:16:34 +02:00
}
if ( isset ( $objectToSave [ 'Object' ][ 'last_seen' ])) {
2019-10-28 15:45:33 +01:00
$forcedSeenOnElements [ 'last_seen' ] = $objectToSave [ 'Object' ][ 'last_seen' ];
2019-06-13 09:16:34 +02:00
}
2019-10-28 15:45:33 +01:00
$object = $this -> syncObjectAndAttributeSeen ( $object , $forcedSeenOnElements , false );
2020-01-27 16:29:28 +01:00
$saveResult = $this -> save ( $object );
if ( $saveResult === false ) {
return $this -> validationErrors ;
}
2019-06-13 09:16:34 +02:00
2024-04-29 14:11:04 +02:00
$this -> Event -> captureAnalystData ( $user , $objectToSave [ 'Object' ], 'Object' , $saveResult [ 'Object' ][ 'uuid' ]);
2019-06-13 09:16:34 +02:00
if ( ! $onlyAddNewAttribute ) {
2019-10-28 15:45:33 +01:00
$checkFields = array ( 'category' , 'value' , 'to_ids' , 'distribution' , 'sharing_group_id' , 'comment' , 'disable_correlation' , 'first_seen' , 'last_seen' );
2019-10-02 11:23:04 +02:00
if ( ! empty ( $objectToSave [ 'Attribute' ])) {
foreach ( $objectToSave [ 'Attribute' ] as $newKey => $newAttribute ) {
foreach ( $object [ 'Attribute' ] as $origKey => $originalAttribute ) {
if ( ! empty ( $newAttribute [ 'uuid' ])) {
if ( $newAttribute [ 'uuid' ] == $originalAttribute [ 'uuid' ]) {
$different = false ;
foreach ( $checkFields as $f ) {
if ( $f == 'sharing_group_id' && empty ( $newAttribute [ $f ])) {
$newAttribute [ $f ] = 0 ;
}
2020-11-11 09:56:05 +01:00
if ( isset ( $newAttribute [ $f ]) && $this -> attributeValueDifferent ( $originalAttribute [ $f ], $newAttribute [ $f ], $f )) {
2019-10-02 11:23:04 +02:00
$different = true ;
}
2019-08-05 10:50:39 +02:00
}
2019-10-02 11:23:04 +02:00
if ( $different ) {
$newAttribute [ 'id' ] = $originalAttribute [ 'id' ];
$newAttribute [ 'event_id' ] = $object [ 'Object' ][ 'event_id' ];
$newAttribute [ 'object_id' ] = $object [ 'Object' ][ 'id' ];
2021-11-24 14:36:08 +01:00
$newAttribute [ 'timestamp' ] = $time ;
2021-07-27 18:58:04 +02:00
$result = $this -> Event -> Attribute -> save ( array ( 'Attribute' => $newAttribute ), array ( 'fieldList' => Attribute :: EDITABLE_FIELDS ));
2020-07-24 13:55:34 +02:00
if ( $result ) {
2020-07-24 14:30:41 +02:00
$this -> Event -> Attribute -> AttributeTag -> handleAttributeTags ( $user , $newAttribute , $newAttribute [ 'event_id' ], $capture = true );
2021-11-24 14:36:08 +01:00
} else {
2021-11-25 10:10:04 +01:00
$this -> Event -> Attribute -> logDropped ( $user , $newAttribute , 'edit' );
2020-07-24 13:55:34 +02:00
}
2019-08-05 10:50:39 +02:00
}
2019-10-02 11:23:04 +02:00
unset ( $object [ 'Attribute' ][ $origKey ]);
continue 2 ;
2018-07-19 11:48:22 +02:00
}
}
}
2019-10-02 11:23:04 +02:00
$newAttribute [ 'event_id' ] = $object [ 'Object' ][ 'event_id' ];
$newAttribute [ 'object_id' ] = $object [ 'Object' ][ 'id' ];
// Set seen of object at attribute level
2019-10-28 15:45:33 +01:00
if ( isset ( $forcedSeenOnElements [ 'first_seen' ])) {
2020-11-06 09:05:57 +01:00
$newAttribute [ 'first_seen' ] = empty ( $newAttribute [ 'first_seen' ]) ? $forcedSeenOnElements [ 'first_seen' ] : $newAttribute [ 'first_seen' ];
2019-10-28 15:45:33 +01:00
if ( $newAttribute [ 'object_relation' ] == 'first-seen' ) {
$newAttribute [ 'value' ] = $forcedSeenOnElements [ 'first_seen' ];
}
2019-08-05 10:50:39 +02:00
}
2019-10-28 15:45:33 +01:00
if ( isset ( $forcedSeenOnElements [ 'last_seen' ])) {
2020-11-06 09:05:57 +01:00
$newAttribute [ 'last_seen' ] = empty ( $newAttribute [ 'last_seen' ]) ? $forcedSeenOnElements [ 'last_seen' ] : $newAttribute [ 'last_seen' ];
2019-10-28 15:45:33 +01:00
if ( $newAttribute [ 'object_relation' ] == 'last-seen' ) {
$newAttribute [ 'value' ] = $forcedSeenOnElements [ 'last_seen' ];
}
2019-10-02 11:23:04 +02:00
}
2020-07-24 10:13:54 +02:00
if ( ! isset ( $newAttribute [ 'distribution' ])) {
2021-11-24 14:36:08 +01:00
$newAttribute [ 'distribution' ] = $this -> Event -> Attribute -> defaultDistribution ();
2019-06-13 09:16:34 +02:00
}
2021-11-24 14:36:08 +01:00
$this -> Event -> Attribute -> create ();
2020-07-24 13:55:34 +02:00
$saveResult = $this -> Event -> Attribute -> save ( $newAttribute );
if ( $saveResult ) {
$newAttribute [ 'id' ] = $this -> Event -> Attribute -> id ;
2020-07-24 14:30:41 +02:00
$this -> Event -> Attribute -> AttributeTag -> handleAttributeTags ( $user , $newAttribute , $newAttribute [ 'event_id' ], $capture = true );
2021-11-24 14:36:08 +01:00
} else {
2021-11-25 10:10:04 +01:00
$this -> Event -> Attribute -> logDropped ( $user , $newAttribute , 'add' );
2020-07-24 13:55:34 +02:00
}
2019-10-02 11:23:04 +02:00
unset ( $objectToSave [ 'Attribute' ][ $newKey ]);
}
2021-11-24 14:36:08 +01:00
foreach ( $object [ 'Attribute' ] as $originalAttribute ) {
2019-10-02 11:23:04 +02:00
$originalAttribute [ 'deleted' ] = 1 ;
2021-07-27 18:58:04 +02:00
$this -> Event -> Attribute -> save ( $originalAttribute , array ( 'fieldList' => Attribute :: EDITABLE_FIELDS ));
2019-02-10 13:08:12 +01:00
}
2019-08-05 10:50:39 +02:00
}
2019-06-13 09:16:34 +02:00
} else { // we only add the new attribute
$newAttribute = $objectToSave [ 'Attribute' ][ 0 ];
2019-10-28 15:45:33 +01:00
$newAttribute [ 'event_id' ] = $object [ 'Object' ][ 'event_id' ];
$newAttribute [ 'object_id' ] = $object [ 'Object' ][ 'id' ];
// Set seen of object at attribute level
if (
( ! array_key_exists ( 'first_seen' , $newAttribute ) || is_null ( $newAttribute [ 'first_seen' ])) &&
( ! array_key_exists ( 'first_seen' , $object [ 'Object' ]) && ! is_null ( $object [ 'Object' ][ 'first_seen' ]))
) {
$newAttribute [ 'first_seen' ] = $object [ 'Object' ][ 'first_seen' ];
}
if (
( ! array_key_exists ( 'last_seen' , $newAttribute ) || is_null ( $newAttribute [ 'last_seen' ])) &&
( ! array_key_exists ( 'last_seen' , $object [ 'Object' ]) && ! is_null ( $object [ 'Object' ][ 'last_seen' ]))
) {
$newAttribute [ 'last_seen' ] = $object [ 'Object' ][ 'last_seen' ];
}
2020-07-24 14:30:41 +02:00
$saveAttributeResult = $this -> Attribute -> saveAttributes ( array ( $newAttribute ), $user );
2022-09-28 10:11:57 +02:00
return $saveAttributeResult ? $this -> id : $this -> Attribute -> validationErrors ;
2018-07-19 11:48:22 +02:00
}
return $this -> id ;
}
2017-07-06 15:04:01 +02:00
2021-09-07 10:09:08 +02:00
/**
* @ param array $object
* @ param int $eventId
* @ param array $user
* @ param bool $unpublish
* @ param false $breakOnDuplicate
* @ param array | false $parentEvent
* @ return bool | string
* @ throws Exception
*/
2021-10-10 15:42:37 +02:00
public function captureObject ( $object , $eventId , $user , $unpublish = true , $breakOnDuplicate = false , $parentEvent = false )
2018-07-19 11:48:22 +02:00
{
$this -> create ();
if ( ! isset ( $object [ 'Object' ])) {
$object = array ( 'Object' => $object );
}
2021-02-16 00:15:18 +01:00
if ( ! empty ( $object [ 'Object' ][ 'breakOnDuplicate' ]) || $breakOnDuplicate ) {
2022-05-07 10:54:07 +02:00
$duplicatedObjectId = null ;
2021-11-16 19:20:21 +01:00
$duplicateObjectUuid = null ;
2022-05-07 10:54:07 +02:00
$duplicate = $this -> checkForDuplicateObjects ( $object , $eventId , $duplicatedObjectId , $duplicateObjectUuid );
2021-01-20 15:10:16 +01:00
if ( $duplicate ) {
2021-08-30 17:08:49 +02:00
$this -> loadLog () -> createLogEntry ( $user , 'add' , 'Object' , 0 ,
2022-05-07 10:54:07 +02:00
__ ( 'Object dropped due to it being a duplicate (ID: %s, UUID: %s) and breakOnDuplicate being requested for Event %s' , $duplicatedObjectId , $duplicateObjectUuid , $eventId ),
2021-08-30 17:08:49 +02:00
'Duplicate object found.'
);
2021-02-03 08:48:10 +01:00
return true ;
2021-01-20 15:10:16 +01:00
}
}
2021-10-10 15:40:04 +02:00
unset ( $object [ 'Object' ][ 'id' ]);
2018-07-19 11:48:22 +02:00
$object [ 'Object' ][ 'event_id' ] = $eventId ;
2021-10-10 15:40:04 +02:00
if ( ! $this -> save ( $object )) {
2021-08-30 17:08:49 +02:00
$this -> loadLog () -> createLogEntry ( $user , 'add' , 'Object' , 0 ,
'Object dropped due to validation for Event ' . $eventId . ' failed: ' . $object [ 'Object' ][ 'name' ],
'Validation errors: ' . json_encode ( $this -> validationErrors ) . ' Full Object: ' . json_encode ( $object )
);
2021-10-10 15:40:04 +02:00
return 'fail' ;
}
if ( $unpublish ) {
$this -> Event -> unpublishEvent ( $eventId );
2018-07-19 11:48:22 +02:00
}
2021-10-10 15:40:04 +02:00
$objectId = $this -> id ;
if ( ! empty ( $object [ 'Object' ][ 'Attribute' ])) {
foreach ( $object [ 'Object' ][ 'Attribute' ] as $attribute ) {
2022-08-04 08:19:54 +02:00
if ( ! empty ( $object [ 'Object' ][ 'deleted' ])) {
$attribute [ 'deleted' ] = 1 ;
}
2021-10-10 15:40:04 +02:00
$this -> Attribute -> captureAttribute ( $attribute , $eventId , $user , $objectId , false , $parentEvent );
}
}
2024-04-29 14:11:04 +02:00
$this -> Event -> captureAnalystData ( $user , $object [ 'Object' ], 'Object' , $object [ 'Object' ][ 'uuid' ]);
2021-10-10 15:40:04 +02:00
return true ;
2018-07-19 11:48:22 +02:00
}
2017-08-28 09:18:40 +02:00
2021-09-07 10:24:08 +02:00
public function editObject ( $object , array $event , $user , $log , $force = false , & $nothingToChange = false )
2018-07-19 11:48:22 +02:00
{
2021-09-07 10:24:08 +02:00
$eventId = $event [ 'Event' ][ 'id' ];
2018-07-19 11:48:22 +02:00
$object [ 'event_id' ] = $eventId ;
2021-04-08 10:57:20 +02:00
if ( isset ( $object [ 'distribution' ]) && $object [ 'distribution' ] == 4 ) {
if ( ! empty ( $object [ 'SharingGroup' ])) {
$object [ 'sharing_group_id' ] = $this -> SharingGroup -> captureSG ( $object [ 'SharingGroup' ], $user );
} elseif ( ! empty ( $object [ 'sharing_group_id' ])) {
if ( ! $this -> SharingGroup -> checkIfAuthorised ( $user , $object [ 'sharing_group_id' ])) {
unset ( $object [ 'sharing_group_id' ]);
}
}
if ( empty ( $object [ 'sharing_group_id' ])) {
$object_short = ( isset ( $object [ 'meta-category' ]) ? $object [ 'meta-category' ] : 'N/A' ) . '/' . ( isset ( $object [ 'name' ]) ? $object [ 'name' ] : 'N/A' ) . ' ' . ( isset ( $object [ 'uuid' ]) ? $object [ 'uuid' ] : 'N/A' );
2021-09-04 07:17:04 +02:00
$this -> loadLog () -> createLogEntry ( $user , 'edit' , 'MispObject' , 0 ,
'Object dropped due to invalid sharing group for Event ' . $eventId . ' failed: ' . $object_short ,
'Validation errors: ' . json_encode ( $this -> validationErrors ) . ' Full Object: ' . json_encode ( $object )
);
2021-04-08 10:57:20 +02:00
return 'Invalid sharing group choice.' ;
}
}
2018-07-19 11:48:22 +02:00
if ( isset ( $object [ 'uuid' ])) {
$existingObject = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array ( 'Object.uuid' => $object [ 'uuid' ])
));
if ( empty ( $existingObject )) {
2021-08-30 17:08:49 +02:00
return $this -> captureObject ( $object , $eventId , $user );
2018-07-19 11:48:22 +02:00
} else {
if ( $existingObject [ 'Object' ][ 'event_id' ] != $eventId ) {
2021-09-04 07:17:04 +02:00
$change = 'An object was blocked from being saved due to a duplicate UUID. The uuid in question is: ' . $object [ 'uuid' ] . '. This can also be due to the same object (or an object with the same UUID) existing in a different event)' ;
$this -> loadLog () -> createLogEntry ( $user , 'edit' , 'MispObject' , 0 , 'Duplicate UUID found in object' , $change );
2018-07-19 11:48:22 +02:00
return true ;
}
if ( isset ( $object [ 'timestamp' ])) {
2020-05-14 09:34:49 +02:00
if ( $force || $existingObject [ 'Object' ][ 'timestamp' ] >= $object [ 'timestamp' ]) {
2020-07-22 13:10:52 +02:00
$nothingToChange = true ;
2018-07-19 11:48:22 +02:00
return true ;
}
} else {
2021-09-04 07:17:04 +02:00
$object [ 'timestamp' ] = time ();
2018-07-19 11:48:22 +02:00
}
}
} else {
2021-08-30 17:08:49 +02:00
return $this -> captureObject ( $object , $eventId , $user );
2018-07-19 11:48:22 +02:00
}
// At this point we have an existingObject that we can edit
$recoverFields = array (
'name' ,
'meta-category' ,
'description' ,
'template_uuid' ,
'template_version' ,
'distribution' ,
'sharing_group_id' ,
'comment' ,
'deleted'
);
foreach ( $recoverFields as $rF ) {
if ( ! isset ( $object [ $rF ])) {
$object [ $rF ] = $existingObject [ 'Object' ][ $rF ];
}
}
$object [ 'id' ] = $existingObject [ 'Object' ][ 'id' ];
$object [ 'uuid' ] = $existingObject [ 'Object' ][ 'uuid' ];
$object [ 'event_id' ] = $eventId ;
if ( $object [ 'distribution' ] == 4 ) {
$object [ 'sharing_group_id' ] = $this -> SharingGroup -> captureSG ( $object [ 'SharingGroup' ], $user );
}
if ( ! $this -> save ( $object )) {
2021-09-04 07:17:04 +02:00
$this -> loadLog () -> createLogEntry ( $user , 'edit' , 'MispObject' , 0 ,
'Object dropped due to validation for Event ' . $eventId . ' failed: ' . $object [ 'name' ],
'Validation errors: ' . json_encode ( $this -> validationErrors ) . ' Full Object: ' . json_encode ( $object )
);
2018-07-19 11:48:22 +02:00
return $this -> validationErrors ;
}
2024-04-29 14:11:04 +02:00
$this -> Event -> captureAnalystData ( $user , $object , 'Object' , $object [ 'uuid' ]);
2018-07-19 11:48:22 +02:00
if ( ! empty ( $object [ 'Attribute' ])) {
2023-11-23 09:42:51 +01:00
$attributes = [];
2018-07-19 11:48:22 +02:00
foreach ( $object [ 'Attribute' ] as $attribute ) {
2022-08-04 08:19:54 +02:00
if ( ! empty ( $object [ 'deleted' ])) {
$attribute [ 'deleted' ] = 1 ;
}
2021-09-07 10:24:08 +02:00
$result = $this -> Attribute -> editAttribute ( $attribute , $event , $user , $object [ 'id' ], false , $force );
2023-11-23 09:42:51 +01:00
if ( is_array ( $result )) {
$attributes [] = $result ;
}
2018-07-19 11:48:22 +02:00
}
2023-11-23 09:42:51 +01:00
$this -> Attribute -> editAttributeBulk ( $attributes , $event , $user );
$this -> Attribute -> editAttributePostProcessing ( $attributes , $event , $user );
2018-07-19 11:48:22 +02:00
}
return true ;
}
2017-09-04 17:26:45 +02:00
2020-08-05 10:12:53 +02:00
public function deleteObject ( array $object , $hard = false , $unpublish = true )
2020-07-23 11:52:46 +02:00
{
$id = $object [ 'Object' ][ 'id' ];
if ( $hard ) {
// For a hard delete, simply run the delete, it will cascade
$this -> delete ( $id );
} else {
// For soft deletes, sanitise the object first if the setting is enabled
if ( Configure :: read ( 'Security.sanitise_attribute_on_delete' )) {
$object [ 'Object' ][ 'name' ] = 'N/A' ;
$object [ 'Object' ][ 'category' ] = 'N/A' ;
$object [ 'Object' ][ 'description' ] = 'N/A' ;
$object [ 'Object' ][ 'template_uuid' ] = 'N/A' ;
$object [ 'Object' ][ 'template_version' ] = 0 ;
$object [ 'Object' ][ 'comment' ] = '' ;
}
$date = new DateTime ();
$object [ 'Object' ][ 'deleted' ] = 1 ;
$object [ 'Object' ][ 'timestamp' ] = $date -> getTimestamp ();
$saveResult = $this -> save ( $object );
if ( ! $saveResult ) {
return $saveResult ;
}
foreach ( $object [ 'Attribute' ] as $attribute ) {
if ( Configure :: read ( 'Security.sanitise_attribute_on_delete' )) {
$attribute [ 'category' ] = 'Other' ;
$attribute [ 'type' ] = 'comment' ;
$attribute [ 'value' ] = 'deleted' ;
$attribute [ 'comment' ] = '' ;
$attribute [ 'to_ids' ] = 0 ;
}
$attribute [ 'deleted' ] = 1 ;
$attribute [ 'timestamp' ] = $date -> getTimestamp ();
$this -> Attribute -> save ( array ( 'Attribute' => $attribute ));
$this -> Event -> ShadowAttribute -> deleteAll (
array ( 'ShadowAttribute.old_id' => $attribute [ 'id' ]),
false
);
}
if ( $unpublish ) {
$this -> Event -> unpublishEvent ( $object [ 'Event' ][ 'id' ]);
}
$object_refs = $this -> ObjectReference -> find ( 'all' , array (
'conditions' => array (
'ObjectReference.referenced_type' => 1 ,
'ObjectReference.referenced_id' => $id ,
),
'recursive' => - 1
));
foreach ( $object_refs as $ref ) {
$ref [ 'ObjectReference' ][ 'deleted' ] = 1 ;
$this -> ObjectReference -> save ( $ref );
}
}
return true ;
}
2021-10-06 18:26:59 +02:00
/**
* @ param int $id
* @ param false | int $timestamp
* @ return array | bool | mixed | null
* @ throws Exception
*/
2020-01-16 11:02:56 +01:00
public function updateTimestamp ( $id , $timestamp = false )
2018-07-19 11:48:22 +02:00
{
2021-10-18 13:20:12 +02:00
$object = [
'Object' => [
'id' => $id ,
'timestamp' => $timestamp === false ? time () : $timestamp ,
'skip_zmq' => 1 ,
'skip_kafka' => 1 ,
],
];
2022-10-12 20:32:39 +02:00
return $this -> save ( $object , [ 'fieldList' => [ 'timestamp' ], 'skipAuditLog' => true ]);
2018-07-19 11:48:22 +02:00
}
2017-09-04 23:12:46 +02:00
2018-07-19 11:48:22 +02:00
// Hunt down all LEDA and CASTOR clones
public function removeOrphanedObjects ()
{
$orphans = $this -> find ( 'list' , array (
'fields' => array ( 'Object.id' , 'Object.id' ),
'conditions' => array ( 'Event.id' => null ),
'contain' => array ( 'Event' => array ( 'fields' => array ( 'id' )))
));
foreach ( $orphans as $orphan ) {
$this -> delete ( $orphan );
}
return count ( $orphans );
}
2019-05-08 16:56:19 +02:00
2022-09-22 16:09:18 +02:00
/**
* @ param array $user
* @ param int $eventId
* @ param array $selectedAttributeIds
* @ return array | array []
* @ throws Exception
*/
public function validObjectsFromAttributeTypes ( array $user , $eventId , array $selectedAttributeIds )
2019-05-08 16:56:19 +02:00
{
2022-09-22 16:09:18 +02:00
$attributes = $this -> Attribute -> fetchAttributesSimple ( $user , [
'conditions' => [
'Attribute.id' => $selectedAttributeIds ,
'Attribute.event_id' => $eventId ,
'Attribute.object_id' => 0 ,
],
2022-10-01 13:52:43 +02:00
'fields' => [ 'Attribute.type' ],
2022-09-22 16:09:18 +02:00
]);
2019-05-17 16:02:06 +02:00
if ( empty ( $attributes )) {
2019-05-21 08:35:19 +02:00
return array ( 'templates' => array (), 'types' => array ());
2019-05-17 16:02:06 +02:00
}
2022-10-01 13:52:43 +02:00
$attributeTypes = array_column ( array_column ( $attributes , 'Attribute' ), 'type' );
return $this -> ObjectTemplate -> fetchPossibleTemplatesBasedOnTypes ( $attributeTypes );
2019-05-08 16:56:19 +02:00
}
2019-06-06 15:11:34 +02:00
2022-09-22 16:09:18 +02:00
public function groupAttributesIntoObject ( array $user , $event_id , array $object , $template , array $selected_attribute_ids , array $selected_object_relation_mapping , $hard_delete_attribute )
2019-06-06 15:11:34 +02:00
{
$saved_object_id = $this -> saveObject ( $object , $event_id , $template , $user );
if ( ! is_numeric ( $saved_object_id )) {
return $saved_object_id ;
}
$existing_attributes = $this -> Attribute -> fetchAttributes ( $user , array ( 'conditions' => array (
'Attribute.id' => $selected_attribute_ids ,
'Attribute.event_id' => $event_id ,
'Attribute.object_id' => 0
)));
if ( empty ( $existing_attributes )) {
return __ ( 'Selected Attributes do not exist.' );
}
$event = array ( 'Event' => $existing_attributes [ 0 ][ 'Event' ]);
2019-06-12 11:38:19 +02:00
// Duplicate the attribute and its context, otherwise connected instances will drop the duplicated UUID
2022-09-22 16:09:18 +02:00
foreach ( $existing_attributes as $existing_attribute ) {
2019-06-06 15:11:34 +02:00
if ( isset ( $selected_object_relation_mapping [ $existing_attribute [ 'Attribute' ][ 'id' ]])) {
$sightings = $this -> Event -> Sighting -> attachToEvent ( $event , $user , $existing_attribute [ 'Attribute' ][ 'id' ]);
$object_relation = $selected_object_relation_mapping [ $existing_attribute [ 'Attribute' ][ 'id' ]];
$created_attribute = $existing_attribute [ 'Attribute' ];
unset ( $created_attribute [ 'timestamp' ]);
unset ( $created_attribute [ 'id' ]);
unset ( $created_attribute [ 'uuid' ]);
$created_attribute [ 'object_relation' ] = $object_relation ;
2022-09-22 16:09:18 +02:00
$created_attribute [ 'object_id' ] = $saved_object_id ;
2019-06-06 15:11:34 +02:00
if ( isset ( $existing_attribute [ 'AttributeTag' ])) {
$created_attribute [ 'AttributeTag' ] = $existing_attribute [ 'AttributeTag' ];
}
if ( ! empty ( $sightings )) {
$created_attribute [ 'Sighting' ] = $sightings ;
}
2022-09-22 16:09:18 +02:00
$this -> Attribute -> captureAttribute ( $created_attribute , $event_id , $user , $saved_object_id );
2019-06-12 11:56:42 +02:00
$this -> Attribute -> deleteAttribute ( $existing_attribute [ 'Attribute' ][ 'id' ], $user , $hard_delete_attribute );
2019-06-06 15:11:34 +02:00
}
}
2022-09-22 16:09:18 +02:00
return $saved_object_id ;
2019-06-06 15:11:34 +02:00
}
2020-01-17 10:11:51 +01:00
public function resolveUpdatedTemplate ( $template , $object , $update_template_available = false )
{
$toReturn = array (
'updateable_attribute' => false ,
'not_updateable_attribute' => false ,
'newer_template_version' => false ,
2021-02-16 10:59:18 +01:00
'original_template_unkown' => false ,
2020-01-17 10:11:51 +01:00
'template' => $template
);
2021-02-16 10:59:18 +01:00
$newer_template = $this -> ObjectTemplate -> find ( 'first' , array (
'conditions' => array (
'ObjectTemplate.uuid' => $object [ 'Object' ][ 'template_uuid' ],
'ObjectTemplate.version >' => $object [ 'Object' ][ 'template_version' ],
),
'recursive' => - 1 ,
'contain' => array (
'ObjectTemplateElement'
),
'order' => array ( 'ObjectTemplate.version DESC' )
));
$template_difference = array ();
if ( ! empty ( $newer_template )) {
2023-10-24 09:26:41 +02:00
$toReturn [ 'newer_template_version' ] = $newer_template [ 'ObjectTemplate' ][ 'version' ];
2021-02-16 10:59:18 +01:00
$newer_template_temp = Hash :: remove ( Hash :: remove ( $newer_template [ 'ObjectTemplateElement' ], '{n}.id' ), '{n}.object_template_id' );
if ( ! empty ( $template )) {
// ignore IDs for comparison
$cur_template_temp = Hash :: remove ( Hash :: remove ( $template [ 'ObjectTemplateElement' ], '{n}.id' ), '{n}.object_template_id' );
2020-01-17 10:11:51 +01:00
2021-02-16 10:59:18 +01:00
// check how current template is included in the newer
foreach ( $cur_template_temp as $cur_obj_rel ) {
$flag_sim = false ;
foreach ( $newer_template_temp as $newer_obj_rel ) {
$tmp = Hash :: diff ( $cur_obj_rel , $newer_obj_rel );
if ( count ( $tmp ) == 0 ) {
$flag_sim = true ;
break ;
}
}
if ( ! $flag_sim ) {
$template_difference [] = $cur_obj_rel ;
}
}
} else { // original template unkown
$toReturn [ 'original_template_unkown' ] = true ;
$unmatched_attributes = array ();
foreach ( $object [ 'Attribute' ] as $i => $attribute ) {
$flag_match = false ;
foreach ( $newer_template_temp as $newer_obj_rel ) {
2020-01-17 10:11:51 +01:00
if (
2021-02-16 10:59:18 +01:00
$newer_obj_rel [ 'object_relation' ] == $attribute [ 'object_relation' ] &&
$newer_obj_rel [ 'type' ] == $attribute [ 'type' ]
) {
$flag_match = true ;
break ;
2020-01-17 10:11:51 +01:00
}
}
2021-02-16 10:59:18 +01:00
if ( ! $flag_match ) {
$unmatched_attributes [] = $attribute ;
}
}
// simulate unkown template from the attribute
foreach ( $unmatched_attributes as $unmatched_attribute ) {
$template_difference [] = [
'object_relation' => $unmatched_attribute [ 'object_relation' ],
'type' => $unmatched_attribute [ 'type' ]
];
2020-01-17 10:11:51 +01:00
}
}
2021-02-16 10:59:18 +01:00
$toReturn [ 'updateable_attribute' ] = $object [ 'Attribute' ];
$toReturn [ 'not_updateable_attribute' ] = array ();
2022-09-23 15:03:46 +02:00
if ( ! empty ( $template_difference )) { // older template not completely embeded in newer
foreach ( $template_difference as $temp_diff_element ) {
foreach ( $object [ 'Attribute' ] as $i => $attribute ) {
if (
$attribute [ 'object_relation' ] == $temp_diff_element [ 'object_relation' ]
&& $attribute [ 'type' ] == $temp_diff_element [ 'type' ]
) { // This attribute cannot be merged automatically
$attribute [ 'merge-possible' ] = false ;
$toReturn [ 'not_updateable_attribute' ][] = $attribute ;
unset ( $toReturn [ 'updateable_attribute' ][ $i ]);
}
2021-02-16 10:59:18 +01:00
}
}
2020-01-17 10:11:51 +01:00
}
}
2022-09-23 15:03:46 +02:00
2021-02-16 10:59:18 +01:00
if ( $update_template_available ) { // template version bump requested
$toReturn [ 'template' ] = $newer_template ; // bump the template version
}
2020-01-17 10:11:51 +01:00
return $toReturn ;
}
2022-09-23 15:03:46 +02:00
public function reviseObject ( $revised_object , $object , $template )
{
2020-01-17 10:11:51 +01:00
$revised_object_both = array ( 'mergeable' => array (), 'notMergeable' => array ());
// Loop through attributes to inject and perform the correct action
// (inject, duplicate, add warnings, ...) when applicable
foreach ( $revised_object [ 'Attribute' ] as $attribute_to_inject ) {
$flag_no_collision = true ;
foreach ( $object [ 'Attribute' ] as $attribute ) {
if (
$attribute [ 'object_relation' ] == $attribute_to_inject [ 'object_relation' ]
&& $attribute [ 'type' ] == $attribute_to_inject [ 'type' ]
&& $attribute [ 'value' ] !== $attribute_to_inject [ 'value' ]
) { // Collision on value
$multiple = ! empty ( Hash :: extract ( $template [ 'ObjectTemplateElement' ], sprintf ( '{n}[object_relation=%s][type=%s][multiple=true]' , $attribute [ 'object_relation' ], $attribute [ 'type' ])));
if ( $multiple ) { // if multiple is set, check if an entry exists already
$flag_entry_exists = false ;
foreach ( $object [ 'Attribute' ] as $attr ) {
if (
$attr [ 'object_relation' ] == $attribute_to_inject [ 'object_relation' ]
&& $attr [ 'type' ] == $attribute_to_inject [ 'type' ]
&& $attr [ 'value' ] === $attribute_to_inject [ 'value' ]
) {
$flag_entry_exists = true ;
break ;
}
}
if ( ! $flag_entry_exists ) { // entry does no exists, can be duplicated
$attribute_to_inject [ 'is_multiple' ] = true ;
$revised_object_both [ 'mergeable' ][] = $attribute_to_inject ;
$object [ 'Attribute' ][] = $attribute_to_inject ;
}
} else { // Collision on value, multiple not set => propose overwrite
$attribute_to_inject [ 'current_value' ] = $attribute [ 'value' ];
$attribute_to_inject [ 'merge-possible' ] = true ; // the user can still swap value
$revised_object_both [ 'notMergeable' ][] = $attribute_to_inject ;
}
$flag_no_collision = false ;
} else if (
$attribute [ 'object_relation' ] == $attribute_to_inject [ 'object_relation' ]
&& $attribute [ 'type' ] == $attribute_to_inject [ 'type' ]
&& $attribute [ 'value' ] === $attribute_to_inject [ 'value' ]
) { // all good, they are basically the same, do nothing
$revised_object_both [ 'mergeable' ][] = $attribute_to_inject ;
$flag_no_collision = false ;
}
}
if ( $flag_no_collision ) { // no collision, nor equalities => inject it straight away
$revised_object_both [ 'mergeable' ][] = $attribute_to_inject ;
$object [ 'Attribute' ][] = $attribute_to_inject ;
}
}
return array (
'object' => $object ,
'revised_object_both' => $revised_object_both
);
}
2020-02-29 08:57:32 +01:00
public function restSearch ( $user , $returnFormat , $filters , $paramsOnly = false , $jobId = false , & $elementCounter = 0 , & $renderView = false )
{
if ( ! isset ( $this -> validFormats [ $returnFormat ][ 1 ])) {
throw new NotFoundException ( 'Invalid output format.' );
}
App :: uses ( $this -> validFormats [ $returnFormat ][ 1 ], 'Export' );
$exportTool = new $this -> validFormats [ $returnFormat ][ 1 ]();
if ( empty ( $exportTool -> non_restrictive_export )) {
if ( ! isset ( $filters [ 'to_ids' ])) {
$filters [ 'to_ids' ] = 1 ;
}
if ( ! isset ( $filters [ 'published' ])) {
$filters [ 'published' ] = 1 ;
}
$filters [ 'allow_proposal_blocking' ] = 1 ;
}
if ( ! empty ( $filters [ 'quickFilter' ])) {
$filters [ 'searchall' ] = $filters [ 'quickFilter' ];
if ( ! empty ( $filters [ 'value' ])) {
unset ( $filters [ 'value' ]);
}
}
if ( ! empty ( $exportTool -> renderView )) {
$renderView = $exportTool -> renderView ;
}
if ( isset ( $filters [ 'searchall' ])) {
if ( ! empty ( $filters [ 'value' ])) {
$filters [ 'wildcard' ] = $filters [ 'value' ];
unset ( $filters [ 'value' ]);
} else {
$filters [ 'wildcard' ] = $filters [ 'searchall' ];
}
}
$subqueryElements = $this -> Event -> harvestSubqueryElements ( $filters );
2020-11-18 12:07:29 +01:00
$filters = $this -> Event -> addFiltersFromSubqueryElements ( $filters , $subqueryElements , $user );
2020-05-29 15:23:27 +02:00
$filters = $this -> Event -> addFiltersFromUserSettings ( $user , $filters );
2020-09-20 18:18:46 +02:00
$conditions = $this -> buildFilterConditions ( $filters );
2020-02-29 08:57:32 +01:00
$params = array (
2020-09-20 18:18:46 +02:00
'conditions' => $conditions ,
'fields' => array ( 'Attribute.*' , 'Event.org_id' , 'Event.distribution' , 'Object.*' ),
'withAttachments' => ! empty ( $filters [ 'withAttachments' ]) ? $filters [ 'withAttachments' ] : 0 ,
'enforceWarninglist' => ! empty ( $filters [ 'enforceWarninglist' ]) ? $filters [ 'enforceWarninglist' ] : 0 ,
'includeAllTags' => ! empty ( $filters [ 'includeAllTags' ]) ? $filters [ 'includeAllTags' ] : 0 ,
'includeEventUuid' => ! empty ( $filters [ 'includeEventUuid' ]) ? $filters [ 'includeEventUuid' ] : 0 ,
'includeEventTags' => ! empty ( $filters [ 'includeEventTags' ]) ? $filters [ 'includeEventTags' ] : 0 ,
'includeProposals' => ! empty ( $filters [ 'includeProposals' ]) ? $filters [ 'includeProposals' ] : 0 ,
'includeWarninglistHits' => ! empty ( $filters [ 'includeWarninglistHits' ]) ? $filters [ 'includeWarninglistHits' ] : 0 ,
'includeContext' => ! empty ( $filters [ 'includeContext' ]) ? $filters [ 'includeContext' ] : 0 ,
'includeSightings' => ! empty ( $filters [ 'includeSightings' ]) ? $filters [ 'includeSightings' ] : 0 ,
'includeSightingdb' => ! empty ( $filters [ 'includeSightingdb' ]) ? $filters [ 'includeSightingdb' ] : 0 ,
'includeCorrelations' => ! empty ( $filters [ 'includeCorrelations' ]) ? $filters [ 'includeCorrelations' ] : 0 ,
'includeDecayScore' => ! empty ( $filters [ 'includeDecayScore' ]) ? $filters [ 'includeDecayScore' ] : 0 ,
'includeFullModel' => ! empty ( $filters [ 'includeFullModel' ]) ? $filters [ 'includeFullModel' ] : 0 ,
'allow_proposal_blocking' => ! empty ( $filters [ 'allow_proposal_blocking' ]) ? $filters [ 'allow_proposal_blocking' ] : 0 ,
'metadata' => ! empty ( $filters [ 'metadata' ]) ? $filters [ 'metadata' ] : 0 ,
2020-02-29 08:57:32 +01:00
);
if ( ! empty ( $filters [ 'attackGalaxy' ])) {
$params [ 'attackGalaxy' ] = $filters [ 'attackGalaxy' ];
}
if ( isset ( $filters [ 'include_event_uuid' ])) {
$params [ 'includeEventUuid' ] = $filters [ 'include_event_uuid' ];
}
if ( isset ( $filters [ 'limit' ])) {
$params [ 'limit' ] = $filters [ 'limit' ];
if ( ! isset ( $filters [ 'page' ])) {
$filters [ 'page' ] = 1 ;
}
}
if ( isset ( $filters [ 'page' ])) {
$params [ 'page' ] = $filters [ 'page' ];
}
if ( ! empty ( $filters [ 'deleted' ])) {
$params [ 'deleted' ] = $filters [ 'deleted' ];
}
if ( ! empty ( $filters [ 'excludeDecayed' ])) {
$params [ 'excludeDecayed' ] = $filters [ 'excludeDecayed' ];
$params [ 'includeDecayScore' ] = 1 ;
}
if ( ! empty ( $filters [ 'decayingModel' ])) {
$params [ 'decayingModel' ] = $filters [ 'decayingModel' ];
}
if ( ! empty ( $filters [ 'modelOverrides' ])) {
$params [ 'modelOverrides' ] = $filters [ 'modelOverrides' ];
}
if ( ! empty ( $filters [ 'score' ])) {
$params [ 'score' ] = $filters [ 'score' ];
}
2020-05-06 09:32:44 +02:00
if ( ! empty ( $filters [ 'metadata' ])) {
$params [ 'metadata' ] = $filters [ 'metadata' ];
}
2020-02-29 08:57:32 +01:00
if ( $paramsOnly ) {
return $params ;
}
if ( method_exists ( $exportTool , 'modify_params' )) {
$params = $exportTool -> modify_params ( $user , $params );
}
$exportToolParams = array (
'user' => $user ,
'params' => $params ,
'returnFormat' => $returnFormat ,
'scope' => 'Object' ,
'filters' => $filters
);
if ( ! empty ( $exportTool -> additional_params )) {
$params = array_merge_recursive (
$params ,
$exportTool -> additional_params
);
}
2020-06-19 16:21:53 +02:00
$tmpfile = new TmpFileTool ();
$tmpfile -> write ( $exportTool -> header ( $exportToolParams ));
2020-02-29 08:57:32 +01:00
$loop = false ;
if ( empty ( $params [ 'limit' ])) {
$memory_in_mb = $this -> convert_to_memory_limit_to_mb ( ini_get ( 'memory_limit' ));
$default_attribute_memory_coefficient = Configure :: check ( 'MISP.default_attribute_memory_coefficient' ) ? Configure :: read ( 'MISP.default_attribute_memory_coefficient' ) : 80 ;
$memory_scaling_factor = isset ( $exportTool -> memory_scaling_factor ) ? $exportTool -> memory_scaling_factor : $default_attribute_memory_coefficient ;
$params [ 'limit' ] = $memory_in_mb * $memory_scaling_factor / 10 ;
$loop = true ;
$params [ 'page' ] = 1 ;
}
$this -> __iteratedFetch ( $user , $params , $loop , $tmpfile , $exportTool , $exportToolParams , $elementCounter );
2020-06-19 16:21:53 +02:00
$tmpfile -> write ( $exportTool -> footer ( $exportToolParams ));
2020-10-25 09:44:01 +01:00
return $tmpfile ;
2020-02-29 08:57:32 +01:00
}
2020-06-19 16:21:53 +02:00
private function __iteratedFetch ( $user , & $params , & $loop , TmpFileTool $tmpfile , $exportTool , $exportToolParams , & $elementCounter = 0 )
2020-02-29 08:57:32 +01:00
{
$continue = true ;
while ( $continue ) {
2020-05-07 07:58:51 +02:00
$temp = '' ;
2020-09-01 15:59:38 +02:00
$this -> Allowedlist = ClassRegistry :: init ( 'Allowedlist' );
2020-02-29 08:57:32 +01:00
$results = $this -> fetchObjects ( $user , $params , $continue );
2020-05-06 14:22:05 +02:00
if ( empty ( $results )) {
$loop = false ;
return true ;
}
2020-05-07 07:58:51 +02:00
if ( $elementCounter !== 0 && ! empty ( $results )) {
$temp .= $exportTool -> separator ( $exportToolParams );
}
2020-02-29 08:57:32 +01:00
if ( $params [ 'includeSightingdb' ]) {
$this -> Sightingdb = ClassRegistry :: init ( 'Sightingdb' );
$results = $this -> Sightingdb -> attachToObjects ( $results , $user );
}
$params [ 'page' ] += 1 ;
2024-01-19 18:11:28 +01:00
foreach ( $results as $k => $result ) {
$results [ $k ][ 'Attribute' ] = $this -> Allowedlist -> removeAllowedlistedFromArray ( $result [ 'Attribute' ], true );
}
2020-02-29 08:57:32 +01:00
$results = array_values ( $results );
$i = 0 ;
foreach ( $results as $object ) {
$elementCounter ++ ;
$handlerResult = $exportTool -> handler ( $object , $exportToolParams );
$temp .= $handlerResult ;
if ( $handlerResult !== '' ) {
if ( $i != count ( $results ) - 1 ) {
$temp .= $exportTool -> separator ( $exportToolParams );
}
}
$i ++ ;
}
if ( ! $loop ) {
$continue = false ;
}
2020-06-19 16:21:53 +02:00
$tmpfile -> write ( $temp );
2020-02-29 08:57:32 +01:00
}
return true ;
}
2020-11-11 09:56:05 +01:00
private function attributeValueDifferent ( $newValue , $originalValue , $field )
{
if ( in_array ( $field , [ 'first_seen' , 'last_seen' ])) {
return new DateTime ( $newValue ) != new DateTime ( $originalValue );
} else {
return $newValue != $originalValue ;
}
}
2023-03-23 15:03:58 +01:00
private function getObjectAttributeHash ( $attribute )
{
if ( $attribute [ 'type' ] === 'malware-sample' ) {
if ( strpos ( $attribute [ 'value' ], '|' ) === false && ! empty ( $attribute [ 'data' ])) {
$attribute [ 'value' ] = $attribute [ 'value' ] . '|' . md5 ( base64_decode ( $attribute [ 'data' ]));
}
}
$attributeValueAfterModification = AttributeValidationTool :: modifyBeforeValidation ( $attribute [ 'type' ], $attribute [ 'value' ]);
$attributeValueAfterModification = $this -> Attribute -> runRegexp ( $attribute [ 'type' ], $attributeValueAfterModification );
return sha1 ( $attribute [ 'object_relation' ] . $attribute [ 'category' ] . $attribute [ 'type' ] . $attributeValueAfterModification , true );
}
2017-06-13 12:08:26 +02:00
}