2017-06-13 12:08:26 +02:00
< ? php
App :: uses ( 'AppModel' , 'Model' );
2021-10-30 22:04:49 +02:00
App :: uses ( 'FileAccessTool' , 'Tools' );
2017-06-13 12:08:26 +02:00
2021-10-30 22:04:49 +02:00
/**
* @ property ObjectTemplateElement $ObjectTemplateElement
*/
2018-07-19 11:48:22 +02:00
class ObjectTemplate extends AppModel
{
public $actsAs = array (
2021-01-22 13:01:23 +01:00
'AuditLog' ,
2018-07-19 11:48:22 +02:00
'Containable' ,
'SysLogLogable.SysLogLogable' => array ( // TODO Audit, logable
'userModel' => 'User' ,
'userKey' => 'user_id' ,
'change' => 'full' ),
);
2017-06-13 12:08:26 +02:00
2018-07-19 11:48:22 +02:00
public $belongsTo = array (
'User' => array (
'className' => 'User' ,
'foreignKey' => 'user_id'
),
'Organisation' => array (
2022-05-05 14:13:12 +02:00
'className' => 'Organisation' ,
'foreignKey' => 'org_id'
2018-07-19 11:48:22 +02:00
)
);
public $hasMany = array (
'ObjectTemplateElement' => array (
'className' => 'ObjectTemplateElement' ,
'dependent' => true ,
)
);
2017-06-13 12:08:26 +02:00
2022-05-05 14:13:12 +02:00
const OBJECTS_DIR = APP . 'files/misp-objects/objects' ;
2021-01-27 10:43:31 +01:00
2018-07-19 11:48:22 +02:00
public function afterFind ( $results , $primary = false )
{
foreach ( $results as $k => $result ) {
2021-10-30 20:16:32 +02:00
if ( isset ( $result [ 'ObjectTemplate' ][ 'requirements' ])) {
$results [ $k ][ 'ObjectTemplate' ][ 'requirements' ] = json_decode ( $result [ 'ObjectTemplate' ][ 'requirements' ], true );
2018-07-19 11:48:22 +02:00
}
}
return $results ;
}
2017-07-02 00:05:15 +02:00
2018-07-19 11:48:22 +02:00
public function beforeSave ( $options = array ())
{
$this -> data [ 'ObjectTemplate' ][ 'requirements' ] = empty ( $this -> data [ 'ObjectTemplate' ][ 'requirements' ]) ? '[]' : json_encode ( $this -> data [ 'ObjectTemplate' ][ 'requirements' ]);
return true ;
}
2017-07-02 00:05:15 +02:00
2019-04-10 08:29:28 +02:00
public function update ( $user = false , $type = false , $force = false )
2018-07-19 11:48:22 +02:00
{
2021-01-27 10:43:31 +01:00
$directories = $this -> getTemplateDirectoryPaths ();
2018-07-19 11:48:22 +02:00
foreach ( $directories as $k => $dir ) {
2022-05-05 14:13:12 +02:00
$dir = str_replace ( self :: OBJECTS_DIR , '' , $dir );
2018-07-19 11:48:22 +02:00
$directories [ $k ] = $dir ;
}
$updated = array ();
foreach ( $directories as $dir ) {
if ( $type && '/' . $type != $dir ) {
continue ;
}
2022-05-05 14:13:12 +02:00
if ( ! file_exists ( self :: OBJECTS_DIR . DS . $dir . DS . 'definition.json' )) {
2018-07-19 11:48:22 +02:00
continue ;
}
2022-05-05 14:13:12 +02:00
$template = FileAccessTool :: readJsonFromFile ( self :: OBJECTS_DIR . DS . $dir . DS . 'definition.json' );
2018-07-19 11:48:22 +02:00
if ( ! isset ( $template [ 'version' ])) {
$template [ 'version' ] = 1 ;
}
$current = $this -> find ( 'first' , array (
'fields' => array ( 'MAX(version) AS version' , 'uuid' ),
'conditions' => array ( 'uuid' => $template [ 'uuid' ]),
'recursive' => - 1 ,
'group' => array ( 'uuid' )
));
if ( ! empty ( $current )) {
$current [ 'ObjectTemplate' ][ 'version' ] = $current [ 0 ][ 'version' ];
}
if ( $force || empty ( $current ) || $template [ 'version' ] > $current [ 'ObjectTemplate' ][ 'version' ]) {
$result = $this -> __updateObjectTemplate ( $template , $current , $user );
if ( $result === true ) {
$temp = array ( 'name' => $template [ 'name' ], 'new' => $template [ 'version' ]);
if ( ! empty ( $current )) {
$temp [ 'old' ] = $current [ 'ObjectTemplate' ][ 'version' ];
}
$updated [ 'success' ][] = $temp ;
} else {
$updated [ 'fails' ][] = array ( 'name' => $template [ 'name' ], 'fail' => json_encode ( $result ));
}
}
}
return $updated ;
}
2017-06-13 12:08:26 +02:00
2019-04-10 08:29:28 +02:00
private function __updateObjectTemplate ( $template , $current , $user = false )
2018-07-19 11:48:22 +02:00
{
$template [ 'requirements' ] = array ();
$requirementFields = array ( 'required' , 'requiredOneOf' );
foreach ( $requirementFields as $field ) {
if ( isset ( $template [ $field ])) {
$template [ 'requirements' ][ $field ] = $template [ $field ];
}
}
2019-04-10 08:29:28 +02:00
if ( ! empty ( $user )) {
$template [ 'user_id' ] = $user [ 'id' ];
$template [ 'org_id' ] = $user [ 'org_id' ];
} else {
$template [ 'user_id' ] = 0 ;
$template [ 'org_id' ] = 0 ;
}
2018-07-19 11:48:22 +02:00
$template [ 'fixed' ] = 1 ;
$this -> create ();
$result = $this -> save ( $template );
if ( ! $result ) {
return $this -> validationErrors ;
}
$id = $this -> id ;
$this -> setActive ( $id );
2021-10-30 22:04:49 +02:00
$attributes = [];
2018-07-19 11:48:22 +02:00
foreach ( $template [ 'attributes' ] as $k => $attribute ) {
$attribute = $this -> __convertJSONToElement ( $attribute );
2021-10-30 22:04:49 +02:00
$attribute [ 'object_relation' ] = $k ;
2018-07-19 11:48:22 +02:00
$attribute [ 'object_template_id' ] = $id ;
2021-10-30 22:04:49 +02:00
$attributes [] = [ 'ObjectTemplateElement' => $attribute ];
2018-07-19 11:48:22 +02:00
}
2021-10-30 22:04:49 +02:00
$this -> ObjectTemplateElement -> saveMany ( $attributes );
2018-07-19 11:48:22 +02:00
return true ;
}
2017-07-02 00:05:15 +02:00
2018-07-19 11:48:22 +02:00
private function __convertJSONToElement ( $attribute )
{
$result = array ();
$translation_table = array (
2021-10-30 22:04:49 +02:00
'misp-usage-frequency' => 'frequency' ,
'misp-attribute' => 'type' ,
'description' => 'description' ,
'ui-priority' => 'ui-priority' ,
'type' => 'type' ,
'disable_correlation' => 'disable_correlation' ,
'object_relation' => 'object_relation' ,
'categories' => 'categories' ,
'sane_default' => 'sane_default' ,
'values_list' => 'values_list' ,
'multiple' => 'multiple'
2018-07-19 11:48:22 +02:00
);
foreach ( $translation_table as $from => $to ) {
if ( isset ( $attribute [ $from ])) {
$result [ $to ] = $attribute [ $from ];
}
}
return $result ;
}
2017-07-02 20:32:30 +02:00
2018-07-19 11:48:22 +02:00
public function checkTemplateConformity ( $template , $attributes )
{
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ])) {
// check for all required attributes
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'required' ])) {
foreach ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'required' ] as $requiredField ) {
$found = false ;
foreach ( $attributes [ 'Attribute' ] as $attribute ) {
if ( $attribute [ 'object_relation' ] == $requiredField ) {
$found = true ;
}
}
if ( ! $found ) {
return 'Could not save the object as a required attribute is not set (' . $requiredField . ')' ;
}
}
}
// check for all required one of attributes
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'requiredOneOf' ])) {
$found = false ;
foreach ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'requiredOneOf' ] as $requiredField ) {
foreach ( $attributes [ 'Attribute' ] as $attribute ) {
if ( $attribute [ 'object_relation' ] == $requiredField ) {
$found = true ;
}
}
}
if ( ! $found ) {
return 'Could not save the object as it requires at least one of the following attributes to be set: ' . implode ( ', ' , $template [ 'ObjectTemplate' ][ 'requirements' ][ 'requiredOneOf' ]);
}
}
}
// check the multiple flag is adhered to
foreach ( $template [ 'ObjectTemplateElement' ] as $template_attribute ) {
if ( $template_attribute [ 'multiple' ] !== true ) {
$found_relations = array ();
foreach ( $attributes [ 'Attribute' ] as $attribute ) {
if ( $attribute [ 'object_relation' ] == $template_attribute [ 'object_relation' ]) {
if ( ! isset ( $found_relations [ $attribute [ 'object_relation' ]])) {
$found_relations [ $attribute [ 'object_relation' ]] = true ;
} else {
return 'Could not save the object as a unique relationship within the object was assigned to more than one attribute. This is only allowed if the multiple flag is set in the object template.' ;
}
}
}
}
}
return true ;
}
2017-09-07 12:20:20 +02:00
2019-05-08 16:56:19 +02:00
public function checkTemplateConformityBasedOnTypes ( $template , $attributes )
{
2019-05-17 16:02:06 +02:00
$to_return = array ( 'valid' => true , 'missingTypes' => array ());
2019-05-08 16:56:19 +02:00
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ])) {
// check for all required attributes
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'required' ])) {
foreach ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'required' ] as $requiredField ) {
$requiredType = Hash :: extract ( $template [ 'ObjectTemplateElement' ], sprintf ( '{n}[object_relation=%s].type' , $requiredField ))[ 0 ];
$found = false ;
foreach ( $attributes as $attribute ) {
if ( $attribute [ 'Attribute' ][ 'type' ] == $requiredType ) {
$found = true ;
}
}
if ( ! $found ) {
2019-05-17 16:02:06 +02:00
$to_return = array ( 'valid' => false , 'missingTypes' => array ( $requiredType ));
2019-05-08 16:56:19 +02:00
}
}
}
// check for all required one of attributes
if ( ! empty ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'requiredOneOf' ])) {
$found = false ;
$all_required_type = array ();
foreach ( $template [ 'ObjectTemplate' ][ 'requirements' ][ 'requiredOneOf' ] as $requiredField ) {
2019-05-24 10:45:19 +02:00
$requiredType = Hash :: extract ( $template [ 'ObjectTemplateElement' ], sprintf ( '{n}[object_relation=%s].type' , $requiredField ));
$requiredType = empty ( $requiredType ) ? NULL : $requiredType [ 0 ];
2019-05-08 16:56:19 +02:00
$all_required_type [] = $requiredType ;
foreach ( $attributes as $attribute ) {
if ( $attribute [ 'Attribute' ][ 'type' ] == $requiredType ) {
$found = true ;
}
}
}
if ( ! $found ) {
2019-05-17 16:02:06 +02:00
$to_return = array ( 'valid' => false , 'missingTypes' => $all_required_type );
2019-05-08 16:56:19 +02:00
}
}
}
2019-05-17 16:02:06 +02:00
2019-06-12 11:38:19 +02:00
// at this point, an object could created; checking if all attribute are valids
2019-05-17 16:02:06 +02:00
$valid_types = array ();
$to_return [ 'invalidTypes' ] = array ();
$to_return [ 'invalidTypesMultiple' ] = array ();
foreach ( $template [ 'ObjectTemplateElement' ] as $templateElement ) {
$valid_types [ $templateElement [ 'type' ]] = $templateElement [ 'multiple' ];
}
$check_for_multiple_type = array ();
foreach ( $attributes as $attribute ) {
if ( isset ( $valid_types [ $attribute [ 'Attribute' ][ 'type' ]])) {
if ( ! $valid_types [ $attribute [ 'Attribute' ][ 'type' ]]) { // is not multiple
if ( isset ( $check_for_multiple_type [ $attribute [ 'Attribute' ][ 'type' ]])) {
$to_return [ 'invalidTypesMultiple' ][] = $attribute [ 'Attribute' ][ 'type' ];
} else {
$check_for_multiple_type [ $attribute [ 'Attribute' ][ 'type' ]] = 1 ;
}
}
} else {
$to_return [ 'invalidTypes' ][] = $attribute [ 'Attribute' ][ 'type' ];
}
}
$to_return [ 'invalidTypes' ] = array_unique ( $to_return [ 'invalidTypes' ]);
$to_return [ 'invalidTypesMultiple' ] = array_unique ( $to_return [ 'invalidTypesMultiple' ]);
if ( ! empty ( $to_return [ 'invalidTypesMultiple' ])) {
$to_return [ 'valid' ] = false ;
}
return $to_return ;
2019-05-08 16:56:19 +02:00
}
2018-07-19 11:48:22 +02:00
// simple test to see if there are any object templates - if not trigger update
2022-05-05 14:13:12 +02:00
public function populateIfEmpty ( array $user )
2018-07-19 11:48:22 +02:00
{
2022-05-15 11:29:12 +02:00
if ( ! $this -> hasAny ()) {
2018-07-19 11:48:22 +02:00
$this -> update ( $user );
}
}
2017-09-18 00:38:30 +02:00
2018-07-19 11:48:22 +02:00
public function setActive ( $id )
{
$template = $this -> find ( 'first' , array (
'recursive' => - 1 ,
2021-10-30 22:04:49 +02:00
'conditions' => array ( 'ObjectTemplate.id' => $id ),
'fields' => [ 'ObjectTemplate.id' , 'ObjectTemplate.uuid' , 'ObjectTemplate.active' ],
2018-07-19 11:48:22 +02:00
));
if ( empty ( $template )) {
return false ;
}
if ( $template [ 'ObjectTemplate' ][ 'active' ]) {
$template [ 'ObjectTemplate' ][ 'active' ] = 0 ;
2021-10-30 22:04:49 +02:00
$this -> save ( $template , true , [ 'active' ]);
2018-07-19 11:48:22 +02:00
return 0 ;
}
$similar_templates = $this -> find ( 'all' , array (
'recursive' => - 1 ,
2021-10-30 22:04:49 +02:00
'fields' => [ 'ObjectTemplate.id' ],
2018-07-19 11:48:22 +02:00
'conditions' => array (
'ObjectTemplate.uuid' => $template [ 'ObjectTemplate' ][ 'uuid' ],
'NOT' => array (
'ObjectTemplate.id' => $template [ 'ObjectTemplate' ][ 'id' ]
)
)
));
$template [ 'ObjectTemplate' ][ 'active' ] = 1 ;
2021-10-30 22:04:49 +02:00
$this -> save ( $template , true , [ 'active' ]);
2018-07-19 11:48:22 +02:00
foreach ( $similar_templates as $st ) {
$st [ 'ObjectTemplate' ][ 'active' ] = 0 ;
2021-10-30 22:04:49 +02:00
$this -> save ( $st , true , [ 'active' ]);
2018-07-19 11:48:22 +02:00
}
return 1 ;
}
2020-11-30 16:29:31 +01:00
public function getRawFromDisk ( $uuidOrName )
{
if ( Validation :: uuid ( $uuidOrName )) {
2021-01-27 10:43:31 +01:00
foreach ( $this -> readTemplatesFromDisk () as $templateFromDisk ) {
2021-10-30 22:04:49 +02:00
if ( $templateFromDisk [ 'uuid' ] === $uuidOrName ) {
return $templateFromDisk ;
2020-11-30 16:29:31 +01:00
}
}
2021-01-27 10:43:31 +01:00
} else {
$allTemplateNames = $this -> getTemplateDirectoryPaths ( false );
2021-10-30 22:04:49 +02:00
if ( in_array ( $uuidOrName , $allTemplateNames , true )) { // ensure the path is not out of scope
return $this -> readTemplateFromDisk ( $this -> getFullPathFromTemplateName ( $uuidOrName ));
2021-01-27 10:43:31 +01:00
}
2020-11-30 16:29:31 +01:00
}
2021-10-30 22:04:49 +02:00
return [];
2020-11-30 16:29:31 +01:00
}
2021-10-30 22:04:49 +02:00
/**
* @ throws Exception
*/
2021-01-27 10:43:31 +01:00
private function readTemplateFromDisk ( $path )
2020-11-30 16:29:31 +01:00
{
2021-10-30 22:04:49 +02:00
if ( ! file_exists ( $path )) {
2021-01-27 10:43:31 +01:00
return false ;
2020-11-30 16:29:31 +01:00
}
2022-05-05 14:13:12 +02:00
return FileAccessTool :: readJsonFromFile ( $path );
2021-01-27 10:43:31 +01:00
}
private function readTemplatesFromDisk ()
{
foreach ( $this -> getTemplateDirectoryPaths () as $dirpath ) {
$filepath = $dirpath . DS . 'definition.json' ;
$template = $this -> readTemplateFromDisk ( $filepath );
2020-11-30 16:29:31 +01:00
if ( isset ( $template [ 'uuid' ])) {
2021-01-27 10:43:31 +01:00
yield $template ;
2020-11-30 16:29:31 +01:00
}
}
2021-01-27 10:43:31 +01:00
}
private function getTemplateDirectoryPaths ( $fullPath = true )
{
2022-05-05 14:13:12 +02:00
$dir = new Folder ( self :: OBJECTS_DIR , false );
2021-01-27 10:43:31 +01:00
return $dir -> read ( true , false , $fullPath )[ 0 ];
}
private function getFullPathFromTemplateName ( $templateName )
{
2022-05-05 14:13:12 +02:00
return self :: OBJECTS_DIR . DS . $templateName . DS . 'definition.json' ;
2020-11-30 16:29:31 +01:00
}
2017-06-13 12:08:26 +02:00
}