new: [sharing group blueprints]

- create a rule based blueprint that is used to create and update a sharing group
- nest sharing groups
- filter organisations by metadata fields
- nested via boolean operators
- CLI exposed
- API exposed
- Lightweight ownership model (only blueprint owner can see and edit the blueprint)
pull/8183/head
iglocska 2022-03-02 02:09:20 +01:00
parent 0c4f225e71
commit 639a4929e3
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
12 changed files with 906 additions and 2 deletions

View File

@ -8,7 +8,7 @@ App::uses('ProcessTool', 'Tools');
*/
class AdminShell extends AppShell
{
public $uses = array('Event', 'Post', 'Attribute', 'Job', 'User', 'Task', 'Allowedlist', 'Server', 'Organisation', 'AdminSetting', 'Galaxy', 'Taxonomy', 'Warninglist', 'Noticelist', 'ObjectTemplate', 'Bruteforce', 'Role', 'Feed');
public $uses = array('Event', 'Post', 'Attribute', 'Job', 'User', 'Task', 'Allowedlist', 'Server', 'Organisation', 'AdminSetting', 'Galaxy', 'Taxonomy', 'Warninglist', 'Noticelist', 'ObjectTemplate', 'Bruteforce', 'Role', 'Feed', 'SharingGroupBlueprint');
public function getOptionParser()
{
@ -1142,4 +1142,37 @@ class AdminShell extends AppShell
$this->out($setting['setting'] . ': ' . $setting['errorMessage']);
}
}
public function executeSGBlueprint()
{
$id = false;
$target = 'all';
if (!empty($this->args[0])) {
$target = trim($this->args[0]);
}
if (!is_numeric($target) && !in_array($target, ['all', 'attached', 'deteached'])) {
$this->error(__('Invalid target. Either pass a blueprint ID or one of the following filters: all, attached, detached.'));
}
$conditions = [];
if (is_numeric($target)) {
$conditions['SharingGroupBlueprint']['id'] = $target;
} else if ($target === 'attached') {
$conditions['SharingGroupBlueprint']['sharing_group_id >'] = 0;
} else if ($target === 'detached') {
$conditions['SharingGroupBlueprint']['sharing_group_id'] = 0;
}
$sharingGroupBlueprints = $this->SharingGroupBlueprint->find('all', ['conditions' => $conditions, 'recursive' => 0]);
if (empty($sharingGroupBlueprints)) {
$this->error(__('No valid blueprints found.'));
}
$stats = $this->SharingGroupBlueprint->execute($sharingGroupBlueprints);
$message = __(
'Done, %s sharing group blueprint(s) matched. Sharing group changes: Created: %s. Updated: %s. Failed to create: %s.',
count($sharingGroupBlueprints),
$stats['created'],
$stats['changed'],
$stats['failed']
);
$this->out($message);
}
}

View File

