MISP/app/Model/GalaxyClusterRelation.php

543 lines
25 KiB
PHP

<?php
App::uses('AppModel', 'Model');
/**
* @property GalaxyClusterRelationTag $GalaxyClusterRelationTag
* @property GalaxyCluster $TargetCluster
* @property SharingGroup $SharingGroup
*/
class GalaxyClusterRelation extends AppModel
{
public $useTable = 'galaxy_cluster_relations';
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'),
'Containable',
);
public $validate = array(
'referenced_galaxy_cluster_type' => array(
'stringNotEmpty' => array(
'rule' => array('stringNotEmpty')
)
),
'galaxy_cluster_uuid' => array(
'uuid' => array(
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
),
),
'referenced_galaxy_cluster_uuid' => array(
'uuid' => array(
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
),
),
'distribution' => array(
'rule' => array('inList', array('0', '1', '2', '3', '4')),
'message' => 'Options: Your organisation only, This community only, Connected communities, All communities, Sharing group',
'required' => true
)
);
public $belongsTo = array(
'SourceCluster' => array(
'className' => 'GalaxyCluster',
'foreignKey' => 'galaxy_cluster_id',
),
'TargetCluster' => array(
'className' => 'GalaxyCluster',
'foreignKey' => 'referenced_galaxy_cluster_id',
),
'SharingGroup' => array(
'className' => 'SharingGroup',
'foreignKey' => 'sharing_group_id'
),
);
public $hasMany = array(
'GalaxyClusterRelationTag' => array('dependent' => true),
);
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $result) {
if (isset($result['TargetCluster']) && key_exists('id', $result['TargetCluster']) && is_null($result['TargetCluster']['id'])) {
$results[$k]['TargetCluster'] = array();
}
if (isset($result['GalaxyClusterRelation']['distribution']) && $result['GalaxyClusterRelation']['distribution'] != 4) {
unset($results[$k]['SharingGroup']);
}
}
return $results;
}
public function buildConditions($user, $clusterConditions = true)
{
$conditions = [];
if (!$user['Role']['perm_site_admin']) {
$alias = $this->alias;
$sgids = $this->SharingGroup->authorizedIds($user);
$gcOwnerIds = $this->SourceCluster->cacheGalaxyClusterOwnerIDs($user);
$conditionsRelations['AND']['OR'] = [
"${alias}.galaxy_cluster_id" => $gcOwnerIds,
[
'AND' => [
"${alias}.distribution >" => 0,
"${alias}.distribution <" => 4
],
],
[
'AND' => [
"${alias}.sharing_group_id" => $sgids,
"${alias}.distribution" => 4
]
]
];
$conditionsSourceCluster = $clusterConditions ? $this->SourceCluster->buildConditions($user) : [];
$conditions = [
'AND' => [
$conditionsRelations,
$conditionsSourceCluster
]
];
}
return $conditions;
}
public function fetchRelations($user, $options, $full=false)
{
$params = array(
'conditions' => $this->buildConditions($user),
'recursive' => -1
);
if (!empty($options['contain'])) {
$params['contain'] = $options['contain'];
} elseif ($full) {
$params['contain'] = array('SharingGroup', 'SourceCluster', 'TargetCluster');
}
if (empty($params['contain'])) {
$params['contain'] = ['SourceCluster'];
}
if (!in_array('SourceCluster', $params['contain'])) {
$params['contain'][] = 'SourceCluster';
}
if (isset($options['fields'])) {
$params['fields'] = $options['fields'];
}
if (isset($options['conditions'])) {
$params['conditions']['AND'][] = $options['conditions'];
}
if (isset($options['group'])) {
$params['group'] = empty($options['group']) ? $options['group'] : false;
}
$relations = $this->find('all', $params);
return $relations;
}
public function getExistingRelationships()
{
$existingRelationships = $this->find('column', array(
'recursive' => -1,
'fields' => array('referenced_galaxy_cluster_type'),
'unique' => true,
));
$this->ObjectRelationship = ClassRegistry::init('ObjectRelationship');
$objectRelationships = $this->ObjectRelationship->find('column', array(
'recursive' => -1,
'fields' => array('name'),
'unique' => true,
));
return array_unique(array_merge($existingRelationships, $objectRelationships));
}
/**
* saveRelations
*
* @see saveRelation
* @return array List of errors if any
*/
public function saveRelations(array $user, array $cluster, array $relations, $captureTag=false, $force=false)
{
$errors = array();
foreach ($relations as $k => $relation) {
$saveResult = $this->saveRelation($user, $cluster, $relation, $captureTag=$captureTag, $force=$force);
$errors = array_merge($errors, $saveResult);
}
return $errors;
}
/**
* saveRelation Respecting ACL saves a relation and set correct fields where applicable.
* Contrary to its capture equivalent, trying to save a relation for a unknown target cluster will fail.
*
* @param array $user
* @param array $cluster The cluster from which the relation is originating
* @param array $relation The relation to save
* @param bool $captureTag Should the tag be captured if it doesn't exists
* @param bool $force Should the relation be edited if it exists
* @return array List errors if any
*/
public function saveRelation(array $user, array $cluster, array $relation, $captureTag=false, $force=false)
{
$errors = array();
if (!isset($relation['GalaxyClusterRelation']) && !empty($relation)) {
$relation = array('GalaxyClusterRelation' => $relation);
}
$authorizationCheck = $this->SourceCluster->fetchIfAuthorized($user, $cluster, array('edit'), $throwErrors=false, $full=false);
if (isset($authorizationCheck['authorized']) && !$authorizationCheck['authorized']) {
$errors[] = $authorizationCheck['error'];
return $errors;
}
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $cluster['uuid'];
$existingRelation = $this->find('first', [
'conditions' => [
'galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'],
'referenced_galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
'referenced_galaxy_cluster_type' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_type'],
],
'fields' => ['id'],
'recursive' => -1,
]);
if (!empty($existingRelation)) {
if (!$force) {
$errors[] = __('Relation already exists');
return $errors;
} else {
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
}
} else {
$this->create();
}
if (empty($errors)) {
if (!isset($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$errors[] = __('referenced_galaxy_cluster_uuid not provided');
return $errors;
}
if (!$force) {
$targetCluster = $this->TargetCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], 'view', $throwErrors=false, $full=false);
if (isset($targetCluster['authorized']) && !$targetCluster['authorized']) { // do not save the relation if referenced cluster is not accessible by the user (or does not exist)
$errors[] = array(__('Invalid referenced galaxy cluster'));
return $errors;
}
}
$relation = $this->syncUUIDsAndIDs($user, $relation);
$saveSuccess = $this->save($relation);
if ($saveSuccess) {
$savedRelation = $this->find('first', array(
'conditions' => array('id' => $this->id),
'recursive' => -1
));
$tags = array();
if (!empty($relation['GalaxyClusterRelation']['tags'])) {
$tags = $relation['GalaxyClusterRelation']['tags'];
} elseif (!empty($relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'])) {
$tags = $relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'];
$tags = Hash::extract($tags, '{n}.name');
} elseif (!empty($relation['GalaxyClusterRelation']['Tag'])) {
$tags = $relation['GalaxyClusterRelation']['Tag'];
$tags = Hash::extract($tags, '{n}.name');
}
if (!empty($tags)) {
$tagSaveResults = $this->GalaxyClusterRelationTag->attachTags($user, $this->id, $tags, $capture=$captureTag);
if (!$tagSaveResults) {
$errors[] = __('Tags could not be saved for relation (%s)', $this->id);
}
}
} else {
foreach ($this->validationErrors as $validationError) {
$errors[] = $validationError[0];
}
}
}
return $errors;
}
/**
* editRelation Respecting ACL edits a relation and set correct fields where applicable.
* Contrary to its capture equivalent, trying to save a relation for a unknown target cluster will fail.
*
* @param array $user
* @param array $relation The relation to be saved
* @param array $fieldList Only edit the fields provided
* @param bool $captureTag Should the tag be captured if it doesn't exists
* @return array List of errors if any
*/
public function editRelation(array $user, array $relation, array $fieldList=array(), $captureTag=false)
{
$this->SharingGroup = ClassRegistry::init('SharingGroup');
$errors = array();
if (!isset($relation['GalaxyClusterRelation']['galaxy_cluster_id'])) {
$errors[] = __('galaxy_cluster_id not provided');
return $errors;
}
$authorizationCheck = $this->SourceCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['galaxy_cluster_id'], array('edit'), $throwErrors=false, $full=false);
if (isset($authorizationCheck['authorized']) && !$authorizationCheck['authorized']) {
$errors[] = $authorizationCheck['error'];
return $errors;
}
if (isset($relation['GalaxyClusterRelation']['id'])) {
$existingRelation = $this->find('first', array('conditions' => array('GalaxyClusterRelation.id' => $relation['GalaxyClusterRelation']['id'])));
} else {
$errors[] = __('UUID not provided');
}
if (empty($existingRelation)) {
$errors[] = __('Unkown ID');
} else {
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid']
));
$cluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (empty($cluster)) {
$errors[] = __('Invalid source galaxy cluster');
}
$cluster = $cluster[0];
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $cluster['SourceCluster']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $cluster['SourceCluster']['uuid'];
if (isset($relation['GalaxyClusterRelation']['distribution']) && $relation['GalaxyClusterRelation']['distribution'] == 4 && !$this->SharingGroup->checkIfAuthorised($user, $relation['GalaxyClusterRelation']['sharing_group_id'])) {
$errors[] = array(__('Galaxy Cluster Relation could not be saved: The user has to have access to the sharing group in order to be able to edit it.'));
}
if (empty($errors)) {
if (!isset($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$errors[] = __('referenced_galaxy_cluster_uuid not provided');
return $errors;
}
$targetCluster = $this->TargetCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], 'view', $throwErrors=false, $full=false);
if (isset($targetCluster['authorized']) && !$targetCluster['authorized']) { // do not save the relation if referenced cluster is not accessible by the user (or does not exist)
$errors[] = array(__('Invalid referenced galaxy cluster'));
return $errors;
}
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $targetCluster['TargetCluster']['id'];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'] = $targetCluster['TargetCluster']['uuid'];
$relation['GalaxyClusterRelation']['default'] = false;
if (empty($fieldList)) {
$fieldList = array('galaxy_cluster_id', 'galaxy_cluster_uuid', 'referenced_galaxy_cluster_id', 'referenced_galaxy_cluster_uuid', 'referenced_galaxy_cluster_type', 'distribution', 'sharing_group_id', 'default');
}
$saveSuccess = $this->save($relation, array('fieldList' => $fieldList));
if (!$saveSuccess) {
foreach ($this->validationErrors as $validationError) {
$errors[] = $validationError[0];
}
} else {
$this->GalaxyClusterRelationTag->deleteAll(array('GalaxyClusterRelationTag.galaxy_cluster_relation_id' => $relation['GalaxyClusterRelation']['id']));
$this->GalaxyClusterRelationTag->attachTags($user, $relation['GalaxyClusterRelation']['id'], $relation['GalaxyClusterRelation']['tags'], $capture=$captureTag);
}
}
}
return $errors;
}
public function bulkSaveRelations(array $relations)
{
// Fetch existing tags Name => ID mapping
$tagNameToId = $this->GalaxyClusterRelationTag->Tag->find('list', [
'fields' => ['Tag.name', 'Tag.id'],
'callbacks' => false,
]);
// Fetch all cluster UUID => ID mapping
$galaxyClusterUuidToId = $this->TargetCluster->find('list', [
'fields' => ['uuid', 'id'],
'callbacks' => false,
]);
$lookupSavedIds = [];
$relationTagsToSave = [];
foreach ($relations as &$relation) {
if (isset($galaxyClusterUuidToId[$relation['referenced_galaxy_cluster_uuid']])) {
$relation['referenced_galaxy_cluster_id'] = $galaxyClusterUuidToId[$relation['referenced_galaxy_cluster_uuid']];
} else {
$relation['referenced_galaxy_cluster_id'] = 0; // referenced cluster doesn't exists
}
if (!empty($relation['tags'])) {
$lookupSavedIds[$relation['galaxy_cluster_id']] = true;
foreach ($relation['tags'] as $tag) {
if (!isset($tagNameToId[$tag])) {
$tagNameToId[$tag] = $this->GalaxyClusterRelationTag->Tag->quickAdd($tag);
}
$relationTagsToSave[$relation['galaxy_cluster_uuid']][$relation['referenced_galaxy_cluster_uuid']][] = $tagNameToId[$tag];
}
}
}
unset($galaxyClusterUuidToId, $tagNameToId);
$this->saveMany($relations, ['validate' => false]); // Some clusters uses invalid UUID :/
// Insert tags
$savedRelations = $this->find('all', [
'recursive' => -1,
'conditions' => ['galaxy_cluster_id' => array_keys($lookupSavedIds)],
'fields' => ['id', 'galaxy_cluster_uuid', 'referenced_galaxy_cluster_uuid']
]);
$relation_tags = [];
foreach ($savedRelations as $savedRelation) {
$uuid1 = $savedRelation['GalaxyClusterRelation']['galaxy_cluster_uuid'];
$uuid2 = $savedRelation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'];
if (isset($relationTagsToSave[$uuid1][$uuid2])) {
foreach ($relationTagsToSave[$uuid1][$uuid2] as $tagId) {
$relation_tags[] = [$savedRelation['GalaxyClusterRelation']['id'], $tagId];
}
}
}
if (!empty($relation_tags)) {
$db = $this->getDataSource();
$fields = array('galaxy_cluster_relation_id', 'tag_id');
$db->insertMulti('galaxy_cluster_relation_tags', $fields, $relation_tags);
}
}
/**
* Gets a relation then save it.
*
* @param array $user
* @param array $cluster The cluster for which the relation is being saved
* @param array $relation The relation to be saved
* @param bool $fromPull If the current capture is performed from a PULL sync. If set, it allows edition of existing relations
* @return array The capture success results
*/
public function captureRelations(array $user, array $cluster, array $relations, $fromPull=false)
{
$results = array('success' => false, 'imported' => 0, 'failed' => 0);
$this->Log = ClassRegistry::init('Log');
$clusterUuid = $cluster['GalaxyCluster']['uuid'];
foreach ($relations as $k => $relation) {
if (!isset($relation['GalaxyClusterRelation'])) {
$relation = array('GalaxyClusterRelation' => $relation);
}
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $clusterUuid;
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $cluster['GalaxyCluster']['id'];
if (empty($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$this->Log->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('No referenced cluster UUID provided'), __('relation for cluster (%s)', $clusterUuid));
$results['failed']++;
continue;
} else {
$options = array(
'conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
),
'fields' => array(
'id', 'uuid',
)
);
$referencedCluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (empty($referencedCluster)) {
if (!$fromPull) {
$this->Log->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('Referenced cluster not found'), __('relation to (%s) for cluster (%s)', $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], $clusterUuid));
}
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = 0;
} else {
$referencedCluster = $referencedCluster[0];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $referencedCluster['SourceCluster']['id'];
}
}
$existingRelation = $this->find('first', array('conditions' => array(
'GalaxyClusterRelation.galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'],
'GalaxyClusterRelation.referenced_galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
'GalaxyClusterRelation.referenced_galaxy_cluster_type' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_type'],
)));
if (!empty($existingRelation)) {
if (!$fromPull) {
$this->Log->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('Relation already exists'), __('relation to (%s) for cluster (%s)', $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], $clusterUuid));
$results['failed']++;
continue;
} else {
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
}
} else {
unset($relation['GalaxyClusterRelation']['id']);
$this->create();
}
$this->Event = ClassRegistry::init('Event');
if (isset($relation['GalaxyClusterRelation']['distribution']) && $relation['GalaxyClusterRelation']['distribution'] == 4) {
$relation['GalaxyClusterRelation'] = $this->Event->captureSGForElement($relation['GalaxyClusterRelation'], $user);
}
$saveSuccess = $this->save($relation);
if ($saveSuccess) {
$results['imported']++;
$modelKey = false;
if (!empty($relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'])) {
$modelKey = 'GalaxyClusterRelationTag';
} elseif (!empty($relation['GalaxyClusterRelation']['Tag'])) {
$modelKey = 'Tag';
}
if ($modelKey !== false) {
$tagNames = Hash::extract($relation['GalaxyClusterRelation'][$modelKey], '{n}.name');
// Similar behavior as for AttributeTags: Here we only attach tags. If they were removed at some point it's not taken into account.
// Since we don't have tag soft-deletion, tags added by users will be kept.
$this->GalaxyClusterRelationTag->attachTags($user, $this->id, $tagNames, $capture=true);
}
} else {
$results['failed']++;
}
}
$results['success'] = $results['imported'] > 0;
return $results;
}
public function removeNonAccessibleTargetCluster($user, $relations)
{
$availableTargetClusterIDs = $this->TargetCluster->cacheGalaxyClusterIDs($user);
$availableTargetClusterIDsKeyed = array_flip($availableTargetClusterIDs);
foreach ($relations as $i => $relation) {
if (
isset($relation['TargetCluster']['id']) &&
!isset($availableTargetClusterIDsKeyed[$relation['TargetCluster']['id']])
) {
$relations[$i]['TargetCluster'] = null;
}
}
return $relations;
}
/**
* syncUUIDsAndIDs Adapt IDs of source and target cluster inside the relation based on the provided two UUIDs
*
* @param array $user
* @param array $relation
* @return array The adpated relation
*/
private function syncUUIDsAndIDs(array $user, array $relation)
{
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid']
));
$sourceCluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (!empty($sourceCluster)) {
$sourceCluster = $sourceCluster[0];
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $sourceCluster['SourceCluster']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $sourceCluster['SourceCluster']['uuid'];
}
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid']
));
$targetCluster = $this->TargetCluster->fetchGalaxyClusters($user, $options);
if (!empty($targetCluster)) {
$targetCluster = $targetCluster[0];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $targetCluster['TargetCluster']['id'];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'] = $targetCluster['TargetCluster']['uuid'];
} else {
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = 0;
}
return $relation;
}
}