@ -0,0 +1,196 @@
<?php
App::uses('AppController', 'Controller');
class SharingGroupBlueprintsController extends AppController
{
public $components = array('Session', 'RequestHandler');
public function beforeFilter()
{
parent::beforeFilter();
}
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999
);
public function index()
{
$params = [
'filters' => ['name', 'uuid'],
'quickFilters' => ['name']
];
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('menuData', array('menuList' => 'globalActions', 'menuItem' => 'indexMG'));
}
public function add()
{
$currentUser = $this->Auth->user();
$params = [
'beforeSave' => function($data) use ($currentUser) {
$data['SharingGroupBlueprint']['uuid'] = CakeText::uuid();
$data['SharingGroupBlueprint']['user_id'] = $currentUser['id'];
$data['SharingGroupBlueprint']['org_id'] = $currentUser['org_id'];
return $data;
}
];
$this->CRUD->add($params);
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$this->set('menuData', array('menuList' => 'globalActions', 'menuItem' => 'addMG'));
}
public function edit($id)
{
$this->set('menuData', array('menuList' => 'globalActions', 'menuItem' => 'editMG'));
$this->set('id', $id);
$params = [
'fields' => ['rules']
];
$this->CRUD->edit($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->render('add');
}
public function delete($id)
{
$this->CRUD->delete($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function view($id)
{
$this->set('menuData', ['menuList' => 'sync', 'menuItem' => 'view_cerebrate']);
$this->CRUD->view($id, ['contain' => ['Organisation.name', 'Organisation.uuid', 'Organisation.id', 'SharingGroup.id', 'SharingGroup.name']]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('id', $id);
$this->set('menuData', array('menuList' => 'globalActions', 'menuItem' => 'viewMG'));
}
public function viewOrgs($id)
{
$conditions = ['SharingGroupBlueprint.id' => $id];
if (!$this->_isSiteAdmin()) {
$conditions['SharingGroupBlueprint.org_id'] = $this->Auth->user('org_id');
}
$sharingGroupBlueprint = $this->SharingGroupBlueprint->find('first', ['conditions' => $conditions]);
if (empty($sharingGroupBlueprint)) {
throw new NotFoundException(__('Invalid Sharing Group Blueprint'));
}
// we create a fake user to restrict the visible sharing groups to the creator of the SharingGroupBlueprint, in case an admin wants to update it
$fake_user = [
'Role' => [
'perm_site_admin' => false
],
'org_id' => $sharingGroupBlueprint['SharingGroupBlueprint']['org_id'],
'id' => 1
];
$temp = $this->SharingGroupBlueprint->evaluateSharingGroupBlueprint($sharingGroupBlueprint, $fake_user);
$orgs = $this->SharingGroupBlueprint->SharingGroup->Organisation->find('all', [
'recursive' => -1,
'fields' => ['id', 'uuid', 'name', 'sector', 'type', 'nationality'],
'conditions' => ['id' => $temp['orgs']]
]);
$this->set('data', $orgs);
$this->set('menuData', array('menuList' => 'SharingGroupBlueprints', 'menuItem' => 'viewOrgs'));
}
public function execute($id = false)
{
$conditions = [];
if (!empty($id)) {
$conditions['SharingGroupBlueprint.id'] = $id;
}
if (!$this->Auth->user('Role')['perm_admin']) {
$conditions['SharingGroupBlueprint.org_id'] = $this->Auth->user('org_id');
}
$sharingGroupBlueprints = $this->SharingGroupBlueprint->find('all', ['conditions' => $conditions, 'recursive' => 0]);
if (empty($sharingGroupBlueprints)) {
throw new NotFoundException(__('No valid blueprints found.'));
}
if ($this->request->is('post')) {
$stats = $this->SharingGroupBlueprint->execute($sharingGroupBlueprints);
$message = __(
'Done, %s sharing group blueprint(s) matched. Sharing group changes: Created: %s. Updated: %s. Failed to create: %s.',
count($sharingGroupBlueprints),
$stats['created'],
$stats['changed'],
$stats['failed']
);
if ($this->IndexFilter->isRest()) {
if ($stats['changed'] || $stats['created']) {
return $this->RestResponse->saveSuccessResponse('sharingGroupBlueprints', 'execute', $id, false, $message);
} else {
return $this->RestResponse->saveFailResponse('sharingGroupBlueprints', 'execute', $id, $message, $this->response->type());
}
} else {
$status = 'success';
if ($stats['failed']) {
$status = 'error';
if ($stats['created'] || $stats['changed']) {
$status = 'info';
}
}
$this->Flash->{$status}($message);
$this->redirect($this->referer());
}
} else {
$this->set('id', empty($id) ? $id : 'all');
$this->set('title', __('Execute Sharing Group Blueprint'));
$this->set('question', __('Are you sure you want to (re)create a sharing group based on the Sharing Group Blueprint?'));
$this->set('actionName', __('Execute'));
$this->layout = 'ajax';
$this->render('/genericTemplates/confirm');
}
}
public function detach($id)
{
$conditions = [];
if (empty($id)) {
throw new MethodNotAllowedException(__('No ID specified.'));
}
$conditions['SharingGroupBlueprint.id'] = $id;
if (!$this->Auth->user('Role')['perm_admin']) {
$conditions['SharingGroupBlueprint.org_id'] = $this->Auth->user('org_id');
}
$sharingGroupBlueprint = $this->SharingGroupBlueprint->find('first', ['conditions' => $conditions, 'recursive' => -1]);
if (empty($sharingGroupBlueprint)) {
throw new NotFoundException(__('Invalid Sharing Group Blueprint'));
}
if ($this->request->is('post')) {
$sharingGroupBlueprint['SharingGroupBlueprint']['sharing_group_id'] = 0;
$result = $this->SharingGroupBlueprint->save($sharingGroupBlueprint);
$message = $result ? __('Sharing group detached.') : __('Could not detach sharing group.');
if ($this->IndexFilter->isRest()) {
if ($result) {
return $this->RestResponse->saveSuccessResponse('sharingGroupBlueprints', 'detach', $id, false, $message);
} else {
return $this->RestResponse->saveFailResponse('sharingGroupBlueprints', 'detach', $id, $message, $this->response->type());
}
} else {
$this->Flash->{$result ? 'success' : 'error'}($message);
$this->redirect($this->referer());
}
} else {
$this->set('id', $id);
$this->set('title', __('Detach Sharing Group Blueprint'));
$this->set('question', __('Are you sure you want to detach the associated sharing group from this Sharing Group Blueprint? This action is irreversible.'));
$this->set('actionName', __('Detach'));
$this->layout = 'ajax';
$this->render('/genericTemplates/confirm');
}
}
}

View File

@ -85,7 +85,7 @@ class AppModel extends Model
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => true, 64 => false, 65 => false, 66 => false, 67 => false, 68 => false,
69 => false, 70 => false, 71 => true, 72 => true, 73 => false, 74 => false,
75 => false, 76 => true, 77 => false, 78 => false, 79 => false,
75 => false, 76 => true, 77 => false, 78 => false, 79 => false, 80 => false
);
public $advanced_updates_description = array(
@ -1614,6 +1614,23 @@ class AppModel extends Model
$sqlArray[] = "ALTER TABLE `users` ADD `sub` varchar(255) NULL DEFAULT NULL;";
$sqlArray[] = "ALTER TABLE `users` ADD UNIQUE INDEX `sub` (`sub`);";
break;
case 80:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `sharing_group_blueprints` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) COLLATE utf8_bin NOT NULL ,
`name` varchar(191) NOT NULL,
`timestamp` int(11) NOT NULL DEFAULT 0,
`user_id` int(11) NOT NULL,
`org_id` int(11) NOT NULL,
`sharing_group_id` int(11),
`rules` text,
PRIMARY KEY (`id`),
INDEX `uuid` (`uuid`),
INDEX `name` (`name`),
INDEX `org_id` (`org_id`),
INDEX `sharing_group_id` (`sharing_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -38,6 +38,7 @@ class Log extends AppModel
'enable',
'enrichment',
'error',
'execute_blueprint',
'export',
'fetchEvent',
'file_upload',

View File

@ -7268,6 +7268,7 @@ class Server extends AppModel
'Enqueue push' => 'MISP/app/Console/cake Server enqueuePush [timestamp] [task_id] [user_id]',
'Enqueue feed fetch' => 'MISP/app/Console/cake Server enqueueFeedFetch [timestamp] [user_id] [task_id]',
'Enqueue feed cache' => 'MISP/app/Console/cake Server enqueueFeedCache [timestamp] [user_id] [task_id]',
'Update sharing groups based on blueprints' => 'MISP/app/Console/cake Server executeSGBlueprint [blueprint_id|all|attached|detached]'
),
'description' => __('If you would like to automate tasks such as caching feeds or pulling from server instances, you can do it using the following command line tools. Simply execute the given commands via the command line / create cron jobs easily out of them.'),
'header' => __('Automating certain console tasks')

View File

@ -0,0 +1,307 @@
<?php
App::uses('AppModel', 'Model');
App::uses('EncryptedValue', 'Tools');
class SharingGroupBlueprint extends AppModel
{
public $actsAs = [
'AuditLog',
'SysLogLogable.SysLogLogable' => [
'roleModel' => 'Role',
'roleKey' => 'role_id',
'change' => 'full'
],
'Containable'
];
public $belongsTo = array(
'SharingGroup',
'Organisation' => array(
'className' => 'Organisation',
'foreignKey' => 'org_id'
)
);
public $validFilters = [
'org' => [
'org_id' => 'id',
'org_uuid' => 'uuid',
'org_name' => 'name',
'org_nationality' => 'nationality',
'org_sector' => 'sector',
'org_type' => 'type'
],
'sharing_group' => [
'sharing_group_id' => 'id',
'sharing_group_uuid' => 'uuid'
]
];
public $operands = [
'OR',
'AND',
'NOT'
];
public function beforeSave($options = array())
{
$this->data['SharingGroupBlueprint']['timestamp'] = time();
$this->data['SharingGroupBlueprint']['rules'] = json_decode($this->data['SharingGroupBlueprint']['rules']);
$this->data['SharingGroupBlueprint']['rules'] = json_encode($this->data['SharingGroupBlueprint']['rules']);
return true;
}
public function afterFind($results, $primary = false)
{
foreach ($results as &$v) {
$v['SharingGroupBlueprint']['rules'] = json_encode(json_decode($v['SharingGroupBlueprint']['rules']), JSON_PRETTY_PRINT);
}
return $results;
}
public function execute($sharingGroupBlueprints)
{
$stats = [
'changed' => 0,
'created' => 0,
'failed' => 0
];
$updated = $failed = 0;
foreach ($sharingGroupBlueprints as $sharingGroupBlueprint) {
// we create a fake user to restrict the visible sharing groups to the creator of the SharingGroupBlueprint, in case an admin wants to update it
$fake_user = [
'Role' => [
'perm_site_admin' => false
],
'org_id' => $sharingGroupBlueprint['SharingGroupBlueprint']['org_id'],
'id' => 1
];
$result = $this->updateSharingGroup($sharingGroupBlueprint, $fake_user);
foreach (array_keys($stats) as $field) {
$stats[$field] += $result[$field];
}
}
return $stats;
}
public function updateSharingGroup($sharingGroupBlueprint, $user)
{
$this->Organisation = ClassRegistry::init('Organisation');
$data = $this->evaluateSharingGroupBlueprint($sharingGroupBlueprint, $user);
$failed = 0;
if (empty($sharingGroupBlueprint['SharingGroupBlueprint']['sharing_group_id'])) {
$created = true;
$this->SharingGroup->create();
$org_uuid = $this->SharingGroup->Organisation->find('first', [
'recursive' => -1,
'conditions' => ['Organisation.id' => $sharingGroupBlueprint['SharingGroupBlueprint']['org_id']],
'fields' => ['Organisation.uuid']
]);
if (empty($org_uuid)) {
throw new MethodNotAllowedException(__('Invalid owner organisation.'));
}
$org_uuid = $org_uuid['Organisation']['uuid'];
$sg = [
'name' => $sharingGroupBlueprint['SharingGroupBlueprint']['name'],
'description' => __('Generated based on Sharing Group Blueprint rules'),
'org_id' => $user['org_id'],
'organisation_uuid' => $org_uuid,
'releasability' => __('Generated based on Sharing Group Blueprint rules'),
'local' => 1,
'roaming' => 1
];
if ($this->SharingGroup->save($sg)) {
$id = $this->SharingGroup->id;
$sharingGroupBlueprint['SharingGroupBlueprint']['sharing_group_id'] = $id;
$existingOrgs = [];
$this->save($sharingGroupBlueprint);
} else {
$failed++;
}
} else {
$created = false;
$sg = $this->SharingGroup->find('first', [
'recursive' => -1,
'contain' => ['SharingGroupOrg'],
'conditions' => ['SharingGroup.id' => $sharingGroupBlueprint['SharingGroupBlueprint']['sharing_group_id']]
]);
$existingOrgs = [];
foreach ($sg['SharingGroupOrg'] as $sgo) {
$existingOrgs[] = $sgo['org_id'];
}
$existingOrgs = array_unique($existingOrgs);
$id = $sg['SharingGroup']['id'];
}
return [
'id' => $id,
'changed' => !$created && $this->__handleSharingGroupOrgs($existingOrgs, $data['orgs'], $id),
'created' => $created,
'failed' => $failed
];
}
private function __handleSharingGroupOrgs($existingOrgs, $newOrgs, $id)
{
$added = 0;
$removed = 0;
$this->Log = ClassRegistry::init('Log');
foreach ($existingOrgs as $existingOrg) {
if (!in_array($existingOrg, $newOrgs)) {
$this->SharingGroup->SharingGroupOrg->deleteAll([
'sharing_group_id' => $id,
'org_id' => $existingOrg
], false);
$removed++;
}
}
foreach ($newOrgs as $newOrg) {
if (!in_array($newOrg, $existingOrgs)) {
$sgo = [
'sharing_group_id' => $id,
'org_id' => $newOrg,
'extend' => false
];
$this->SharingGroup->SharingGroupOrg->create();
$this->SharingGroup->SharingGroupOrg->save($sgo);
$added++;
}
}
if ($added || $removed) {
$this->Log->create();
$entry = array(
'org' => 'SYSTEM',
'model' => 'SharingGroup',
'model_id' => $id,
'email' => 'SYSTEM',
'action' => 'execute_blueprint',
'user_id' => 0,
'title' => 'Updated the sharing group.',
'change' => __('Updated sharing group. Added %s and removed %s organisations', $added, $removed)
);
$this->Log->save($entry);
return true;
}
return false;
}
// Walking on water and developing software from a specification are easy if both are frozen - Edward V Berard
public function evaluateSharingGroupBlueprint($sharingGroupBlueprint, $user)
{
$data = [];
$rules = json_decode($sharingGroupBlueprint['SharingGroupBlueprint']['rules'], true);
$data = $this->__recursiveEvaluate($user, $rules, 'OR');
return $data;
}
private function __recursiveEvaluate($user, $rules, $operand)
{
if (!empty($rules)) {
$data = [];
foreach ($rules as $key => $value) {
if (in_array($key, $this->operands)) {
if ($operand === 'NOT') {
throw new MethodNotAllwedException(__('Boolean branches within a NOT branch are not supported.'));
}
$temp = $this->__recursiveEvaluate($user, $rules[$key], $key);
} else {
$negation = $operand === 'NOT';
$temp = $this->__evaluateLeaf($user, $key, $value, $negation);
}
if ($operand === 'OR') {
if (!isset($data['orgs'])) {
$data['orgs'] = [];
}
$data['orgs'] = array_merge(
$data['orgs'],
isset($temp['orgs']) ? $temp['orgs'] : []
);
} else if ($operand === 'AND' || $operand === 'NOT') {
if (!isset($data['orgs'])) {
$data['orgs'] = $temp['orgs'];
} else {
$data['orgs'] = array_intersect($data['orgs'], $temp['orgs']);
}
}
}
}
return $data;
}
private function __evaluateLeaf($user, $key, $value, $negation = false)
{
if (substr($key, 0, strlen('org')) === 'org') {
return $this->__evaluateOrgLeaf(
$user,
substr($key, (strlen('org_'))),
$value,
$negation
);
} else if (substr($key, 0, strlen('sharing_group')) === 'sharing_group') {
return $this->__evaluateSGLeaf(
$user,
substr($key, (strlen('sharing_group_'))),
$value,
$negation
);
}
return [];
}
private function __evaluateOrgLeaf($user, $key, $value, $negation)
{
if (in_array($key, $this->validFilters['org'])) {
$conditions = [$key => $value];
if ($negation) {
$conditions = ['NOT' => $conditions];
}
$orgs = $this->SharingGroup->Organisation->find('list', [
'fields' => ['id', 'id'],
'recursive' => -1,
'conditions' => $conditions
]);
$orgs = array_values($orgs);
if (empty($orgs)) {
$orgs = [-1];
}
return [
'orgs' => $orgs
];
}
return [];
}
private function __evaluateSGLeaf($user, $key, $value, $negation)
{
$orgs = [];
if (in_array($key, $this->validFilters['sharing_group'])) {
$conditions = [$key => $value];
if ($negation) {
$conditions = ['NOT' => $conditions];
}
$sgs = $this->SharingGroup->find('all', [
'fields' => ['id', 'uuid', 'name', 'org_id'],
'contain' => ['SharingGroupOrg.org_id'],
'recursive' => -1,
'conditions' => $conditions
]);
foreach ($sgs as $sg) {
if ($this->SharingGroup->checkIfAuthorised($user, $sg['SharingGroup']['id'])) {
$orgs[$sg['SharingGroup']['org_id']] = true;
foreach ($sg['SharingGroupOrg'] as $sgo) {
$orgs[$sgo['org_id']] = true;
}
}
}
$orgs = array_keys($orgs);
if (empty($orgs)) {
$orgs = [-1];
}
return [
'orgs' => $orgs
];
}
return [];
}
}

View File

@ -672,6 +672,25 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'text' => __('View Sharing Group')
));
}
if ($menuItem === 'editMG' || ($menuItem == 'viewMG' && $isAclSharingGroup)) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'editMG',
'url' => $baseurl . '/sharing_group_blueprints/edit/' . h($id),
'text' => __('Edit Sharing Group Blueprint')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'viewMG',
'url' => $baseurl . '/sharing_group_blueprints/view/' . h($id),
'text' => __('View Sharing Group Blueprint')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'text' => __('Execute Sharing Group Blueprint'),
'onClick' => array(
'function' => 'openGenericModal',
'params' => array($baseurl . '/sharing_group_blueprints/execute/' . h($id))
),
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'indexSG',
'url' => $baseurl . '/sharing_groups/index',
@ -683,6 +702,16 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => $baseurl . '/sharing_groups/add',
'text' => __('Add Sharing Group')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'indexMG',
'url' => $baseurl . '/sharing_group_blueprints/index',
'text' => __('List Sharing Group Blueprints')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'addMG',
'url' => $baseurl . '/sharing_group_blueprints/add',
'text' => __('Add Sharing Group Blueprint')
));
}
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(

View File

@ -220,6 +220,16 @@
'url' => $baseurl . '/sharing_groups/add',
'requirement' => $isAclSharingGroup
),
array(
'text' => __('List Sharing Groups Blueprints'),
'url' => $baseurl . '/sharing_group_blueprints/index',
'requirement' => $isAclSharingGroup
),
array(
'text' => __('Add Sharing Group Blueprint'),
'url' => $baseurl . '/sharing_group_blueprints/add',
'requirement' => $isAclSharingGroup
),
array(
'type' => 'separator'
),

View File

@ -0,0 +1,84 @@
<?php
$modelForForm = 'SharingGroupBlueprints';
$edit = $this->request->params['action'] === 'edit' ? true : false;
$fields = [
[
'field' => 'name',
'class' => 'span6'
],
[
'field' => 'rules',
'type' => 'textarea'
]
];
$description = sprintf(
'%s<br />%s<br /><br />%s<br />%s',
__('Create a sharing group blueprint, which can be used to generate a sharing rule based on the nested rules described.'),
__('Simply create a JSON dictionary using a combination of filters and boolean operators.'),
'<span class="bold">Filters</span>: org_id, org_type, org_uuid, org_name, org_sector, org_nationality, sharing_group_id, , sharing_group_uuid',
'<span class="bold">Boolean operators</span>: OR, AND, NOT',
);
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => $description,
'model' => 'SharingGroupBlueprint',
'title' => $edit ? __('Edit SharingGroupBlueprint') : __('Add SharingGroupBlueprint'),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
echo $this->element('genericElements/assetLoader', array(
'js' => array(
'codemirror/codemirror',
'codemirror/modes/javascript',
'codemirror/addons/closebrackets',
'codemirror/addons/lint',
'codemirror/addons/jsonlint',
'codemirror/addons/json-lint',
),
'css' => array(
'codemirror',
'codemirror/show-hint',
'codemirror/lint',
)
));
?>
<script>
var cm;
setupCodeMirror()
function setupCodeMirror() {
var cmOptions = {
mode: "application/json",
theme:'default',
gutters: ["CodeMirror-lint-markers"],
lint: true,
lineNumbers: true,
indentUnit: 4,
showCursorWhenSelecting: true,
lineWrapping: true,
autoCloseBrackets: true
}
cm = CodeMirror.fromTextArea(document.getElementById('SharingGroupBlueprintRules'), cmOptions);
cm.on("keyup", function(cm, event) {
$('#urlParams').val(cm.getValue())
});
}
</script>
<style>
div .CodeMirror {
width: 500px;
border: 1px solid #ddd;
}
</style>

View File

@ -0,0 +1,118 @@
<?php
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Add SharingGroupBlueprint'),
'class' => 'btn btn-primary',
'url' => sprintf(
'%s/SharingGroupBlueprints/add',
$baseurl
)
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'quickFilter'
]
]
],
'fields' => [
[
'name' => __('Id'),
'sort' => 'SharingGroupBlueprint.id',
'data_path' => 'SharingGroupBlueprint.id'
],
[
'name' => __('Owner organisation'),
'sort' => 'Organisation',
'data_path' => 'Organisation',
'element' => 'org'
],
[
'name' => __('Name'),
'sort' => 'SharingGroupBlueprint.name',
'data_path' => 'SharingGroupBlueprint.name'
],
[
'name' => __('SharingGroup'),
'sort' => 'SharingGroupBlueprint.sharing_group_id',
'data_path' => 'SharingGroupBlueprint.sharing_group_id',
'element' => 'custom',
'function' => function ($row) use ($baseurl) {
if (!empty($row['SharingGroupBlueprint']['sharing_group_id'])) {
if (!empty($row['SharingGroup'])) {
echo sprintf(
'<a href="%s/sharingGroups/view/%s" title="%s">#%s: %s</a> %s',
$baseurl,
h($row['SharingGroup']['id']),
h($row['SharingGroup']['releasability']),
h($row['SharingGroup']['id']),
h($row['SharingGroup']['name']),
sprintf(
'<a href="#" class="black fas fa-trash" onClick="openGenericModal(\'%s/sharing_group_blueprints/detach/%s\');"></a>',
$baseurl,
h($row['SharingGroupBlueprint']['id'])
)
);
}
} else {
echo '&nbsp;';
}
},
],
[
'name' => __('Rules'),
'sort' => 'SharingGroupBlueprint.rules',
'data_path' => 'SharingGroupBlueprint.rules',
'element' => 'json'
]
],
'title' => empty($ajax) ? __('Sharing Group Blueprints') : false,
'description' => empty($ajax) ? __('Sharing Group Blueprints are blueprints for the creation of sharing groups') : false,
'actions' => [
[
'url' => $baseurl . '/SharingGroupBlueprints/view',
'url_params_data_paths' => ['SharingGroupBlueprint.id'],
'icon' => 'eye'
],
[
'url' => $baseurl . '/SharingGroupBlueprints/edit',
'url_params_data_paths' => ['SharingGroupBlueprint.id'],
'icon' => 'edit'
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/SharingGroupBlueprints/execute/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'SharingGroupBlueprint.id',
'icon' => 'recycle',
'title' => __('(Re)generate sharing group based on blueprint')
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/SharingGroupBlueprints/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'SharingGroupBlueprint.id',
'icon' => 'trash'
]
]
]
]
]);
?>

View File

@ -0,0 +1,54 @@
<?php
echo $this->element(
'genericElements/SingleViews/single_view',
[
'title' => 'Sharing Group Blueprint view',
'data' => $data,
'fields' => [
[
'key' => __('Id'),
'path' => 'SharingGroupBlueprint.id'
],
[
'key' => __('Uuid'),
'path' => 'SharingGroupBlueprint.uuid'
],
[
'key' => __('Owner Organisation'),
'path' => 'SharingGroupBlueprint.org_id',
'pathName' => 'Organisation.name',
'type' => 'model',
'model' => 'organisations'
],
[
'key' => __('Name'),
'path' => 'SharingGroupBlueprint.name'
],
[
'key' => __('Description'),
'path' => 'SharingGroupBlueprint.description'
],
[
'key' => __('SharingGroup'),
'path' => 'SharingGroupBlueprint.sharing_group_id',
'pathName' => 'SharingGroup.name',
'type' => 'model',
'model' => 'sharing_groups',
'error' => __('No Sharing group assigned yet, execute the Sharing Group Blueprint first.')
],
[
'key' => __('Rules'),
'path' => 'SharingGroupBlueprint.rules',
'type' => 'json'
],
],
'children' => [
[
'url' => '/SharingGroupBlueprints/viewOrgs/{{0}}/',
'url_params' => ['SharingGroupBlueprint.id'],
'title' => __('Organisations'),
'elementId' => 'preview_orgs_container'
]
]
]
);

View File

@ -0,0 +1,54 @@
<?php
echo sprintf('<div%s>', empty($ajax) ? ' class="index"' : '');
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'skip_pagination' => 1,
'data' => $data,
'fields' => [
[
'name' => __('Id'),
'sort' => 'Organisation.id',
'data_path' => 'Organisation.id'
],
[
'name' => __('Uuid'),
'sort' => 'Organisation.uuid',
'data_path' => 'Organisation.uuid'
],
[
'name' => __('name'),
'sort' => 'Organisation.name',
'data_path' => 'Organisation.name'
],
[
'name' => __('sector'),
'sort' => 'Organisation.sector',
'data_path' => 'Organisation.sector'
],
[
'name' => __('type'),
'sort' => 'Organisation.type',
'data_path' => 'Organisation.type'
],
[
'name' => __('nationality'),
'sort' => 'Organisation.nationality',
'data_path' => 'Organisation.nationality'
]
],
'title' => false,
'description' => __('Organisations that would end up in a sharing group with the current SharingGroupBlueprint blueprint.'),
'actions' => [
[
'url' => $baseurl . '/organisations/view',
'url_params_data_paths' => ['Organisation.id'],
'icon' => 'eye'
]
]
]
]);
echo '</div>';
if (empty($ajax)) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
?>