From 66c5312ea6cddb671d9477e3e906446aa38bfc67 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 28 Jun 2012 17:24:12 +0200 Subject: [PATCH 01/30] DataBase migrate, Audit and Access Control granulation. --- app/Config/Schema/db_group.php | 45 ++ app/Config/Schema/db_group.sql | 31 + app/Config/Schema/db_log.php | 28 + app/Config/Schema/db_log.sql | 21 + app/Config/Schema/schema_0.2.2.1.php | 81 +++ app/Config/Schema/schema_0.2.2.php | 73 +++ app/Config/Schema/schema_0.2.3.php | 101 +++ app/Config/bootstrap.php | 2 + app/Console/Command/Populate023Shell.php | 12 + app/Console/Command/Task/GroupIdTask.php | 14 + .../Command/Task/GroupToAroAcoTask.php | 18 + app/Console/Command/Task/GroupsTask.php | 24 + app/Console/shell/degrate-0.2.3-0.2.2.sh | 12 + app/Console/shell/migrate-0.2.2-0.2.3.sh | 29 + app/Console/shell/rights.sh | 12 + app/Controller/AppController.php | 26 +- app/Controller/AttributesController.php | 4 +- app/Controller/EventsController.php | 10 +- app/Controller/GroupsController.php | 177 ++++++ app/Controller/LogsController.php | 112 ++++ app/Controller/ServersController.php | 2 +- app/Controller/UsersController.php | 127 +++- app/Model/Attribute.php | 10 + app/Model/Event.php | 8 + app/Model/Group.php | 58 ++ app/Model/Log.php | 31 + app/Model/Server.php | 8 + app/Model/User.php | 57 ++ .../Console/Command/AclExtrasShell.php | 308 +++++++++ app/Plugin/AclExtras/README.md | 27 + .../Case/Console/Command/AclExtrasTest.php | 240 +++++++ .../AclExtras/Test/test_controllers.php | 53 ++ app/Plugin/SysLog/Lib/SysLog.php | 91 +++ app/README.txt | 36 +- app/README.ubuntu.txt | 34 + app/View/Attributes/edit.ctp | 16 +- app/View/Attributes/index.ctp | 406 +++++++++++- app/View/Elements/actions_menu.ctp | 9 +- app/View/Events/index.ctp | 591 +++++++++++++++++- app/View/Events/view.ctp | 166 ++++- app/View/Groups/add.ctp | 19 + app/View/Groups/admin_add.ctp | 19 + app/View/Groups/admin_edit.ctp | 23 + app/View/Groups/admin_index.ctp | 55 ++ app/View/Groups/admin_view.ctp | 47 ++ app/View/Groups/edit.ctp | 21 + app/View/Groups/index.ctp | 48 ++ app/View/Groups/view.ctp | 40 ++ app/View/Layouts/default.ctp | 2 + app/View/Logs/admin_index.ctp | 59 ++ app/View/Logs/admin_search.ctp | 53 ++ app/View/Logs/admin_view.ctp | 54 ++ app/View/Logs/index.ctp | 59 ++ app/View/Servers/index.ctp | 407 +++++++++++- app/View/Users/admin_add.ctp | 1 + app/View/Users/admin_edit.ctp | 1 + app/View/Users/admin_index.ctp | 410 +++++++++++- app/View/Users/admin_view.ctp | 30 +- app/View/Users/edit.ctp | 1 + app/View/Users/view.ctp | 5 + app/technical_design/TD-ACL.txt | 16 + app/technical_design/TD-Audit.txt | 28 + app/technical_design/TD-forum.txt | 12 + app/webroot/css/cake.generic.css | 8 + app/webroot/js/deactivateButtons.js | 101 +++ 65 files changed, 4584 insertions(+), 45 deletions(-) create mode 100755 app/Config/Schema/db_group.php create mode 100755 app/Config/Schema/db_group.sql create mode 100755 app/Config/Schema/db_log.php create mode 100755 app/Config/Schema/db_log.sql create mode 100755 app/Config/Schema/schema_0.2.2.1.php create mode 100755 app/Config/Schema/schema_0.2.2.php create mode 100755 app/Config/Schema/schema_0.2.3.php mode change 100644 => 100755 app/Config/bootstrap.php create mode 100755 app/Console/Command/Populate023Shell.php create mode 100755 app/Console/Command/Task/GroupIdTask.php create mode 100755 app/Console/Command/Task/GroupToAroAcoTask.php create mode 100755 app/Console/Command/Task/GroupsTask.php create mode 100755 app/Console/shell/degrate-0.2.3-0.2.2.sh create mode 100755 app/Console/shell/migrate-0.2.2-0.2.3.sh create mode 100755 app/Console/shell/rights.sh mode change 100644 => 100755 app/Controller/AppController.php mode change 100644 => 100755 app/Controller/AttributesController.php mode change 100644 => 100755 app/Controller/EventsController.php create mode 100755 app/Controller/GroupsController.php create mode 100755 app/Controller/LogsController.php mode change 100644 => 100755 app/Controller/ServersController.php mode change 100644 => 100755 app/Controller/UsersController.php mode change 100644 => 100755 app/Model/Attribute.php mode change 100644 => 100755 app/Model/Event.php create mode 100755 app/Model/Group.php create mode 100755 app/Model/Log.php mode change 100644 => 100755 app/Model/Server.php mode change 100644 => 100755 app/Model/User.php create mode 100755 app/Plugin/AclExtras/Console/Command/AclExtrasShell.php create mode 100755 app/Plugin/AclExtras/README.md create mode 100755 app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php create mode 100755 app/Plugin/AclExtras/Test/test_controllers.php create mode 100755 app/Plugin/SysLog/Lib/SysLog.php create mode 100755 app/README.ubuntu.txt create mode 100755 app/View/Groups/add.ctp create mode 100755 app/View/Groups/admin_add.ctp create mode 100755 app/View/Groups/admin_edit.ctp create mode 100755 app/View/Groups/admin_index.ctp create mode 100755 app/View/Groups/admin_view.ctp create mode 100755 app/View/Groups/edit.ctp create mode 100755 app/View/Groups/index.ctp create mode 100755 app/View/Groups/view.ctp mode change 100644 => 100755 app/View/Layouts/default.ctp create mode 100755 app/View/Logs/admin_index.ctp create mode 100755 app/View/Logs/admin_search.ctp create mode 100755 app/View/Logs/admin_view.ctp create mode 100755 app/View/Logs/index.ctp mode change 100644 => 100755 app/View/Servers/index.ctp mode change 100644 => 100755 app/View/Users/admin_add.ctp mode change 100644 => 100755 app/View/Users/admin_edit.ctp mode change 100644 => 100755 app/View/Users/admin_index.ctp mode change 100644 => 100755 app/View/Users/admin_view.ctp create mode 100755 app/technical_design/TD-ACL.txt create mode 100755 app/technical_design/TD-Audit.txt create mode 100755 app/technical_design/TD-forum.txt mode change 100644 => 100755 app/webroot/css/cake.generic.css create mode 100755 app/webroot/js/deactivateButtons.js diff --git a/app/Config/Schema/db_group.php b/app/Config/Schema/db_group.php new file mode 100755 index 000000000..7457d55eb --- /dev/null +++ b/app/Config/Schema/db_group.php @@ -0,0 +1,45 @@ +create(); +// $groups->save(array('Group' => array('name' => 'malware analyst', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => false, 'perm_full' => false))); +// $groups->create(); +// $groups->save(array('Group' => array('name' => 'admin', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => true, 'perm_full' => true))); +// $groups->create(); +// $groups->save(array('Group' => array('name' => 'IDS analyst', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => true, 'perm_full' => false))); +// $groups->create(); +// $groups->save(array('Group' => array('name' => 'guest', 'perm_add' => false, 'perm_modify' => false, 'perm_publish' => false, 'perm_full' => false))); + // populate Users.group_id +// $users = ClassRegistry::init('User'); +// $user = $users->read(null, '1'); +// $users->saveField('group_id', '2'); // $user['User']['group_id'] = '2'; + break; + } + } + } + + public $groups = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'perm_add' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_modify' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_publish' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_full' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') + ); +} diff --git a/app/Config/Schema/db_group.sql b/app/Config/Schema/db_group.sql new file mode 100755 index 000000000..87c3107c8 --- /dev/null +++ b/app/Config/Schema/db_group.sql @@ -0,0 +1,31 @@ +-- ACL, group table +-- works in conjunction with: CakePHP AclComponent + +CREATE TABLE groups ( + id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + perm_add boolean, + perm_modify boolean, + perm_publish boolean, + perm_full boolean, + created DATETIME, + modified DATETIME +); + +-- ALTER TABLE users ADD COLUMN group_id INT(11); + +-- data of Groups +-- INSERT INTO groups (name,perm_add,perm_modify,perm_publish,perm_full) VALUES ('malware analyst',true,true,false,false); +-- INSERT INTO groups (name,perm_add,perm_modify,perm_publish,perm_full) VALUES ('admin',true,true,true,true); +-- INSERT INTO groups (name,perm_add,perm_modify,perm_publish,perm_full) VALUES ('IDS analyst',true,true,true,false); +-- INSERT INTO groups (name,perm_add,perm_modify,perm_publish,perm_full) VALUES ('guest',false,false,false,false); + +-- CakePHP AclComponent acor & aros tables + +-- aros table (should be auto generated on group create) +-- INSERT INTO aros (model,foreign_key,lft,rght) VALUES ('Group',1,1,2); +-- INSERT INTO aros (model,foreign_key,lft,rght) VALUES ('Group',2,3,4); +-- INSERT INTO aros (model,foreign_key,lft,rght) VALUES ('Group',3,5,6); +-- INSERT INTO aros (model,foreign_key,lft,rght) VALUES ('Group',4,7,8); + +-- aros_acos diff --git a/app/Config/Schema/db_log.php b/app/Config/Schema/db_log.php new file mode 100755 index 000000000..a35e515a2 --- /dev/null +++ b/app/Config/Schema/db_log.php @@ -0,0 +1,28 @@ + array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'title' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'model' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 20, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'model_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'action' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 20, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'user_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'change' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'email' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'description' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); +} diff --git a/app/Config/Schema/db_log.sql b/app/Config/Schema/db_log.sql new file mode 100755 index 000000000..226717d72 --- /dev/null +++ b/app/Config/Schema/db_log.sql @@ -0,0 +1,21 @@ +-- Audit, log table +-- works in conjunction with: +-- https://github.com/alkemann/CakePHP-Assets/wiki +-- also described at: +-- http://bakery.cakephp.org/articles/alkemann/2008/10/21/logablebehavior + +DROP TABLE logs; +CREATE TABLE logs ( + id int(11) NOT NULL AUTO_INCREMENT, + title varchar(255), + created DATETIME, + description varchar(255), + model varchar(20), + model_id int(11), + action varchar(20), + user_id int(11), + `change` varchar(255), + email varchar(255), + org varchar(255) COLLATE utf8_bin, + PRIMARY KEY (id) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=2 ; \ No newline at end of file diff --git a/app/Config/Schema/schema_0.2.2.1.php b/app/Config/Schema/schema_0.2.2.1.php new file mode 100755 index 000000000..bc532f04e --- /dev/null +++ b/app/Config/Schema/schema_0.2.2.1.php @@ -0,0 +1,81 @@ + array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'event_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'), + 'type' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'category' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'value1' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'to_ids' => array('type' => 'boolean', 'null' => false, 'default' => '1'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'revision' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => 10), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'value2' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'event_id' => array('column' => 'event_id', 'unique' => 0), 'uuid' => array('column' => 'uuid', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $bruteforces = array( + 'ip' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'expire' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'indexes' => array(), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $events = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'date' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'info' => array('type' => 'text', 'null' => false, 'default' => NULL, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'alerted' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'published' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'uuid' => array('column' => 'uuid', 'unique' => 0), 'info' => array('column' => 'info', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $servers = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'url' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'push' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'pull' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'lastfetchedid' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $users = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'email' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'autoalert' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'invited_by' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'gpgkey' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'nids_sid' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'length' => 15), + 'termsaccepted' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'newsread' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'group_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'username' => array('column' => 'password', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); +} diff --git a/app/Config/Schema/schema_0.2.2.php b/app/Config/Schema/schema_0.2.2.php new file mode 100755 index 000000000..ffcb924dc --- /dev/null +++ b/app/Config/Schema/schema_0.2.2.php @@ -0,0 +1,73 @@ + array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'event_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'), + 'type' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'category' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'value1' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'to_ids' => array('type' => 'boolean', 'null' => false, 'default' => '1'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'revision' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => 10), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'value2' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'event_id' => array('column' => 'event_id', 'unique' => 0), 'uuid' => array('column' => 'uuid', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $bruteforces = array( + 'ip' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'expire' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'indexes' => array(), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $events = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'date' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'info' => array('type' => 'text', 'null' => false, 'default' => NULL, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'alerted' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'published' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'uuid' => array('column' => 'uuid', 'unique' => 0), 'info' => array('column' => 'info', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $servers = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'url' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'push' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'pull' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'lastfetchedid' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $users = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'email' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'autoalert' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'invited_by' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'gpgkey' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'nids_sid' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'length' => 15), + 'termsaccepted' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'newsread' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'username' => array('column' => 'password', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); +} diff --git a/app/Config/Schema/schema_0.2.3.php b/app/Config/Schema/schema_0.2.3.php new file mode 100755 index 000000000..dad2bf3e6 --- /dev/null +++ b/app/Config/Schema/schema_0.2.3.php @@ -0,0 +1,101 @@ + array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'event_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'), + 'type' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'category' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'value1' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'to_ids' => array('type' => 'boolean', 'null' => false, 'default' => '1'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'revision' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => 10), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'value2' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'event_id' => array('column' => 'event_id', 'unique' => 0), 'uuid' => array('column' => 'uuid', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $bruteforces = array( + 'ip' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'expire' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'indexes' => array(), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $events = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'date' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'info' => array('type' => 'text', 'null' => false, 'default' => NULL, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'alerted' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'private' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'published' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'uuid' => array('column' => 'uuid', 'unique' => 0), 'info' => array('column' => 'info', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $groups = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'perm_add' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_modify' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_publish' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'perm_full' => array('type' => 'boolean', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') + ); + public $logs = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'title' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), + 'model' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 20, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'model_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'action' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 20, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'user_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'change' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'email' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'description' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $servers = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'url' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'push' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'pull' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'lastfetchedid' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); + public $users = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'org' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'email' => array('type' => 'string', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'autoalert' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'authkey' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'invited_by' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'gpgkey' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_bin', 'charset' => 'utf8'), + 'nids_sid' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'length' => 15), + 'termsaccepted' => array('type' => 'boolean', 'null' => false, 'default' => NULL), + 'newsread' => array('type' => 'date', 'null' => false, 'default' => NULL), + 'group_id' => array('type' => 'integer', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'username' => array('column' => 'password', 'unique' => 0)), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM') + ); +} diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php old mode 100644 new mode 100755 index 785fceec2..9a30d725c --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -142,3 +142,5 @@ Configure::write('SecureAuth.expire', 300); // the time-window for th * CakePlugin::load('DebugKit'); //Loads a single plugin named DebugKit * */ +CakePlugin::load('SysLog'); +CakePlugin::load('AclExtras'); \ No newline at end of file diff --git a/app/Console/Command/Populate023Shell.php b/app/Console/Command/Populate023Shell.php new file mode 100755 index 000000000..200b355e0 --- /dev/null +++ b/app/Console/Command/Populate023Shell.php @@ -0,0 +1,12 @@ +Groups->execute(); + $this->GroupId->execute('2'); + $this->GroupToAroAco->execute(); + } +} \ No newline at end of file diff --git a/app/Console/Command/Task/GroupIdTask.php b/app/Console/Command/Task/GroupIdTask.php new file mode 100755 index 000000000..14868e312 --- /dev/null +++ b/app/Console/Command/Task/GroupIdTask.php @@ -0,0 +1,14 @@ +Users = new UsersController(); + $this->Users->constructClasses(); + $this->Users->setgroupid($fk); + } +} \ No newline at end of file diff --git a/app/Console/Command/Task/GroupToAroAcoTask.php b/app/Console/Command/Task/GroupToAroAcoTask.php new file mode 100755 index 000000000..da7f05849 --- /dev/null +++ b/app/Console/Command/Task/GroupToAroAcoTask.php @@ -0,0 +1,18 @@ +Groups = new GroupsController(); + $this->Groups->constructClasses(); + + $groups = $this->Group->find('all'); + foreach ($groups as $group) { + $this->Groups->saveAcl(array('model' => 'Group', 'foreign_key' => $group['Group']['id']), $group['Group']['perm_add'], $group['Group']['perm_modify'], $group['Group']['perm_publish']); + } + } +} \ No newline at end of file diff --git a/app/Console/Command/Task/GroupsTask.php b/app/Console/Command/Task/GroupsTask.php new file mode 100755 index 000000000..ac243f089 --- /dev/null +++ b/app/Console/Command/Task/GroupsTask.php @@ -0,0 +1,24 @@ +Groups = new GroupsController(); + $this->Groups->constructClasses(); + + $groups = ClassRegistry::init('Group'); + $groups->create(); + $groups->save(array('Group' => array('name' => 'malware analyst', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => false, 'perm_full' => false))); + $groups->create(); + $groups->save(array('Group' => array('name' => 'admin', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => true, 'perm_full' => true))); + $groups->create(); + $groups->save(array('Group' => array('name' => 'IDS analyst', 'perm_add' => true, 'perm_modify' => true, 'perm_publish' => true, 'perm_full' => false))); + $groups->create(); + $groups->save(array('Group' => array('name' => 'guest', 'perm_add' => false, 'perm_modify' => false, 'perm_publish' => false, 'perm_full' => false))); + + } +} \ No newline at end of file diff --git a/app/Console/shell/degrate-0.2.3-0.2.2.sh b/app/Console/shell/degrate-0.2.3-0.2.2.sh new file mode 100755 index 000000000..a0b4f0268 --- /dev/null +++ b/app/Console/shell/degrate-0.2.3-0.2.2.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# degrate 0.2.3 to 0.2.2 + +# step into project and .. +PRJCT=/var/www/cydefsig/app +cd ${PRJCT} + +# update Schema, remove Users.group_id +./Console/cake schema update -s 0.2.2 + +exit 0; \ No newline at end of file diff --git a/app/Console/shell/migrate-0.2.2-0.2.3.sh b/app/Console/shell/migrate-0.2.2-0.2.3.sh new file mode 100755 index 000000000..9501f10fe --- /dev/null +++ b/app/Console/shell/migrate-0.2.2-0.2.3.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# migrate 0.2.2 to 0.2.3 + +# DataBase migrate, Audit and Access Control granulation + +# step into project and .. +PRJCT=/var/www/cydefsig/app +cd ${PRJCT} + +# create ACL tables +./Console/cake schema create DbAcl +# populate ACL acos +./Console/cake acl create aco root controllers +./Console/cake AclExtras.AclExtras aco_sync + +# update Schema, add Users.group_id +./Console/cake schema update -s 0.2.2.1 + +# create Log table +./Console/cake schema create DbLog + +# create Groups, populate ACL aros and Users.group_id +./Console/cake schema create DbGroup + +# populate 0.2.3 +./Console/cake populate0_2_3 + +exit 0; \ No newline at end of file diff --git a/app/Console/shell/rights.sh b/app/Console/shell/rights.sh new file mode 100755 index 000000000..101411039 --- /dev/null +++ b/app/Console/shell/rights.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +USER=noud + +chown -R ${USER}:www-data /var/www/cydefsig +chmod -R 750 /var/www/cydefsig +chmod -R g+s /var/www/cydefsig +cd /var/www/cydefsig/app/ +chmod -R g+w tmp +chmod -R g+w files + +exit 0 diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php old mode 100644 new mode 100755 index 15a88d9b0..4f95269d8 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -22,7 +22,6 @@ App::uses('Controller', 'Controller'); App::uses('Sanitize', 'Utility'); - /** * Application Controller * @@ -35,6 +34,7 @@ App::uses('Sanitize', 'Utility'); class AppController extends Controller { public $components = array( + 'Acl', // TODO XXX remove 'Session', 'Auth' => array( 'className' => 'SecureAuth', @@ -46,7 +46,8 @@ class AppController extends Controller { 'authError' => 'Did you really think you are allowed to see that?', 'loginRedirect' => array('controller' => 'users', 'action' => 'routeafterlogin'), 'logoutRedirect' => array('controller' => 'users', 'action' => 'login'), - 'authorize' => array('Controller') // Added this line + 'authorize' => array('Controller', // Added this line + 'Actions' => array('actionPath' => 'controllers')) // TODO ACL, 4: tell actionPath ) ); @@ -92,6 +93,11 @@ class AppController extends Controller { // These variables are required for every view $this->set('me', $this->Auth->user()); $this->set('isAdmin', $this->_isAdmin()); + + // TODO ACL: 5: from Controller to Views + $this->set('isAclAdd', $this->checkAcl('add')); + $this->set('isAclModify', $this->checkAcl('edit')); + $this->set('isAclPublish', $this->checkAcl('publish')); } @@ -246,4 +252,20 @@ class AppController extends Controller { } + // TODO ACL, 6b: check on Group and per Model (not used) + function checkAccess() { + $aco = ucfirst($this->params['controller']); + $user = ClassRegistry::init('User')->findById($this->Auth->user('id')); + return $this->Acl->check($user, 'controllers/'.$aco, '*'); + } + + // TODO ACL, 6: check on Group and any Model + function checkAcl($action) { + $aco = 'Events'; // TODO ACL was 'Attributes' + $user = ClassRegistry::init('User')->findById($this->Auth->user('id')); + // TODO ACL, CHECK, below if indicates some wrong: Fatal error: Call to a member function check() on a non-object in /var/www/cydefsig/app/Controller/AppController.php on line 289 + if ($this->Acl) return $this->Acl->check($user, 'controllers/'.$aco.'/'.$action, '*'); + else return true; + } + } diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php old mode 100644 new mode 100755 index d6f71c125..1ad310240 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -10,7 +10,7 @@ App::uses('File', 'Utility'); */ class AttributesController extends AppController { - public $components = array('Security', 'RequestHandler'); + public $components = array('Acl', 'Security', 'RequestHandler'); // XXX ACL component public $paginate = array( 'limit' => 60, 'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events @@ -33,7 +33,7 @@ class AttributesController extends AppController { return true; } // Only on own attributes for these actions - if (in_array($this->action, array('edit', 'delete'))) { + if (in_array($this->action, array('delete'))) { // TODO ACL, removed 'edit' override $attributeid = $this->request->params['pass'][0]; return $this->Attribute->isOwnedByOrg($attributeid, $this->Auth->user('org')); } diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php old mode 100644 new mode 100755 index 675a4abb2..6266971b1 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -16,6 +16,7 @@ class EventsController extends AppController { */ public $components = array( + 'Acl', // XXX ACL component 'Security', 'Email', 'RequestHandler', @@ -37,6 +38,13 @@ class EventsController extends AppController { $this->Auth->allow('text'); $this->Auth->allow('dot'); + + // TODO Audit, activate logable in a Controller + if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { + $this->{$this->modelClass}->setUserData($this->activeUser); + } + + // TODO ACL, if on ent/attr level, $this->set('isAcl', $this->checkAccess()); } public function isAuthorized($user) { @@ -45,7 +53,7 @@ class EventsController extends AppController { return true; } // Only on own events for these actions - if (in_array($this->action, array('edit', 'delete', 'alert', 'publish'))) { + if (in_array($this->action, array('alert'))) { // TODO ACL, CHECK, remove overruling 'edit', 'delete' and 'publish' $eventid = $this->request->params['pass'][0]; return $this->Event->isOwnedByOrg($eventid, $this->Auth->user('org')); } diff --git a/app/Controller/GroupsController.php b/app/Controller/GroupsController.php new file mode 100755 index 000000000..5fa3e0edf --- /dev/null +++ b/app/Controller/GroupsController.php @@ -0,0 +1,177 @@ + array( + 'authorize' => array( + 'Actions' => array('actionPath' => 'controllers/Groups') + ) + ), + 'Session' + ); + + //public $components = array('Security'); + public $paginate = array( + 'limit' => 60, + 'order' => array( + 'Group.name' => 'ASC' + ) + ); + + function beforeFilter() { + parent::beforeFilter(); + } + +/** + * view method + * + * @param string $id + * @return void + */ + public function view($id = null) { + $this->Group->id = $id; + if (!$this->Group->exists()) { + throw new NotFoundException(__('Invalid group')); + } + $this->set('group', $this->Group->read(null, $id)); + } + +/** + * admin_index method + * + * @return void + */ + public function admin_index() { + $this->Group->recursive = 0; + $this->set('groups', $this->paginate()); + } + +/** + * admin_view method + * + * @param string $id + * @return void + */ + public function admin_view($id = null) { + $this->Group->id = $id; + if (!$this->Group->exists()) { + throw new NotFoundException(__('Invalid group')); + } + $this->set('group', $this->Group->read(null, $id)); + } + +/** + * admin_add method + * + * @return void + */ + public function admin_add() { + if ($this->request->is('post')) { + $this->Group->create(); + if ($this->Group->save($this->request->data)) { + $this->Session->setFlash(__('The group has been saved')); + $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The group could not be saved. Please, try again.')); + } + } else { + // generate auth key for a new user + //$newkey = $this->Group->generateAuthKey(); // TODO generateAuthKey? + //$this->set('authkey', $newkey); + } + } + +/** + * admin_edit method + * + * @param string $id + * @return void + */ + public function admin_edit($id = null) { + $this->Group->id = $id; + if (!$this->Group->exists()) { + throw new NotFoundException(__('Invalid group')); + } + if ($this->request->is('post') || $this->request->is('put')) { + $fields = array(); + foreach (array_keys($this->request->data['Group']) as $field) { + if($field != 'password') array_push($fields, $field); + } + if ("" != $this->request->data['Group']['password']) + $fields[] = 'password'; + if ($this->Group->save($this->request->data, true, $fields)) { + $this->saveAcl($this->Group, $this->data['Group']['perm_add'], $this->data['Group']['perm_modify'], $this->data['Group']['perm_publish']); // save to ACL as well + $this->Session->setFlash(__('The group has been saved')); + $this->_refreshAuth(); // in case we modify ourselves + $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The group could not be saved. Please, try again.')); + } + } else { + $this->Group->recursive=0; + $this->Group->read(null, $id); + //$this->Group->set('password', ''); // TODO set password? + $this->request->data = $this->Group->data; + + } + } + +/** + * admin_delete method + * + * @param string $id + * @return void + */ + public function admin_delete($id = null) { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException(); + } + $this->Group->id = $id; + if (!$this->Group->exists()) { + throw new NotFoundException(__('Invalid group')); + } + if ($this->Group->delete()) { + $this->Session->setFlash(__('Group deleted')); + $this->redirect(array('action' => 'index')); + } + $this->Session->setFlash(__('Group was not deleted')); + $this->redirect(array('action' => 'index')); + } + +/** + * saveAcl method + * + * @param string $id + * @return void + */ + public function saveAcl($group, $permAdd = false, $permModify = false, $permPublish = false) { + // this all could need some 'if-changed then do' + + if ($permAdd) { + $this->Acl->allow($group, 'controllers/Events/add'); + $this->Acl->allow($group, 'controllers/Attributes/add'); + } else { + $this->Acl->deny($group, 'controllers/Events/add'); + $this->Acl->deny($group, 'controllers/Attributes/add'); + } + if ($permModify) { + $this->Acl->allow($group, 'controllers/Events/edit'); + $this->Acl->allow($group, 'controllers/Attributes/edit'); + } else { + $this->Acl->deny($group, 'controllers/Events/edit'); + $this->Acl->deny($group, 'controllers/Attributes/edit'); + } + if ($permPublish) { + $this->Acl->allow($group, 'controllers/Events/publish'); + } else { + $this->Acl->deny($group, 'controllers/Events/publish'); + } + } +} diff --git a/app/Controller/LogsController.php b/app/Controller/LogsController.php new file mode 100755 index 000000000..a36810b12 --- /dev/null +++ b/app/Controller/LogsController.php @@ -0,0 +1,112 @@ + 60, + 'order' => array( + 'Log.id' => 'DESC' + ) + ); + public $helpers = array('Js' => array('Jquery')); + + function beforeFilter() { + parent::beforeFilter(); + + // permit reuse of CSRF tokens on the search page. + if ('search' == $this->request->params['action']) { + $this->Security->csrfUseOnce = false; + } + } + + public function isAuthorized($user) { + // Admins can access everything + if (parent::isAuthorized($user)) { + return true; + } + // the other pages are allowed by logged in users + return true; + } + +/** + * admin_index method + * + * @return void + */ + public function admin_index() { + $this->Log->recursive = 0; + $this->set('logs', $this->paginate()); + } + +/** + * admin_view method + * + * @param string $id + * @return void + */ + public function admin_view($id = null) { + $this->Log->id = $id; + if (!$this->Log->exists()) { + throw new NotFoundException(__('Invalid log')); + } + $this->set('log', $this->Log->read(null, $id)); + } + + public function search() { + $this->admin_search(); + } + + public function admin_search() { + + $this->set('action_definitions', $this->Log->action_definitions); + + if ($this->request->is('post')) { + $email = $this->request->data['Log']['email']; + $org = $this->request->data['Log']['org']; + $action = $this->request->data['Log']['action']; + $title = $this->request->data['Log']['title']; + $change = $this->request->data['Log']['change']; + + // search the db + $conditions = array(); + if($email) { + $conditions['Log.email LIKE'] = '%'.$email.'%'; + } + if($org) { + $conditions['Log.org LIKE'] = '%'.$org.'%'; + } + if($action != 'ALL') { + $conditions['Log.action ='] = $action; + } + if($title) { + $conditions['Log.title LIKE'] = '%'.$title.'%'; + } + if($change) { + $conditions['Log.change LIKE'] = '%'.$change.'%'; + } + $this->Log->recursive = 0; + $this->paginate = array( + 'conditions' => $conditions + ); + $this->set('logs', $this->paginate()); + + // set the same view as the index page + $this->render('index'); + } else { + // no search keyword is given, show the search form + + // combobox for actions + $actions = array('ALL'); + $actions = array_merge($actions, $this->Log->validate['action']['rule'][1]); + $actions = $this->_arrayToValuesIndexArray($actions); + $this->set('actions',compact('actions')); + } + } +} diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php old mode 100644 new mode 100755 index ca0be4a8e..3ff2b0ee7 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -9,7 +9,7 @@ App::uses('Xml', 'Utility'); */ class ServersController extends AppController { - public $components = array('Security' ,'RequestHandler'); + public $components = array('Acl' ,'Security' ,'RequestHandler'); // XXX ACL component public $paginate = array( 'limit' => 60, 'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php old mode 100644 new mode 100755 index b0ff283c3..a160e5d4b --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -8,7 +8,7 @@ App::uses('AppController', 'Controller'); class UsersController extends AppController { - public $components = array('Security'); + public $components = array('Acl','Security'); // TODO ACL, components public $paginate = array( 'limit' => 60, 'order' => array( @@ -74,7 +74,7 @@ class UsersController extends AppController { // Only own profile verified by isAuthorized if ($this->request->is('post') || $this->request->is('put')) { // What fields should be saved (allowed to be saved) - $fieldList=array('email', 'autoalert', 'gpgkey', 'nids_sid' ); + $fieldList=array('email', 'autoalert', 'gpgkey', 'nids_sid'); // TODO ACL, check, My Profile not edit group_id. if ("" != $this->request->data['User']['password']) $fieldList[] = 'password'; // Save the data @@ -92,6 +92,9 @@ class UsersController extends AppController { $this->request->data = $this->User->data; } $this->request->data['User']['org']=$this->Auth->user('org'); + // XXX ACL groups + $groups = $this->User->Group->find('list'); + $this->set(compact('groups')); } /** @@ -164,6 +167,9 @@ class UsersController extends AppController { $newkey = $this->User->generateAuthKey(); $this->set('authkey', $newkey); } + // XXX ACL groups + $groups = $this->User->Group->find('list'); + $this->set(compact('groups')); } /** @@ -182,9 +188,48 @@ class UsersController extends AppController { foreach (array_keys($this->request->data['User']) as $field) { if($field != 'password') array_push($fields, $field); } + // TODO Audit, extraLog, fields get orig + $fields_oldValues = array(); + foreach ($fields as $field) { + if($field != 'confirm_password') array_push($fields_oldValues, $this->User->field($field)); + else array_push($fields_oldValues, $this->User->field('password')); + } + // TODO Audit, extraLog, fields get orig END if ("" != $this->request->data['User']['password']) $fields[] = 'password'; if ($this->User->save($this->request->data, true, $fields)) { + // TODO Audit, extraLog, fields compare + // newValues to array + $fields_newValues = array(); + foreach ($fields as $field) { + if($field != 'confirm_password') { + $newValue = $this->data['User'][$field]; + if (gettype($newValue) == 'array') { + $newValueStr = ''; + $c_p = 0; + foreach ($newValue as $newValuePart) { + if ($c_p < 2) $newValueStr .= '-' . $newValuePart; + else $newValueStr = $newValuePart.$newValueStr; + $c_p++; + } + array_push($fields_newValues, $newValueStr); + } + else array_push($fields_newValues, $newValue); + } + else array_push($fields_newValues, $this->data['User']['password']); + } + // compare + $fields_result_str = ''; + $c = 0; + foreach ($fields as $field) { + if ($fields_oldValues[$c] != $fields_newValues[$c]) { + if($field != 'confirm_password') $fields_result_str = $fields_result_str. ', '.$field.' ('.$fields_oldValues[$c]. ') => ('.$fields_newValues[$c].')'; + } + $c++; + } + $fields_result_str = substr($fields_result_str, 2); + $this->extraLog("admin_modify", "user", $fields_result_str); // TODO Audit, check: modify User + // TODO Audit, extraLog, fields compare END $this->Session->setFlash(__('The user has been saved')); $this->_refreshAuth(); // in case we modify ourselves $this->redirect(array('action' => 'index')); @@ -198,6 +243,13 @@ class UsersController extends AppController { $this->request->data = $this->User->data; } + // TODO ACL CLEANUP combobox for orgs + $org_ids = array('ADMIN', 'NCIRC','Other MOD'); + $org_ids = $this->_arrayToValuesIndexArray($org_ids); + $this->set('org_ids',compact('org_ids')); + // XXX ACL, Groups in Users + $groups = $this->User->Group->find('list'); + $this->set(compact('groups')); } /** @@ -226,6 +278,7 @@ class UsersController extends AppController { public function login() { // FIXME implement authentication brute-force protection if ($this->Auth->login()) { + $this->extraLog("login"); // TODO Audit, extraLog, check: customLog i.s.o. extraLog, no auth user?: $this->User->customLog('login', $this->Auth->user('id'), array('title' => '','user_id' => $this->Auth->user('id'),'email' => $this->Auth->user('email'),'org' => 'IN2')); $this->redirect($this->Auth->redirect()); } else { // don't display "invalid user" before first login attempt @@ -241,7 +294,7 @@ class UsersController extends AppController { } // News page - $new_newsdate = new DateTime("2012-03-27"); + $new_newsdate = new DateTime("2012-03-27"); // TODO general, fixed odd date?? $newsdate = new DateTime($this->Auth->user('newsread')); if ($new_newsdate > $newsdate) { $this->redirect(array('action' => 'news')); @@ -252,6 +305,7 @@ class UsersController extends AppController { } public function logout() { + $this->extraLog("logout"); // TODO Audit, extraLog, check: customLog i.s.o. extraLog, $this->User->customLog('logout', $this->Auth->user('id'), array()); $this->Session->setFlash('Good-Bye'); $this->redirect($this->Auth->logout()); } @@ -349,7 +403,74 @@ class UsersController extends AppController { $this->_refreshAuth(); // refresh auth info } + public function extraLog($action = null, $description = null, $fields_result = null) { // TODO move audit to AuditsController? + // configuration + ClassRegistry::init('ConnectionManager'); + $dbh = ConnectionManager::getDataSource('default'); + $dbhost = $dbh->config['host']; + $dbport = $dbh->config['port']; + $dbname = $dbh->config['database']; + $dbuser = $dbh->config['login']; + $dbpass = $dbh->config['password']; + $dbprefix = $dbh->config['prefix']; // TODO Audit, extra, db prefix delimiter? + + // database connection + $conn = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname",$dbuser,$dbpass); + // new data + $user_id = $this->Auth->user('id'); + $model = 'User'; + $model_id = $this->Auth->user('id'); + $org = $this->Auth->user('org'); + $email = $this->Auth->user('email'); + $action_date = new DateTime(); + $action_date_str = $action_date->format('Y-m-d H:i:sP'); + $description = "User (". $this->Auth->user('id')."): " .$this->Auth->user('email'); + // query + $sql = "INSERT INTO ".$dbprefix."logs (org,email,created,action,title,`change`) VALUES (:org,:email,:created,:action,:title,:change)"; + $q = $conn->prepare($sql); + $q->execute(array(':org'=>$org, + ':email'=>$email, + ':created'=>$action_date_str, + ':action'=>$action, + ':title'=>$description, + ':change'=>$fields_result)); + // database connection disconnect + $dbh = null; + + // write to syslogd as well + $syslog = new SysLog(); + if ($fields_result) $syslog->write('notice', $description.' -- '.$action.' -- '.$fields_result); + else $syslog->write('notice', $description.' -- '.$action); + } + + // used for fields_before and fields for audit + public function arrayCopy( array $array ) { + $result = array(); + foreach( $array as $key => $val ) { + if( is_array( $val ) ) { + $result[$key] = arrayCopy( $val ); + } elseif ( is_object( $val ) ) { + $result[$key] = clone $val; + } else { + $result[$key] = $val; + } + } + return $result; + } + + public function setgroupid($fk = '2') { + $params = array( + 'conditions' => array('User.group_id' => ''), + 'recursive' => 0, + 'fields' => array('User.id'), + ); + $users = $this->User->find('all', $params); + foreach ($users as $user) { + $this->User->id = $user['User']['id']; + $this->User->saveField('group_id', $fk); + } + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php old mode 100644 new mode 100755 index 7bb70d41e..b982df418 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -9,6 +9,16 @@ App::uses('File', 'Utility'); * @property Event $Event */ class Attribute extends AppModel { + + var $combinedKeys = array('event_id', 'category', 'type'); + + var $name = 'Attribute'; // TODO general + var $actsAs = array('Logable' => array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full' + )); + /** * Display field * diff --git a/app/Model/Event.php b/app/Model/Event.php old mode 100644 new mode 100755 index 6d4523a86..0f13361fc --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -7,6 +7,14 @@ App::uses('AppModel', 'Model'); * @property Attribute $Attribute */ class Event extends AppModel { + + var $name = 'Event'; // TODO general + var $actsAs = array('Logable' => array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full' + )); + /** * Display field * diff --git a/app/Model/Group.php b/app/Model/Group.php new file mode 100755 index 000000000..46cec76c7 --- /dev/null +++ b/app/Model/Group.php @@ -0,0 +1,58 @@ + array( + 'notempty' => array( + 'rule' => array('notempty'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + ); + + //The Associations below have been created with all possible keys, those that are not needed can be removed + +/** + * hasMany associations + * + * @var array + */ + public $hasMany = array( + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'group_id', + 'dependent' => false, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) + ); + + // TODO ACL: 1: be requester to CakePHP ACL system + public $actsAs = array('Acl' => array('type' => 'requester')); + + // TODO ACL: 2: hook Group into CakePHP ACL system (so link to aros) + public function parentNode() { + return null; + } + +} diff --git a/app/Model/Log.php b/app/Model/Log.php new file mode 100755 index 000000000..ae40bc75b --- /dev/null +++ b/app/Model/Log.php @@ -0,0 +1,31 @@ + array( + 'rule' => array('inList', array( + 'login', + 'logout', + 'add', + 'edit', + 'delete', + 'publish' // FIXME remove this once all attributes have a category. Otherwise sigs without category are not shown in the list + )), + 'message' => 'Options : ...' + ) + ); + + public $action_definitions = array( + 'login' => array('desc' => 'Login action', 'formdesc' => "Login action"), + 'logout' => array('desc' => 'Logout action', 'formdesc' => "Logout action"), + 'add' => array('desc' => 'Add action', 'formdesc' => "Add action"), + 'edit' => array('desc' => 'Edit action', 'formdesc' => "Edit action"), + 'delete' => array('desc' => 'Delete action', 'formdesc' => "Delete action"), + 'publish' => array('desc' => "Publish action", 'formdesc' => "Publish action") + ); +} \ No newline at end of file diff --git a/app/Model/Server.php b/app/Model/Server.php old mode 100644 new mode 100755 index a7ebad244..b36e69f3b --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -5,6 +5,14 @@ App::uses('AppModel', 'Model'); * */ class Server extends AppModel { + + var $name = 'Server'; // TODO general + var $actsAs = array('Logable' => array( // TODO Audit, logable, check: 'userModel' and 'userKey' can be removed given default + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full' + )); + /** * Display field * diff --git a/app/Model/User.php b/app/Model/User.php old mode 100644 new mode 100755 index d482f088c..91e5fa9e6 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -15,6 +15,13 @@ class User extends AppModel { * @var string */ public $displayField = 'email'; + public $orgField = 'org'; // TODO Audit, LogableBehaviour + org +/** + * Model Name + * + * @var string + */ + var $name = 'User'; // TODO general /** * Validation rules * @@ -67,6 +74,16 @@ class User extends AppModel { //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), + 'org_id' => array( + 'notempty' => array( + 'rule' => array('notempty'), + 'message' => 'Please specify the organisation ID where you are working.', // TODO ACL, org_id in Users + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), 'email' => array( 'email' => array( 'rule' => array('email'), @@ -155,6 +172,21 @@ class User extends AppModel { //The Associations below have been created with all possible keys, those that are not needed can be removed +/** + * belongsTo associations + * + * @var array + */ + public $belongsTo = array( + 'Group' => array( + 'className' => 'Group', + 'foreignKey' => 'group_id', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) + ); + /** * hasMany associations * @@ -176,7 +208,32 @@ class User extends AppModel { ) ); + // TODO ACL: 1: be requester to CakePHP ACL system + public $actsAs = array('Acl' => array('type' => 'requester', 'enabled' => false)); // TODO ACL, + 'enabled' => false + + // TODO ACL: 2: hook User into CakePHP ACL system (so link to aros) + public function parentNode() { + if (!$this->id && empty($this->data)) { + return null; + } + if (isset($this->data['User']['group_id'])) { + $groupId = $this->data['User']['group_id']; + } else { + $groupId = $this->field('group_id'); + } + if (!$groupId) { + return null; + } else { + return array('Group' => array('id' => $groupId)); + } + } + // TODO ACL: 3: rights on Groups: http://stackoverflow.com/questions/6154285/aros-table-in-cakephp-is-still-including-users-even-after-bindnode + function bindNode($user) { + // return array('model' => 'Group', 'foreign_key' => $user['User']['group_id']); + return array('Group' => array('id' => $user['User']['group_id'])); + } + public function beforeSave() { if (isset($this->data[$this->alias]['password'])) { $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']); diff --git a/app/Plugin/AclExtras/Console/Command/AclExtrasShell.php b/app/Plugin/AclExtras/Console/Command/AclExtrasShell.php new file mode 100755 index 000000000..7ac1a36f2 --- /dev/null +++ b/app/Plugin/AclExtras/Console/Command/AclExtrasShell.php @@ -0,0 +1,308 @@ + + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +App::uses('Controller', 'Controller'); +App::uses('ComponentCollection', 'Controller'); +App::uses('AclComponent', 'Controller/Component'); +App::uses('DbAcl', 'Model'); + +/** + * Shell for ACO extras + * + * @package acl_extras + * @subpackage acl_extras.Console.Command + */ +class AclExtrasShell extends Shell { +/** + * Contains instance of AclComponent + * + * @var AclComponent + * @access public + */ + public $Acl; + +/** + * Contains arguments parsed from the command line. + * + * @var array + * @access public + */ + public $args; + +/** + * Contains database source to use + * + * @var string + * @access public + */ + public $dataSource = 'default'; + +/** + * Root node name. + * + * @var string + **/ + public $rootNode = 'controllers'; + +/** + * Internal Clean Actions switch + * + * @var boolean + **/ + public $_clean = false; + +/** + * Start up And load Acl Component / Aco model + * + * @return void + **/ + public function startup() { + parent::startup(); + $collection = new ComponentCollection(); + $this->Acl = new AclComponent($collection); + $controller = null; + $this->Acl->startup($controller); + $this->Aco = $this->Acl->Aco; + } + +/** + * Sync the ACO table + * + * @return void + **/ + function aco_sync() { + $this->_clean = true; + $this->aco_update(); + } +/** + * Updates the Aco Tree with new controller actions. + * + * @return void + **/ + function aco_update() { + $root = $this->_checkNode($this->rootNode, $this->rootNode, null); + $controllers = $this->getControllerList(); + $this->_updateControllers($root, $controllers); + + $plugins = CakePlugin::loaded(); + foreach ($plugins as $plugin) { + $controllers = $this->getControllerList($plugin); + + $path = $this->rootNode . '/' . $plugin; + $pluginRoot = $this->_checkNode($path, $plugin, $root['Aco']['id']); + $this->_updateControllers($pluginRoot, $controllers, $plugin); + } + $this->out(__('Aco Update Complete')); + return true; + } + +/** + * Updates a collection of controllers. + * + * @param array $root Array or ACO information for root node. + * @param array $controllers Array of Controllers + * @param string $plugin Name of the plugin you are making controllers for. + * @return void + */ + function _updateControllers($root, $controllers, $plugin = null) { + $dotPlugin = $pluginPath = $plugin; + if ($plugin) { + $dotPlugin .= '.'; + $pluginPath .= '/'; + } + $appIndex = array_search($plugin . 'AppController', $controllers); + if ($appIndex !== false) { + App::uses($plugin . 'AppController', $dotPlugin . 'Controller'); + unset($controllers[$appIndex]); + } + // look at each controller + foreach ($controllers as $controller) { + App::uses($controller, $dotPlugin . 'Controller'); + $controllerName = preg_replace('/Controller$/', '', $controller); + + $path = $this->rootNode . '/' . $pluginPath . $controllerName; + $controllerNode = $this->_checkNode($path, $controllerName, $root['Aco']['id']); + $this->_checkMethods($controller, $controllerName, $controllerNode, $pluginPath); + } + if ($this->_clean) { + if (!$plugin) { + $controllers = array_merge($controllers, App::objects('plugin', null, false)); + } + $controllerFlip = array_flip($controllers); + + $this->Aco->id = $root['Aco']['id']; + $controllerNodes = $this->Aco->children(null, true); + foreach ($controllerNodes as $ctrlNode) { + $alias = $ctrlNode['Aco']['alias']; + $name = $alias . 'Controller'; + if (!isset($controllerFlip[$name]) && !isset($controllerFlip[$alias])) { + if ($this->Aco->delete($ctrlNode['Aco']['id'])) { + $this->out(__( + 'Deleted %s and all children', + $this->rootNode . '/' . $ctrlNode['Aco']['alias'] + ), 1, Shell::VERBOSE); + } + } + } + } + } + +/** + * Get a list of controllers in the app and plugins. + * + * Returns an array of path => import notation. + * + * @param string $plugin Name of plugin to get controllers for + * @return array + **/ + function getControllerList($plugin = null) { + if (!$plugin) { + $controllers = App::objects('Controller', null, false); + } else { + $controllers = App::objects($plugin . '.Controller', null, false); + } + return $controllers; + } + +/** + * Check a node for existance, create it if it doesn't exist. + * + * @param string $path + * @param string $alias + * @param int $parentId + * @return array Aco Node array + */ + function _checkNode($path, $alias, $parentId = null) { + $node = $this->Aco->node($path); + if (!$node) { + $this->Aco->create(array('parent_id' => $parentId, 'model' => null, 'alias' => $alias)); + $node = $this->Aco->save(); + $node['Aco']['id'] = $this->Aco->id; + $this->out(__('Created Aco node: %s', $path), 1, Shell::VERBOSE); + } else { + $node = $node[0]; + } + return $node; + } + +/** + * Check and Add/delete controller Methods + * + * @param string $controller + * @param array $node + * @param string $plugin Name of plugin + * @return void + */ + function _checkMethods($className, $controllerName, $node, $pluginPath = false) { + $baseMethods = get_class_methods('Controller'); + $actions = get_class_methods($className); + $methods = array_diff($actions, $baseMethods); + foreach ($methods as $action) { + if (strpos($action, '_', 0) === 0) { + continue; + } + $path = $this->rootNode . '/' . $pluginPath . $controllerName . '/' . $action; + $this->_checkNode($path, $action, $node['Aco']['id']); + } + + if ($this->_clean) { + $actionNodes = $this->Aco->children($node['Aco']['id']); + $methodFlip = array_flip($methods); + foreach ($actionNodes as $action) { + if (!isset($methodFlip[$action['Aco']['alias']])) { + $this->Aco->id = $action['Aco']['id']; + if ($this->Aco->delete()) { + $path = $this->rootNode . '/' . $controllerName . '/' . $action['Aco']['alias']; + $this->out(__('Deleted Aco node %s', $path), 1, Shell::VERBOSE); + } + } + } + } + return true; + } + + public function getOptionParser() { + return parent::getOptionParser() + ->description(__("Better manage, and easily synchronize you application's ACO tree")) + ->addSubcommand('aco_update', array( + 'help' => __('Add new ACOs for new controllers and actions. Does not remove nodes from the ACO table.') + ))->addSubcommand('aco_sync', array( + 'help' => __('Perform a full sync on the ACO table.' . + 'Will create new ACOs or missing controllers and actions.' . + 'Will also remove orphaned entries that no longer have a matching controller/action') + ))->addSubcommand('verify', array( + 'help' => __('Verify the tree structure of either your Aco or Aro Trees'), + 'parser' => array( + 'arguments' => array( + 'type' => array( + 'required' => true, + 'help' => __('The type of tree to verify'), + 'choices' => array('aco', 'aro') + ) + ) + ) + ))->addSubcommand('recover', array( + 'help' => __('Recover a corrupted Tree'), + 'parser' => array( + 'arguments' => array( + 'type' => array( + 'required' => true, + 'help' => __('The type of tree to recover'), + 'choices' => array('aco', 'aro') + ) + ) + ) + )); + } + +/** + * Verify a Acl Tree + * + * @param string $type The type of Acl Node to verify + * @access public + * @return void + */ + function verify() { + $type = Inflector::camelize($this->args[0]); + $return = $this->Acl->{$type}->verify(); + if ($return === true) { + $this->out(__('Tree is valid and strong')); + } else { + $this->err(print_r($return, true)); + return false; + } + } +/** + * Recover an Acl Tree + * + * @param string $type The Type of Acl Node to recover + * @access public + * @return void + */ + function recover() { + $type = Inflector::camelize($this->args[0]); + $return = $this->Acl->{$type}->recover(); + if ($return === true) { + $this->out(__('Tree has been recovered, or tree did not need recovery.')); + } else { + $this->err(__('Tree recovery failed.')); + return false; + } + } +} diff --git a/app/Plugin/AclExtras/README.md b/app/Plugin/AclExtras/README.md new file mode 100755 index 000000000..590216c60 --- /dev/null +++ b/app/Plugin/AclExtras/README.md @@ -0,0 +1,27 @@ +# Acl Extras + +Acl Extras provides a console app that helps you manage DbAcl records more easily. Its main feature and purpose is to make generating Aco nodes for all your controllers and actions easier. It also includes some helper methods for verifying and recovering corrupted trees. + +## Installation + +Clone the repo or download a tarball and install it into `app/Plugin/AclExtras` or in any of your pluginPaths. + +## Usage + +You can find a list of commands by running `Console/cake AclExtras.AclExtras -h` from your command line. + +### Setting up the contorller + +You'll need to configure AuthComponent to use the Actions authorization method. +In your `beforeFilter` add the following: + + $this->Auth->authorize = 'actions'; + $this->Auth->actionPath = 'controllers/'; + +## Issues + +If you find an issue in the code or want to suggest something, please use the tickets at http://github.com/markstory/acl_extras/issues + +## License + +Acl Extras is licensed under the MIT license. \ No newline at end of file diff --git a/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php b/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php new file mode 100755 index 000000000..83983ce0d --- /dev/null +++ b/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php @@ -0,0 +1,240 @@ + + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::uses('Shell', 'Console'); +App::uses('Aco', 'Model'); +App::uses('AclComponent', 'Controller/Component'); +App::uses('Controller', 'Controller'); +App::uses('AclExtrasShell', 'AclExtras.Console/Command'); + + +//Mock::generate('Aco', 'MockAco', array('children', 'verify', 'recover')); + +//import test controller class names. +include dirname(dirname(dirname(dirname(__FILE__)))) . DS . 'test_controllers.php'; + +/** + * AclExtras Shell Test case + * + * @package acl_extras.tests.cases + */ +class AclExtrasShellTestCase extends CakeTestCase { + + public $fixtures = array('core.aco', 'core.aro', 'core.aros_aco'); + +/** + * startTest + * + * @return void + * @access public + */ + function setUp() { + parent::setUp(); + Configure::write('Acl.classname', 'DbAcl'); + Configure::write('Acl.database', 'test'); + + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + + $this->Task = $this->getMock( + 'AclExtrasShell', + array('in', 'out', 'hr', 'createFile', 'error', 'err', 'clear', 'getControllerList'), + array($out, $out, $in) + ); + } + +/** + * end the test + * + * @return void + **/ + function tearDown() { + parent::tearDown(); + unset($this->Task); + } + +/** + * test recover + * + * @return void + **/ + function testRecover() { + $this->Task->startup(); + $this->Task->args = array('Aco'); + $this->Task->Acl->Aco = $this->getMock('Aco', array('recover')); + $this->Task->Acl->Aco->expects($this->once()) + ->method('recover') + ->will($this->returnValue(true)); + + $this->Task->expects($this->once()) + ->method('out') + ->with($this->matchesRegularExpression('/recovered/')); + + $this->Task->recover(); + } + +/** + * test verify + * + * @return void + **/ + function testVerify() { + $this->Task->startup(); + $this->Task->args = array('Aco'); + $this->Task->Acl->Aco = $this->getMock('Aco', array('verify')); + $this->Task->Acl->Aco->expects($this->once()) + ->method('verify') + ->will($this->returnValue(true)); + + $this->Task->expects($this->once()) + ->method('out') + ->with($this->matchesRegularExpression('/valid/')); + + $this->Task->verify(); + } + +/** + * test startup + * + * @return void + **/ + function testStartup() { + $this->assertEqual($this->Task->Acl, null); + $this->Task->startup(); + $this->assertInstanceOf('AclComponent', $this->Task->Acl); + } + +/** + * clean fixtures and setup mock + * + * @return void + **/ + function _cleanAndSetup() { + $tableName = $this->db->fullTableName('acos'); + $this->db->execute('DELETE FROM ' . $tableName); + $this->Task->expects($this->any()) + ->method('getControllerList') + ->will($this->returnValue(array('CommentsController', 'PostsController', 'BigLongNamesController'))); + + $this->Task->startup(); + } +/** + * Test aco_update method. + * + * @return void + **/ + function testAcoUpdate() { + $this->_cleanAndSetup(); + $this->Task->aco_update(); + + $Aco = $this->Task->Acl->Aco; + + $result = $Aco->node('controllers/Comments'); + $this->assertEqual($result[0]['Aco']['alias'], 'Comments'); + + $result = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($result), 3); + $this->assertEqual($result[0]['Aco']['alias'], 'add'); + $this->assertEqual($result[1]['Aco']['alias'], 'index'); + $this->assertEqual($result[2]['Aco']['alias'], 'delete'); + + $result = $Aco->node('controllers/Posts'); + $this->assertEqual($result[0]['Aco']['alias'], 'Posts'); + $result = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($result), 3); + + $result = $Aco->node('controllers/BigLongNames'); + $this->assertEqual($result[0]['Aco']['alias'], 'BigLongNames'); + $result = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($result), 4); + } + +/** + * test syncing of Aco records + * + * @return void + **/ + function testAcoSyncRemoveMethods() { + $this->_cleanAndSetup(); + $this->Task->aco_update(); + + $Aco = $this->Task->Acl->Aco; + $Aco->cacheQueries = false; + + $result = $Aco->node('controllers/Comments'); + $new = array( + 'parent_id' => $result[0]['Aco']['id'], + 'alias' => 'some_method' + ); + $Aco->create($new); + $Aco->save(); + $children = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($children), 4); + + $this->Task->aco_sync(); + $children = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($children), 3); + + $method = $Aco->node('controllers/Commments/some_method'); + $this->assertFalse($method); + } + +/** + * test adding methods with aco_update + * + * @return void + **/ + function testAcoUpdateAddingMethods() { + $this->_cleanAndSetup(); + $this->Task->aco_update(); + + $Aco = $this->Task->Acl->Aco; + $Aco->cacheQueries = false; + + $result = $Aco->node('controllers/Comments'); + $children = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($children), 3); + + $Aco->delete($children[0]['Aco']['id']); + $Aco->delete($children[1]['Aco']['id']); + $this->Task->aco_update(); + + $children = $Aco->children($result[0]['Aco']['id']); + $this->assertEqual(count($children), 3); + } + +/** + * test adding controllers on sync + * + * @return void + **/ + function testAddingControllers() { + $this->_cleanAndSetup(); + $this->Task->aco_update(); + + $Aco = $this->Task->Acl->Aco; + $Aco->cacheQueries = false; + + $result = $Aco->node('controllers/Comments'); + $Aco->delete($result[0]['Aco']['id']); + + $this->Task->aco_update(); + $newResult = $Aco->node('controllers/Comments'); + $this->assertNotEqual($newResult[0]['Aco']['id'], $result[0]['Aco']['id']); + } +} diff --git a/app/Plugin/AclExtras/Test/test_controllers.php b/app/Plugin/AclExtras/Test/test_controllers.php new file mode 100755 index 000000000..714e7616a --- /dev/null +++ b/app/Plugin/AclExtras/Test/test_controllers.php @@ -0,0 +1,53 @@ +isWindows()) { + $default_facility = LOG_USER; + } else { + $default_facility= LOG_LOCAL0; + } + $options += array('ident' => LOGS, 'facility' => $default_facility); + $this->_ident = $options['ident']; + $this->_facility = $options['facility']; + } + +/** +* Utilty method to identify if we're running on a Windows box. +* +* @return boolean if running on windows. +*/ + function isWindows() { + return (DIRECTORY_SEPARATOR == '\\' ? true : false); + } + +/** +* Implements writing to the specified syslog +* +* @param string $type The type of log you are making. +* @param string $message The message you want to log. +* @return boolean success of write. +*/ + function write($type, $message) { + $debugTypes = array('notice', 'info', 'debug'); + $priority = LOG_INFO; + if ($type == 'error' || $type == 'warning') { + $priority = LOG_ERR; + } elseif (in_array($type, $debugTypes)) { + $priority = LOG_DEBUG; + } + $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; + if (!openlog($this->_ident, LOG_PID | LOG_PERROR, $this->_facility)) { + return false; + } + $result = syslog($priority, $output); + closelog(); + return $result; + } +} +?> \ No newline at end of file diff --git a/app/README.txt b/app/README.txt index e17eac6c5..bc41f8ca8 100755 --- a/app/README.txt +++ b/app/README.txt @@ -1,14 +1,25 @@ - -TODOs +TODOs v0.2.2 to v0.2.3 ----- +DB Update +- UpdateShell with in/out + Auth - Prevent bruteforce auth attempts -implement auditing/logging system -- add / edit events and signatures -- failed / success logins (with source IP, headers,...) +Acl +- inactive buttons + - must be non-clickable. + - JavaScript include. + - DOM read and disable button_offXX. +- clean-up to first cut. + - saveAcl, from GroupsController to AppController and inherit to *Controllers. + +auditing/logging system +- logins + - add source IP (headers,...); + - failed logins. Security - force cookie reset after login @@ -18,7 +29,7 @@ INSTALLATION INSTRUCTIONS ------------------------- Install the following libraries: apt-get install zip -apt-get install pear +apt-get install php-pear pear install Crypt_GPG # need version >1.3.0 TODO rewrite instructions using git clones and git submodules @@ -77,6 +88,17 @@ Don't forget to change the email, password and authentication key after installa +UPDATE INSTRUCTIONS +------------------- + +To be sure, dump your database before updating. + +CyDefSIG from 0.2.2 to 0.2.3 needs a database migration and population. +This is done executing /var/www/cydefsig/app/Console/shell/migrate-0.2.2-0.2.3.sh +and answer (y)es to all the questions asked. + + + Recommended patches ------------------- -By default CakePHP exposes his name and version in email headers. Apply a patch to remove this behavior. \ No newline at end of file +By default CakePHP exposes his name and version in email headers. Apply a patch to remove this behavior. diff --git a/app/README.ubuntu.txt b/app/README.ubuntu.txt new file mode 100755 index 000000000..c7b3bef46 --- /dev/null +++ b/app/README.ubuntu.txt @@ -0,0 +1,34 @@ +INSTALLATION INSTRUCTIONS +------------------------- +If on Ubuntu, besides the DocumentRoot, +you have to change the AllowOverride from None to All as well. + + DocumentRoot /var/www/cydefsig/app/webroot/ + + Options FollowSymLinks + AllowOverride All + + + Options Indexes FollowSymLinks MultiViews + AllowOverride All + Order allow,deny + allow from all + + +Find the original below, for reference. + + DocumentRoot /var/www + + + Options FollowSymLinks + AllowOverride None + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + + +Now /etc/init.d/apache2 restart +and you are done, and now able to use the application. \ No newline at end of file diff --git a/app/View/Attributes/edit.ctp b/app/View/Attributes/edit.ctp index 448c6fe22..192fdefbf 100755 --- a/app/View/Attributes/edit.ctp +++ b/app/View/Attributes/edit.ctp @@ -1,3 +1,7 @@ +
Form->create('Attribute');?>
@@ -38,7 +42,10 @@
@@ -112,3 +119,10 @@ $('#AttributeType').val(type_value); Js->writeBuffer(); // Write cached scripts ?> + + + \ No newline at end of file diff --git a/app/View/Attributes/index.ctp b/app/View/Attributes/index.ctp index 0605c17ad..74a4586d4 100755 --- a/app/View/Attributes/index.ctp +++ b/app/View/Attributes/index.ctp @@ -1,3 +1,7 @@ +

@@ -35,8 +39,9 @@  
Html->link(__('Edit'), array('action' => 'edit', $attribute['Attribute']['id'])); - echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $attribute['Attribute']['id']), null, __('Are you sure you want to delete this attribute?')); + echo $this->Html->link(__('Edit'), array('action' => 'edit', $attribute['Attribute']['id']), $isAclModify||($attribute['Event']['user_id'] == $me['id']) ? null:array('id' => $button_modify_status.$buttonCounter++, 'class' => $button_modify_status)); + if ($isAclModify || $attribute['Event']['user_id'] == $me['id']) echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $attribute['Attribute']['id']), null, __('Are you sure you want to delete this attribute?')); + else echo $this->Html->link(__('Delete'), array('action' => 'delete', $attribute['Attribute']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); } echo $this->Html->link(__('View'), array('controller' => 'events', 'action' => 'view', $attribute['Attribute']['event_id'])); ?> @@ -64,3 +69,400 @@ element('actions_menu'); ?> + + + \ No newline at end of file diff --git a/app/View/Elements/actions_menu.ctp b/app/View/Elements/actions_menu.ctp index 64b4dbf7b..04ea9f096 100755 --- a/app/View/Elements/actions_menu.ctp +++ b/app/View/Elements/actions_menu.ctp @@ -1,4 +1,5 @@ -
  • Html->link(__('New Event', true), array('controller' => 'events', 'action' => 'add')); ?>
  • + +
  • Html->link(__('New Event', true), array('controller' => 'events', 'action' => 'add'), array('id' => $button_add_status,'class' => $button_add_status,'disabled'=>'disabled','readonly'=>'readonly')); ?>
  • Html->link(__('List Events', true), array('controller' => 'events', 'action' => 'index')); ?>
  • Html->link(__('List Attributes', true), array('controller' => 'attributes', 'action' => 'index')); ?>
  • Html->link(__('Search Attributes', true), array('controller' => 'attributes', 'action' => 'search')); ?>
  • @@ -23,4 +24,10 @@

  • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add', 'admin' => true)); ?>
  • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index', 'admin' => true)); ?>
  • +
  • Html->link(__('New Group', true), array('controller' => 'groups', 'action' => 'add', 'admin' => true)); ?>
  • +
  • Html->link(__('List Groups', true), array('controller' => 'groups', 'action' => 'index', 'admin' => true)); ?>
  • +
  •  
  • +

    +
  • Html->link(__('List Logs', true), array('controller' => 'logs', 'action' => 'index', 'admin' => true)); ?>
  • +
  • Html->link(__('Search Logs', true), array('controller' => 'logs', 'action' => 'admin_search', 'admin' => true,'disabled'=>'disabled','readonly'=>'readonly')); ?>
  • \ No newline at end of file diff --git a/app/View/Events/index.ctp b/app/View/Events/index.ctp index ac354afbf..177c82558 100755 --- a/app/View/Events/index.ctp +++ b/app/View/Events/index.ctp @@ -1,3 +1,9 @@ +

    Events

    @@ -38,13 +44,15 @@ @@ -154,8 +170,8 @@
      -
    • Html->link('Add Attribute', array('controller' => 'attributes', 'action' => 'add', $event['Event']['id']));?>
    • -
    • Html->link('Add Attachment', array('controller' => 'attributes', 'action' => 'add_attachment', $event['Event']['id']));?>
    • +
    • Html->link('Add Attribute', array('controller' => 'attributes', 'action' => 'add', $event['Event']['id']), array('id' =>$button_add_status.$buttonCounter++,'class' => $button_add_status));?>
    • +
    • Html->link('Add Attachment', array('controller' => 'attributes', 'action' => 'add_attachment', $event['Event']['id']),array('id' =>$button_add_status.$buttonCounter++,'class' => $button_add_status));?>
    @@ -166,13 +182,143 @@
      -
    • Html->link(__('Add Attribute', true), array('controller' => 'attributes', 'action' => 'add', $event['Event']['id']));?>
    • -
    • Html->link(__('Add Attachment', true), array('controller' => 'attributes', 'action' => 'add_attachment', $event['Event']['id']));?>
    • -
    • Html->link(__('Edit Event', true), array('action' => 'edit', $event['Event']['id'])); ?>
    • -
    • Form->postLink(__('Delete Event'), array('action' => 'delete', $event['Event']['id']), null, __('Are you sure you want to delete # %s?', $event['Event']['id'])); ?>
    • +
    • Html->link(__('Add Attribute', true), array('controller' => 'attributes', 'action' => 'add', $event['Event']['id']), array('id' =>$button_add_status.$buttonCounter++,'class' => $button_add_status));?>
    • +
    • Html->link(__('Add Attachment', true), array('controller' => 'attributes', 'action' => 'add_attachment', $event['Event']['id']), array('id' =>$button_add_status.$buttonCounter++,'class' => $button_add_status));?>
    • +
    • Html->link(__('Edit Event', true), array('action' => 'edit', $event['Event']['id']), array('id' =>$button_modify_status.$buttonCounter++,'class' => $button_modify_status)); ?>
    • +
    • Form->postLink(__('Delete Event'), array('action' => 'delete', $event['Event']['id']), null, __('Are you sure you want to delete # %s?', $event['Event']['id'])); + else echo $this->Html->link(__('Delete Event'), array('action' => 'delete', $event['Event']['id']), array('id' =>$button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + ?>
    •  
    • element('actions_menu'); ?>
    + + + \ No newline at end of file diff --git a/app/View/Groups/add.ctp b/app/View/Groups/add.ctp new file mode 100755 index 000000000..1e37e2b4f --- /dev/null +++ b/app/View/Groups/add.ctp @@ -0,0 +1,19 @@ +
    +Form->create('Group');?> +
    + + Form->input('name'); + ?> +
    +Form->end(__('Submit'));?> +
    +
    +

    +
      + +
    • Html->link(__('List Groups'), array('action' => 'index'));?>
    • +
    • Html->link(__('List Users'), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User'), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    diff --git a/app/View/Groups/admin_add.ctp b/app/View/Groups/admin_add.ctp new file mode 100755 index 000000000..c1d0972e1 --- /dev/null +++ b/app/View/Groups/admin_add.ctp @@ -0,0 +1,19 @@ +
    +Form->create('Group');?> +
    + + Form->input('name'); + echo $this->Form->input('perm_add'); + echo $this->Form->input('perm_modify'); + echo $this->Form->input('perm_publish'); + echo $this->Form->input('perm_full'); + ?> +
    +Form->end(__('Submit'));?> +
    +
    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/View/Groups/admin_edit.ctp b/app/View/Groups/admin_edit.ctp new file mode 100755 index 000000000..4d813b880 --- /dev/null +++ b/app/View/Groups/admin_edit.ctp @@ -0,0 +1,23 @@ +
    +Form->create('Group');?> +
    + + Form->input('name');?> +
    + + Form->input('perm_add', array( 'label' => 'add')); + echo $this->Form->input('perm_modify', array( 'label' => 'modify')); + echo $this->Form->input('perm_publish', array( 'label' => 'publish')); + echo $this->Form->input('perm_full', array( 'label' => 'full')); + ?> +
    +
    +Form->end(__('Submit'));?> +
    +
    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/View/Groups/admin_index.ctp b/app/View/Groups/admin_index.ctp new file mode 100755 index 000000000..169b227da --- /dev/null +++ b/app/View/Groups/admin_index.ctp @@ -0,0 +1,55 @@ +
    +

    +
    Form->postLink('Publish Event', array('action' => 'alert', $event['Event']['id']), null, 'Are you sure this event is complete and everyone should be informed?'); + if ($isAclPublish || $event['Event']['user_id'] == $me['id']) echo $this->Form->postLink('Publish Event', array('action' => 'alert', $event['Event']['id']), array('action' => 'alert', $event['Event']['id']), 'Are you sure this event is complete and everyone should be informed?'); + else echo $this->Html->link('Publish Event', array('id' =>$button_publish_status.$buttonCounter++,'class' => $button_publish_status, 'action' => 'alert', $event['Event']['id']), array('id' =>$button_publish_status.$buttonCounter++,'class' => $button_publish_status, 'action' => 'alert', $event['Event']['id'])); elseif (0 == $event['Event']['published']) echo 'Not published'; ?> Html->link(__('Edit', true), array('action' => 'edit', $event['Event']['id'])); - echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $event['Event']['id']), null, __('Are you sure you want to delete # %s?', $event['Event']['id'])); + echo $this->Html->link(__('Edit', true), array('action' => 'edit', $event['Event']['id']), $isAclModify||($event['Event']['user_id'] == $me['id']) ? null:array('id' => $button_modify_status.$buttonCounter++, 'class' => $button_modify_status)); + if ($isAclModify || $event['Event']['user_id'] == $me['id']) echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $event['Event']['id']), null, __('Are you sure you want to delete # %s?', $event['Event']['id'])); + else echo $this->Html->link(__('Delete'), array('action' => 'delete', $event['Event']['id']), array('id' =>$button_modify_status.$buttonCounter++,'class' => $button_modify_status)); } ?> Html->link(__('View', true), array('action' => 'view', $event['Event']['id'])); ?> @@ -71,3 +79,580 @@ element('actions_menu'); ?> + + + \ No newline at end of file diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index 101a7243b..644cd3a54 100755 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -1,11 +1,22 @@ +
    • Form->postLink('Publish Event', array('action' => 'alert', $event['Event']['id']), null, 'Are you sure this event is complete and everyone should be informed?'); - echo $this->Form->postLink('Publish (no email)', array('action' => 'publish', $event['Event']['id']), null, 'Publish but do NOT send alert email? Only for minor changes!'); + if ($may_publish) { + echo $this->Form->postLink('Publish Event', array('action' => 'alert', $event['Event']['id']), null, 'Are you sure this event is complete and everyone should be informed?'); + echo $this->Form->postLink('Publish (no email)', array('action' => 'publish', $event['Event']['id']), null, 'Publish but do NOT send alert email? Only for minor changes!'); + } else { + echo $this->Html->link('Publish Event', array('action' => 'alert', $event['Event']['id']), array('id' => $button_publish_status.$buttonCounter++, 'class' => $button_publish_status)); + echo $this->Html->link('Publish (no email)', array('action' => 'publish', $event['Event']['id']), array('id' => $button_publish_status.$buttonCounter++, 'class' => $button_publish_status)); + } ?>
    • Not published
    @@ -141,8 +152,13 @@
    Html->link(__('Edit', true), array('controller' => 'attributes', 'action' => 'edit', $attribute['id'])); - echo $this->Form->postLink(__('Delete'), array('controller' => 'attributes', 'action' => 'delete', $attribute['id']), null, __('Are you sure you want to delete this attribute?')); + if ($isAclModify) { + echo $this->Html->link(__('Edit', true), array('controller' => 'attributes', 'action' => 'edit', $attribute['id'])); + echo $this->Form->postLink(__('Delete'), array('controller' => 'attributes', 'action' => 'delete', $attribute['id']), null, __('Are you sure you want to delete this attribute?')); + } else { + echo $this->Html->link(__('Edit', true), array('controller' => 'attributes', 'action' => 'edit', $attribute['id']), array('id' =>$button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + echo $this->Html->link(__('Delete'), array('controller' => 'attributes', 'action' => 'delete', $attribute['id']), array('id' =>$button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + } ?>
    + + + + + + + + + + + + + + + + + + + + +
    Paginator->sort('id');?>Paginator->sort('name');?>Paginator->sort('add');?>Paginator->sort('modify');?>Paginator->sort('publish');?>Paginator->sort('full');?>
    +   +   +   +   +   +   + Html->link(__('View'), array('admin' => true, 'action' => 'view', $group['Group']['id'])); ?> + Html->link(__('Edit'), array('admin' => true, 'action' => 'edit', $group['Group']['id'])); ?> + Form->postLink(__('Delete'), array('admin' => true, 'action' => 'delete', $group['Group']['id']), null, __('Are you sure you want to delete # %s?', $group['Group']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?>

    + +
    + Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled')); + echo $this->Paginator->numbers(array('separator' => '')); + echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled')); + ?> +
    +
    +
    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/View/Groups/admin_view.ctp b/app/View/Groups/admin_view.ctp new file mode 100755 index 000000000..6e4c0bef8 --- /dev/null +++ b/app/View/Groups/admin_view.ctp @@ -0,0 +1,47 @@ +
    + +

    +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    +
    +

    +
      +
    • Html->link(__('Edit Group'), array('admin' => true, 'action' => 'edit', $group['Group']['id'])); ?>
    • +
    • Form->postLink(__('Delete Group'), array('admin' => true, 'action' => 'delete', $group['Group']['id']), null, __('Are you sure you want to delete # %s?', $group['Group']['id'])); ?>
    • +
    • Html->link(__('List Groups'), array('admin' => true, 'action' => 'index')); ?>
    • +
    • Html->link(__('New Group'), array('admin' => true, 'action' => 'add')); ?>
    • +
    +
    \ No newline at end of file diff --git a/app/View/Groups/edit.ctp b/app/View/Groups/edit.ctp new file mode 100755 index 000000000..75337df81 --- /dev/null +++ b/app/View/Groups/edit.ctp @@ -0,0 +1,21 @@ +
    +Form->create('Group');?> +
    + + Form->input('id'); + echo $this->Form->input('name'); + ?> +
    +Form->end(__('Submit'));?> +
    +
    +

    +
      + +
    • Form->postLink(__('Delete'), array('action' => 'delete', $this->Form->value('Group.id')), null, __('Are you sure you want to delete # %s?', $this->Form->value('Group.id'))); ?>
    • +
    • Html->link(__('List Groups'), array('action' => 'index'));?>
    • +
    • Html->link(__('List Users'), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User'), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    diff --git a/app/View/Groups/index.ctp b/app/View/Groups/index.ctp new file mode 100755 index 000000000..ed37eddde --- /dev/null +++ b/app/View/Groups/index.ctp @@ -0,0 +1,48 @@ +
    +

    + + + + + + + + + + + + + + + + + +
    Paginator->sort('id');?>Paginator->sort('name');?>Paginator->sort('created');?>Paginator->sort('modified');?>
         + Html->link(__('View'), array('action' => 'view', $group['Group']['id'])); ?> + Html->link(__('Edit'), array('action' => 'edit', $group['Group']['id'])); ?> + Form->postLink(__('Delete'), array('action' => 'delete', $group['Group']['id']), null, __('Are you sure you want to delete # %s?', $group['Group']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?>

    + +
    + Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled')); + echo $this->Paginator->numbers(array('separator' => '')); + echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled')); + ?> +
    +
    +
    +

    +
      +
    • Html->link(__('New Group'), array('action' => 'add')); ?>
    • +
    • Html->link(__('List Users'), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User'), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    diff --git a/app/View/Groups/view.ctp b/app/View/Groups/view.ctp new file mode 100755 index 000000000..7d3234f3a --- /dev/null +++ b/app/View/Groups/view.ctp @@ -0,0 +1,40 @@ +
    +

    +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    +
    +
      + element('actions_menu'); ?> +
    +
    \ No newline at end of file diff --git a/app/View/Layouts/default.ctp b/app/View/Layouts/default.ctp old mode 100644 new mode 100755 index 9113e06ef..9590c4df0 --- a/app/View/Layouts/default.ctp +++ b/app/View/Layouts/default.ctp @@ -37,6 +37,8 @@ echo $this->Html->script('jquery-1.7.2.min'); // Include jQuery library ?> + +
    diff --git a/app/View/Logs/admin_index.ctp b/app/View/Logs/admin_index.ctp new file mode 100755 index 000000000..e7848b349 --- /dev/null +++ b/app/View/Logs/admin_index.ctp @@ -0,0 +1,59 @@ +
    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Paginator->sort('id');?>Paginator->sort('email');?>Paginator->sort('org');?>Paginator->sort('created');?>Paginator->sort('action');?>Paginator->sort('title');?>Paginator->sort('change');?>
    +   +   +   +   +   +   +   + Html->link(__('View'), array('admin' => true, 'action' => 'view', $log['Log']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?>

    + +
    + Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled')); + echo $this->Paginator->numbers(array('separator' => '')); + echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled')); + ?> +
    +
    +
    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/View/Logs/admin_search.ctp b/app/View/Logs/admin_search.ctp new file mode 100755 index 000000000..d7cb089d3 --- /dev/null +++ b/app/View/Logs/admin_search.ctp @@ -0,0 +1,53 @@ +
    +Form->create('Log');?> +
    + + Form->input('email', array( 'label' => 'Email')); + echo $this->Form->input('org', array( 'label' => 'Org')); + echo $this->Form->input('action', array('between' => $this->Html->div('forminfo', '', array('id'=> 'LogActionDiv')))); + + echo $this->Form->input('title', array( 'label' => 'Title')); + echo $this->Form->input('change', array( 'label' => 'Change')); + ?> +
    +Form->end(__('Search', true));?> +
    +
    +
      + element('actions_menu'); ?> +
    +
    + +Js->writeBuffer(); // Write cached scripts ?> diff --git a/app/View/Logs/admin_view.ctp b/app/View/Logs/admin_view.ctp new file mode 100755 index 000000000..5654cb4f3 --- /dev/null +++ b/app/View/Logs/admin_view.ctp @@ -0,0 +1,54 @@ +
    + +

    +
    +
    +
    + +   +
    + +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    + +   +
    +
    +
    +
    +

    +
      +
    • Html->link(__('List Logs'), array('admin' => true, 'action' => 'index')); ?>
    • +
    +
    \ No newline at end of file diff --git a/app/View/Logs/index.ctp b/app/View/Logs/index.ctp new file mode 100755 index 000000000..e7848b349 --- /dev/null +++ b/app/View/Logs/index.ctp @@ -0,0 +1,59 @@ +
    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Paginator->sort('id');?>Paginator->sort('email');?>Paginator->sort('org');?>Paginator->sort('created');?>Paginator->sort('action');?>Paginator->sort('title');?>Paginator->sort('change');?>
    +   +   +   +   +   +   +   + Html->link(__('View'), array('admin' => true, 'action' => 'view', $log['Log']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?>

    + +
    + Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled')); + echo $this->Paginator->numbers(array('separator' => '')); + echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled')); + ?> +
    +
    +
    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/View/Servers/index.ctp b/app/View/Servers/index.ctp old mode 100644 new mode 100755 index ec1047e7f..ba2c0a88c --- a/app/View/Servers/index.ctp +++ b/app/View/Servers/index.ctp @@ -1,3 +1,8 @@ +

    @@ -24,8 +29,9 @@
    - Html->link(__('Edit'), array('action' => 'edit', $server['Server']['id'])); ?> - Form->postLink(__('Delete'), array('action' => 'delete', $server['Server']['id']), null, __('Are you sure you want to delete # %s?', $server['Server']['id'])); ?> + Html->link(__('Edit'), array('action' => 'edit', $server['Server']['id']), $isAclModify||($server['Server']['org'] == $me['org']) ? null:array('id' => $button_modify_status.$buttonCounter++, 'class' => $button_modify_status)); ?> + Form->postLink(__('Delete'), array('action' => 'delete', $server['Server']['id']), null, __('Are you sure you want to delete # %s?', $server['Server']['id'])); + else echo $this->Html->link(__('Delete'), array('action' => 'delete', $server['Server']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); ?> Form->postLink(__('Pull'), array('action' => 'pull', $server['Server']['id']) ); ?> Form->postLink(__('Push'), array('action' => 'push', $server['Server']['id']) ); ?> @@ -54,9 +60,404 @@
      -
    • Html->link(__('New Server'), array('controller' => 'servers', 'action' => 'add')); ?>
    • +
    • Html->link(__('New Server'), array('controller' => 'servers', 'action' => 'add'), array('id' =>$button_add_status.$buttonCounter++,'class' => $button_add_status)); ?>
    • Html->link(__('List Servers'), array('controller' => 'servers', 'action' => 'index'));?>
    •  
    • element('actions_menu'); ?>
    + \ No newline at end of file diff --git a/app/View/Users/admin_add.ctp b/app/View/Users/admin_add.ctp old mode 100644 new mode 100755 index 15da789a0..51222612b --- a/app/View/Users/admin_add.ctp +++ b/app/View/Users/admin_add.ctp @@ -7,6 +7,7 @@ echo $this->Form->input('password'); echo $this->Form->input('confirm_password', array('type' => 'password', 'div' => array('class' => 'input password required'))); echo $this->Form->input('org'); + echo $this->Form->input('group_id'); echo $this->Form->input('autoalert'); echo $this->Form->input('authkey', array('value' => $authkey)); echo $this->Form->input('nids_sid'); diff --git a/app/View/Users/admin_edit.ctp b/app/View/Users/admin_edit.ctp old mode 100644 new mode 100755 index 9eb887606..c7240b6b2 --- a/app/View/Users/admin_edit.ctp +++ b/app/View/Users/admin_edit.ctp @@ -7,6 +7,7 @@ echo $this->Form->input('password'); echo $this->Form->input('confirm_password', array('type' => 'password', 'div' => array('class' => 'input password required'))); echo $this->Form->input('org'); + echo $this->Form->input('group_id'); // TODO ACL, User edit group_id. echo $this->Form->input('autoalert'); echo $this->Form->input('authkey'); echo $this->Form->input('nids_sid'); diff --git a/app/View/Users/admin_index.ctp b/app/View/Users/admin_index.ctp old mode 100644 new mode 100755 index 4df624e1b..5771a12d8 --- a/app/View/Users/admin_index.ctp +++ b/app/View/Users/admin_index.ctp @@ -1,9 +1,16 @@ +

    + @@ -20,6 +27,8 @@ + @@ -33,8 +42,10 @@   @@ -59,3 +70,398 @@ element('actions_menu'); ?> + \ No newline at end of file diff --git a/app/View/Users/admin_view.ctp b/app/View/Users/admin_view.ctp old mode 100644 new mode 100755 index 13316c211..ce4329aa7 --- a/app/View/Users/admin_view.ctp +++ b/app/View/Users/admin_view.ctp @@ -1,6 +1,11 @@ +
    -
    • Html->link(__('Edit Profile', true), array('admin' => true, 'action' => 'edit', $user['User']['id'])); ?>
    +
    • Html->link(__('Edit Profile', true), array('admin' => true, 'action' => 'edit', $user['User']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); ?>

    @@ -19,6 +24,11 @@   +
    +
    + Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?> +   +
    @@ -66,12 +76,15 @@

      -
    • Html->link(__('Edit User'), array('admin' => true, 'action' => 'edit', $user['User']['id'])); ?>
    • -
    • Form->postLink(__('Delete User'), array('admin' => true, 'action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete # %s?', $user['User']['id'])); ?>
    • +
    • Html->link(__('Edit User'), array('admin' => true, 'action' => 'edit', $user['User']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); ?>
    • +
    • Form->postLink(__('Delete User'), array('admin' => true, 'action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete # %s?', $user['User']['id'])); + else echo $this->Html->link(__('Delete User'), array('admin' => true, 'action' => 'delete', $user['User']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + ?>
    • Html->link(__('List Users'), array('admin' => true, 'action' => 'index')); ?>
    • -
    • Html->link(__('New User'), array('admin' => true, 'action' => 'add')); ?>
    • +
    • Html->link(__('New User'), array('admin' => true, 'action' => 'add'), array('id' => $button_add_status,'class' => $button_add_status,'disabled'=>'disabled','readonly'=>'readonly')); ?>
    • Html->link(__('List Events'), array('controller' => 'events', 'action' => 'index')); ?>
    • -
    • Html->link(__('New Event'), array('controller' => 'events', 'action' => 'add')); ?>
    • +
    • Html->link(__('New Event'), array('controller' => 'events', 'action' => 'add'), array('id' => $button_add_status,'class' => $button_add_status,'disabled'=>'disabled','readonly'=>'readonly')); ?>
    diff --git a/app/View/Users/edit.ctp b/app/View/Users/edit.ctp index 469550f3d..09a6c7312 100755 --- a/app/View/Users/edit.ctp +++ b/app/View/Users/edit.ctp @@ -8,6 +8,7 @@ echo $this->Form->input('confirm_password', array('type' => 'password', 'div' => array('class' => 'input password required'))); if ($isAdmin) echo $this->Form->input('org'); else echo $this->Form->input('org', array('disabled' => 'disabled')); + echo $this->Form->input('group_id', array('disabled' => 'disabled')); // TODO ACL, check, My Profile not edit group_id. echo $this->Form->input('autoalert'); echo $this->Form->input('nids_sid'); echo $this->Form->input('gpgkey'); diff --git a/app/View/Users/view.ctp b/app/View/Users/view.ctp index b22fbc931..9cbd17f00 100755 --- a/app/View/Users/view.ctp +++ b/app/View/Users/view.ctp @@ -19,6 +19,11 @@   +
    +
    + Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?> +   +
    diff --git a/app/technical_design/TD-ACL.txt b/app/technical_design/TD-ACL.txt new file mode 100755 index 000000000..1b82f0358 --- /dev/null +++ b/app/technical_design/TD-ACL.txt @@ -0,0 +1,16 @@ +ACL Technical Design (TD) + +To use Access Control in CakePHP we use itÅ› own AclComponent. + +http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/simple-acl-controlled-application.html +http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/part-two.html + + CakePHP example, just telling how to populate and connect the CakePHP AclController. + +http://stackoverflow.com/questions/6154285/aros-table-in-cakephp-is-still-including-users-even-after-bindnode + + If ACL on Group level add this small correction, to just only add Groups to CakePHP ACL tables and not to add the Users. + +http://bakery.cakephp.org/articles/theshz/2006/11/28/user-permissions-and-cakephp-acl + + Calling the ACL from within a controller. diff --git a/app/technical_design/TD-Audit.txt b/app/technical_design/TD-Audit.txt new file mode 100755 index 000000000..c4dd3e108 --- /dev/null +++ b/app/technical_design/TD-Audit.txt @@ -0,0 +1,28 @@ +Audit Technical Design (TD) + +To log in CakePHP we use an existing Model Behavior, +to write to a log database table. + +https://github.com/eskil-saatvedt/CakePHP-Assets/blob/master/models/behaviors/LogableBehavior.php + + Adds the logable Model Behavior. + +http://bakery.cakephp.org/articles/rikdc/2010/06/07/syslog-component + + Ads the syslog capability. + +http://bakery.cakephp.org/articles/alkemann/2008/10/21/logablebehavior +http://www.bitsntricks.com/cakephp-logable-behaviour/ + + Short explaination itÅ› use. + +http://stackoverflow.com/questions/9791633/check-if-cakephp-update-changes-a-variable-in-update + + This Logable Model Behavior seemed not to work in the UsersController, +so the change checks are done manual coded in the UsersController. + + + +https://github.com/joebeeson/referee#readme + + Can be handy lateron for log to db or syslog? \ No newline at end of file diff --git a/app/technical_design/TD-forum.txt b/app/technical_design/TD-forum.txt new file mode 100755 index 000000000..993b44a26 --- /dev/null +++ b/app/technical_design/TD-forum.txt @@ -0,0 +1,12 @@ +Forum Technical Design (TD) + +We use a plugin giving Forum use in CakePHP. + +http://milesj.me/code/cakephp/forum + +Alternative PhpBB in conjunction with CakePHP Users and Groups tables. + +http://bakery.cakephp.org/articles/wilsonsheldon/2009/01/13/phpbb3-api-bridge +http://www.phpbb.com/community/viewtopic.php?f=71&t=993475 + + CakePHP and a PhpBB3 forum. \ No newline at end of file diff --git a/app/webroot/css/cake.generic.css b/app/webroot/css/cake.generic.css old mode 100644 new mode 100755 index 5901e6394..888497199 --- a/app/webroot/css/cake.generic.css +++ b/app/webroot/css/cake.generic.css @@ -772,3 +772,11 @@ pre { #url-rewriting-warning { display:none; } + +/* to show a button as off/not-usable */ +a.button_off:link,a.button_off:visited, a.button_off:active { + color:#C1C1C1; + font-size:12px; + background-color:#ffffff; + text-decoration:none; +} \ No newline at end of file diff --git a/app/webroot/js/deactivateButtons.js b/app/webroot/js/deactivateButtons.js new file mode 100755 index 000000000..8e330f4d7 --- /dev/null +++ b/app/webroot/js/deactivateButtons.js @@ -0,0 +1,101 @@ + \ No newline at end of file From 0eb52796ae094118a8cfe908bafc38605629890c Mon Sep 17 00:00:00 2001 From: noud Date: Fri, 29 Jun 2012 08:56:00 +0200 Subject: [PATCH 02/30] Admin Paginator fix. --- app/Config/routes.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Config/routes.php b/app/Config/routes.php index d3bd24e3f..58e312740 100644 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -27,7 +27,11 @@ */ Router::connect('/', array('controller' => 'events', 'action' => 'index')); - + // admin Paginator + Router::connect('/users/admin_index/*', array('controller' => 'users', 'action' => 'index', 'admin' => true)); + Router::connect('/groups/admin_index/*', array('controller' => 'groups', 'action' => 'index', 'admin' => true)); + Router::connect('/logs/admin_index/*', array('controller' => 'logs', 'action' => 'index', 'admin' => true)); + // Activate REST Router::mapResources(array('events')); Router::parseExtensions('xml'); From 9c1c32f959c76e745749bc8abd67f2c0f13bd77f Mon Sep 17 00:00:00 2001 From: noud Date: Fri, 29 Jun 2012 09:36:47 +0200 Subject: [PATCH 03/30] Audit and Access Control granulation in News page. --- app/View/Users/news.ctp | 8 ++++++++ 1 file changed, 8 insertions(+) mode change 100644 => 100755 app/View/Users/news.ctp diff --git a/app/View/Users/news.ctp b/app/View/Users/news.ctp old mode 100644 new mode 100755 index 1a1dc17a8..a1387203e --- a/app/View/Users/news.ctp +++ b/app/View/Users/news.ctp @@ -1,5 +1,13 @@

    News

    +

    June 2012

    +

    Audit
    +There is now log on every login, logout, addition, change and deletion.
    +

    +

    Access Control granulation
    +There is Access Control, an user must be bound to a users-group,
    +so we are able to grant global add, modify and publish rights.
    +

    April 2012

    REST API (output)
    From now on you can use the REST API that uses this XML export. For more information check out the export page.
    From 5bac9ac9284b7e0c15cecc7692c90090d3b37d8a Mon Sep 17 00:00:00 2001 From: noud Date: Mon, 2 Jul 2012 12:52:57 +0200 Subject: [PATCH 04/30] Forgot LogableBehavior in the first commit. --- app/Model/Behavior/LogableBehavior.php | 594 +++++++++++++++++++++++++ 1 file changed, 594 insertions(+) create mode 100755 app/Model/Behavior/LogableBehavior.php diff --git a/app/Model/Behavior/LogableBehavior.php b/app/Model/Behavior/LogableBehavior.php new file mode 100755 index 000000000..747f03530 --- /dev/null +++ b/app/Model/Behavior/LogableBehavior.php @@ -0,0 +1,594 @@ + (Alek), age (28) => (29)] or [name, age] + * + * - "version_id" [int] : cooperates with RevisionBehavior to link the the shadow table (thus linking to old data) + * + * Remember that Logable behavior needs to be added after RevisionBehavior. In fact, just put it last to be safe. + * + * Optionally register what user was responisble for the activity : + * + * - Supply configuration only if defaults are wrong. Example given with defaults : + * + * class Apple extends AppModel { + * var $name = 'Apple'; + * var $actsAs = array('Logable' => array('userModel' => 'User', 'userKey' => 'user_id')); + * [..] + * + * - In AppController (or single controller if only needed once) add these lines to beforeFilter : + * + * if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { + * $this->{$this->modelClass}->setUserData($this->activeUser); + * } + * + * This is not used any longer, as AuthComponent collect the user data instead. + * + * Where "$activeUser" should be an array in the standard format for the User model used : + * + * $activeUser = array( $UserModel->alias => array( $UserModel->primaryKey => 123, $UserModel->displayField => 'Alexander')); + * // any other key is just ignored by this behaviour. + * + * @author Alexander Morland (alexander#maritimecolours.no) + * @co-author Eskil Mjelva Saatvedt + * @co-author Ronny Vindenes + * @co-author Carl Erik Fyllingen + * @contributor Miha + * @category Behavior + * @version 2.3 + * @modified 15.november 2011 by Eskil + */ + +class LogableBehavior extends ModelBehavior { + + public $user = NULL; + + public $UserModel = FALSE; + + public $settings = array(); + + public $defaults = array( +'enabled' => true, +'userModel' => 'User', +'userKey' => 'user_id', +'change' => 'list', +'description_ids' => TRUE, +'skip' => array(), +'ignore' => array(), +'classField' => 'model', +'foreignKey' => 'model_id'); + + public $schema = array(); + + /** + * Cake called intializer + * Config options are : + * userModel : 'User'. Class name of the user model you want to use (User by default), if you want to save User in log + * userKey : 'user_id'. The field for saving the user to (user_id by default). + * change : 'list' > [name, age]. Set to 'full' for [name (alek) => (Alek), age (28) => (29)] + * description_ids : TRUE. Set to FALSE to not include model id and user id in the title field + * skip : array(). String array of actions to not log + * + * @param Object $Model + * @param array $config + */ + function setup(&$Model, $config = array()) { + + if (!is_array($config)) { + $config = array(); + } + $this->settings[$Model->alias] = array_merge($this->defaults, $config); + $this->settings[$Model->alias]['ignore'][] = $Model->primaryKey; + + $this->Log = & ClassRegistry::init('Log'); + if ($this->settings[$Model->alias]['userModel'] != $Model->alias) { + $this->UserModel = & ClassRegistry::init($this->settings[$Model->alias]['userModel']); + } else { + $this->UserModel = $Model; + } + $this->schema = $this->Log->schema(); + App::import('Component', 'Auth'); + $this->user[$this->settings[$Model->alias]['userModel']] = AuthComponent::user(); + } + + function settings(&$Model) { + + return $this->settings[$Model->alias]; + } + + function enableLog(&$Model, $enable = null) { + + if ($enable !== null) { + $this->settings[$Model->alias]['enabled'] = $enable; + } + return $this->settings[$Model->alias]['enabled']; + } + + /** + * Useful for getting logs for a model, takes params to narrow find. + * This method can actually also be used to find logs for all models or + * even another model. Using no params will return all activities for + * the models it is called from. + * + * Possible params : + * 'model' : mixed (NULL) String with className, NULL to get current or FALSE to get everything + * 'action' : string (NULL) String with action (add/edit/delete), NULL gets all + * 'order' : string ('created DESC') String with custom order + * 'conditions : array (array()) Add custom conditions + * 'model_id' : int (NULL) Add a int + * + * (remember to use your own user key if you're not using 'user_id') + * 'user_id' : int (NULL) Defaults to all users, supply id if you want for only one User + * + * @param Object $Model + * @param array $params + * @return array + */ + function findLog(&$Model, $params = array()) { + + $defaults = array( + $this->settings[$Model->alias]['classField'] => NULL, +'action' => NULL, +'order' => 'created DESC', + $this->settings[$Model->alias]['userKey'] => NULL, +'conditions' => array(), + $this->settings[$Model->alias]['foreignKey'] => NULL, +'fields' => array(), +'limit' => 50); + $params = array_merge($defaults, $params); + $options = array( +'order' => $params['order'], +'conditions' => $params['conditions'], +'fields' => $params['fields'], +'limit' => $params['limit']); + if ($params[$this->settings[$Model->alias]['classField']] === NULL) { + $params[$this->settings[$Model->alias]['classField']] = $Model->alias; + } + if ($params[$this->settings[$Model->alias]['classField']]) { + if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { + $options['conditions'][$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; + } elseif (isset($this->schema['description'])) { + $options['conditions']['description LIKE '] = $params[$this->settings[$Model->alias]['classField']] . '%'; + } else { + return FALSE; + } + } + if ($params['action'] && isset($this->schema['action'])) { + $options['conditions']['action'] = $params['action']; + } + if ($params[$this->settings[$Model->alias]['userKey']] && $this->UserModel && is_numeric($params[$this->settings[$Model->alias]['userKey']])) { + $options['conditions'][$this->settings[$Model->alias]['userKey']] = $params[$this->settings[$Model->alias]['userKey']]; + } + if ($params[$this->settings[$Model->alias]['foreignKey']] && is_numeric($params[$this->settings[$Model->alias]['foreignKey']])) { + $options['conditions'][$this->settings[$Model->alias]['foreignKey']] = $params[$this->settings[$Model->alias]['foreignKey']]; + } + return $this->Log->find('all', $options); + } + + /** + * Get list of actions for one user. + * Params for getting (one line) activity descriptions + * and/or for just one model + * + * @example $this->Model->findUserActions(301,array('model' => 'BookTest')); + * @example $this->Model->findUserActions(301,array('events' => true)); + * @example $this->Model->findUserActions(301,array('fields' => array('id','model'),'model' => 'BookTest'); + * @param Object $Model + * @param int $user_id + * @param array $params + * @return array + */ + function findUserActions(&$Model, $user_id, $params = array()) { + + if (!$this->UserModel) { + return NULL; + } + // if logged in user is asking for her own log, use the data we allready have + if (isset($this->user) && isset($this->user[$this->UserModel->alias][$this->UserModel->primaryKey]) && $user_id == $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] && isset($this->user[$this->UserModel->alias][$this->UserModel->displayField])) { + $username = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; + } else { + $this->UserModel->recursive = -1; + $user = $this->UserModel->find(array( + $this->UserModel->primaryKey => $user_id)); + $username = $user[$this->UserModel->alias][$this->UserModel->displayField]; + } + $fields = array(); + if (isset($params['fields'])) { + if (is_array($params['fields'])) { + $fields = $params['fields']; + } else { + $fields = array( + $params['fields']); + } + } + $conditions = array( + $this->settings[$Model->alias]['userKey'] => $user_id); + if (isset($params[$this->settings[$Model->alias]['classField']])) { + $conditions[$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; + } + $data = $this->Log->find('all', array( +'conditions' => $conditions, +'recursive' => -1, +'fields' => $fields)); + if (!isset($params['events']) || ( isset($params['events']) && $params['events'] == false )) { + return $data; + } + $result = array(); + foreach ( $data as $key => $row ) { + $one = $row['Log']; + $result[$key]['Log']['id'] = $one['id']; + $result[$key]['Log']['event'] = $username; + // have all the detail models and change as list : + if (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one['change']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { + if ($one['action'] == 'edit') { + $result[$key]['Log']['event'] .= ' edited ' . $one['change'] . ' of ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + + // ' at '.$one['created']; + } elseif ($one['action'] == 'add') { + $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } elseif ($one['action'] == 'delete') { + $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } + + } elseif (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { // have model,model_id and action + if ($one['action'] == 'edit') { + $result[$key]['Log']['event'] .= ' edited ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + + // ' at '.$one['created']; + } elseif ($one['action'] == 'add') { + $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } elseif ($one['action'] == 'delete') { + $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } + } else { // only description field exist + $result[$key]['Log']['event'] = $one['description']; + } + + } + return $result; + } + + /** + * Use this to supply a model with the data of the logged in User. + * Intended to be called in AppController::beforeFilter like this : + * + * if ($this->{$this->modelClass}->Behaviors->attached('Logable')) { + * $this->{$this->modelClass}->setUserData($activeUser);/ + * } + * + * The $userData array is expected to look like the result of a + * User::find(array('id'=>123)); + * + * @param Object $Model + * @param array $userData + */ + function setUserData(&$Model, $userData = null) { + + if ($userData) { + $this->user = $userData; + } + } + + /** + * Used for logging custom actions that arent crud, like login or download. + * + * @example $this->Boat->customLog('ship', 66, array('title' => 'Titanic heads out')); + * @param Object $Model + * @param string $action name of action that is taking place (dont use the crud ones) + * @param int $id id of the logged item (ie model_id in logs table) + * @param array $values optional other values for your logs table + */ + function customLog(&$Model, $action, $id, $values = array()) { + + $logData['Log'] = $values; + /** @todo clean up $logData */ + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && is_numeric($id)) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; + } + $title = NULL; + if (isset($values['title'])) { + $title = $values['title']; + unset($logData['Log']['title']); + } + $logData['Log']['action'] = $action; + $this->_saveLog($Model, $logData, $title); + } + + function clearUserData(&$Model) { + + $this->user = NULL; + } + + function setUserIp(&$Model, $userIP = null) { + + $this->userIP = $userIP; + } + + function beforeDelete(&$Model) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { + return true; + } + $Model->recursive = -1; + $Model->read(); + return true; + } + + function afterDelete(&$Model) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { + return true; + } + $logData = array(); + if (isset($this->schema['description'])) { + $logData['Log']['description'] = $Model->alias; + if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { + $logData['Log']['description'] .= ' "' . $Model->data[$Model->alias][$Model->displayField] . '"'; + } + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= ' (' . $Model->id . ') '; + } + $logData['Log']['description'] .= __('deleted', TRUE); + } + $logData['Log']['action'] = 'delete'; + $this->_saveLog($Model, $logData); + } + + function beforeSave(&$Model) { + + if (isset($this->schema['change']) && $Model->id) { + $this->old = $Model->find('first', array( +'conditions' => array( + $Model->alias . '.' . $Model->primaryKey => $Model->id), +'recursive' => -1)); + } + return true; + } + + function afterSave(&$Model, $created) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['add']) && $this->settings[$Model->alias]['skip']['add'] && $created) { + return true; + } elseif (isset($this->settings[$Model->alias]['skip']['edit']) && $this->settings[$Model->alias]['skip']['edit'] && !$created) { + return true; + } + $keys = array_keys($Model->data[$Model->alias]); + $diff = array_diff($keys, $this->settings[$Model->alias]['ignore']); + if (sizeof($diff) == 0 && empty($Model->logableAction)) { + return false; + } + if ($Model->id) { + $id = $Model->id; + } elseif ($Model->insertId) { + $id = $Model->insertId; + } + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']])) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; + } + if (isset($this->schema['description'])) { + $logData['Log']['description'] = $Model->alias . ' '; + if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { + $logData['Log']['description'] .= '"' . $Model->data[$Model->alias][$Model->displayField] . '" '; + } + + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= '(' . $id . ') '; + } + + if ($created) { + $logData['Log']['description'] .= __('added', TRUE); + } else { + $logData['Log']['description'] .= __('updated', TRUE); + } + } + if (isset($this->schema['action'])) { + if ($created) { + $logData['Log']['action'] = 'add'; + } else { + $logData['Log']['action'] = 'edit'; + } + + } + if (isset($this->schema['change'])) { + $logData['Log']['change'] = ''; + $db_fields = array_keys($Model->schema()); + $changed_fields = array(); + foreach ( $Model->data[$Model->alias] as $key => $value ) { + if (isset($Model->data[$Model->alias][$Model->primaryKey]) && !empty($this->old) && isset($this->old[$Model->alias][$key])) { + $old = $this->old[$Model->alias][$key]; + } else { + $old = ''; + } + // TODO Audit, removed 'revision' as well + if ($key != 'revision' && $key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { + if ($this->settings[$Model->alias]['change'] == 'full') { + $changed_fields[] = $key . ' (' . $old . ') => (' . $value . ')'; + } else if ($this->settings[$Model->alias]['change'] == 'serialize') { + $changed_fields[$key] = array( +'old' => $old, +'value' => $value); + } else { + $changed_fields[] = $key; + } + } + } + $changes = sizeof($changed_fields); + if ($changes == 0) { + return true; + } + if ($this->settings[$Model->alias]['change'] == 'serialize') { + $logData['Log']['change'] = serialize($changed_fields); + } else { + $logData['Log']['change'] = implode(', ', $changed_fields); + } + $logData['Log']['changes'] = $changes; + } + $this->_saveLog($Model, $logData); + } + + /** + * Does the actual saving of the Log model. Also adds the special field if possible. + * + * If model field in table, add the Model->alias + * If action field is NOT in table, remove it from dataset + * If the userKey field in table, add it to dataset + * If userData is supplied to model, add it to the title + * + * @param Object $Model + * @param array $logData + */ + function _saveLog(&$Model, $logData, $title = null) { + + if ($title !== NULL) { + $logData['Log']['title'] = $title; + } elseif ($Model->displayField == $Model->primaryKey) { + $logData['Log']['title'] = $Model->alias . ' (' . $Model->id . ')'; + } elseif (isset($Model->data[$Model->alias][$Model->displayField])) { + $logData['Log']['title'] = $Model->data[$Model->alias][$Model->displayField]; + } else { + $logData['Log']['title'] = $Model->field($Model->displayField); + } + + if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { + // by miha nahtigal + $logData['Log'][$this->settings[$Model->alias]['classField']] = $Model->name; + } + + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && !isset($logData['Log'][$this->settings[$Model->alias]['foreignKey']])) { + if ($Model->id) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->id; + } elseif ($Model->insertId) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->insertId; + } + } + + if (!isset($this->schema['action'])) { + unset($logData['Log']['action']); + } elseif (isset($Model->logableAction) && !empty($Model->logableAction)) { + $logData['Log']['action'] = implode(',', $Model->logableAction); // . ' ' . $logData['Log']['action']; + unset($Model->logableAction); + } + + if (isset($this->schema['version_id']) && isset($Model->version_id)) { + $logData['Log']['version_id'] = $Model->version_id; + unset($Model->version_id); + } + + if (isset($this->schema['ip']) && $this->userIP) { + $logData['Log']['ip'] = $this->userIP; + } + + if (isset($this->schema[$this->settings[$Model->alias]['userKey']]) && $this->user) { + $logData['Log'][$this->settings[$Model->alias]['userKey']] = $this->user[$this->UserModel->alias][$this->UserModel->primaryKey]; + } + + if (isset($this->schema['description'])) { + if ($this->user && $this->UserModel) { + $logData['Log']['description'] .= ' by ' . $this->settings[$Model->alias]['userModel'] . ' "' . $this->user[$this->UserModel->alias][$this->UserModel->displayField] . '"'; + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= ' (' . $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] . ')'; + } + + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['description'] .= ' by System'; + } + $logData['Log']['description'] .= '.'; + } + if (isset($this->schema['email'])) { // TODO Audit, LogableBehevior email + if ($this->user && $this->UserModel) { + $logData['Log']['email'] = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['email'] = 'SYS'; + } + } + if (isset($this->schema['org'])) { // TODO Audit, LogableBehevior org CHECK!!! + if ($this->user && $this->UserModel) { + $logData['Log']['org'] = $this->user[$this->UserModel->alias][$this->UserModel->orgField]; + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['org'] = 'SYS'; + } + } + if (isset($this->schema['title'])) { // TODO LogableBehevior title + if ($this->user && $this->UserModel) { // $Model->data[$Model->alias][$Model->displayField] + switch ($Model->alias) { + case "User": // TODO Audit, not used here but done in UsersController + $title = 'User ('. $Model->data[$Model->alias]['id'].') '. $Model->data[$Model->alias]['email']; + break; + case "Event": + $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '. $Model->data[$Model->alias]['info']; + $logData['Log']['title'] = $title; + break; + case "Attribute": + if (isset($Model->combinedKeys)) { + if (is_array($Model->combinedKeys)) { + $title = 'Attribute ('. $Model->data[$Model->alias]['id'].') '.'from Event ('. $Model->data[$Model->alias]['event_id'].'): '. $Model->data[$Model->alias][$Model->combinedKeys[1]].'/'. $Model->data[$Model->alias][$Model->combinedKeys[2]].' '. $Model->data[$Model->alias]['value1']; + $logData['Log']['title'] = $title; + } + } + break; + case "Server": + $title = 'Server ('. $Model->data[$Model->alias]['id'].'): '. $Model->data[$Model->alias]['url']; + $logData['Log']['title'] = $title; + break; + default: + if (isset($Model->combinedKeys)) { + if (is_array($Model->combinedKeys)) { + $title = ''; + foreach ($Model->combinedKeys as $combinedKey) { + $title .= '/'. $Model->data[$Model->alias][$combinedKey]; + } + $title = substr($title ,1); + $logData['Log']['title'] = $title; + } + } + } + } + } + $this->Log->create($logData); + $this->Log->save(null, array( +'validate' => false, +'callbacks' => false)); + + // write to syslogd as well + $syslog = new SysLog(); + $syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']); + } +} \ No newline at end of file From e2df7442a4e41266fa236b925e08cfda255a644b Mon Sep 17 00:00:00 2001 From: Andrzej Dereszowski Date: Mon, 2 Jul 2012 16:26:50 +0200 Subject: [PATCH 05/30] shit --- .gitignore | 2 ++ app/Config/bootstrap.php | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c65c367a2..8130153d1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ /app/tmp/logs/*.log /app/files/* /app/webroot/img/logo.png +/app/Config/bootstrap.php +/app/Config/database.php diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index 9a30d725c..f9dc2d99e 100755 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -142,5 +142,6 @@ Configure::write('SecureAuth.expire', 300); // the time-window for th * CakePlugin::load('DebugKit'); //Loads a single plugin named DebugKit * */ + CakePlugin::load('SysLog'); -CakePlugin::load('AclExtras'); \ No newline at end of file +CakePlugin::load('AclExtras'); From cf40a908d4a1f5d5658341de38da397422264ced Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 22 Aug 2012 14:05:39 +0200 Subject: [PATCH 06/30] SysLog.SysLog lib import. --- app/Controller/UsersController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 320b712a2..e78b170cf 100755 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -447,6 +447,7 @@ class UsersController extends AppController { $dbh = null; // write to syslogd as well + App::import('Lib', 'SysLog.SysLog'); $syslog = new SysLog(); if ($fields_result) $syslog->write('notice', $description.' -- '.$action.' -- '.$fields_result); else $syslog->write('notice', $description.' -- '.$action); From 8c1cfa731a03de991f43d0a81d671327ddeb884a Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 22 Aug 2012 14:39:41 +0200 Subject: [PATCH 07/30] loggable behaviour. some merge correction for events and servers, so we log again. --- app/Controller/EventsController.php | 7 +++++++ app/Controller/ServersController.php | 7 +++++++ app/Model/Behavior/LogableBehavior.php | 13 ++++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) mode change 100755 => 100644 app/Controller/ServersController.php diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 8a2effae7..157708d4d 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -838,6 +838,13 @@ class EventsController extends AppController { // $gv->image(); // } + public function getName($id = null) { + $events = $this->Event->find('first', array( + 'conditions' => array('Event.id' => $id) + )); + $name = $events['Event']['info']; + return $name; + } } diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php old mode 100755 new mode 100644 index 8beea2923..bdb841093 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -286,5 +286,12 @@ class ServersController extends AppController { $this->set('fails', $fails); } + public function getName($id = null) { + $servers = $this->Server->find('first', array( + 'conditions' => array('Server.id' => $id) + )); + $name = $servers['Server']['url']; + return $name; + } } diff --git a/app/Model/Behavior/LogableBehavior.php b/app/Model/Behavior/LogableBehavior.php index 747f03530..38a4a60e7 100755 --- a/app/Model/Behavior/LogableBehavior.php +++ b/app/Model/Behavior/LogableBehavior.php @@ -66,7 +66,7 @@ App::import('Lib', 'SysLog.SysLog'); // Audit, syslogd, extra */ class LogableBehavior extends ModelBehavior { - + public $user = NULL; public $UserModel = FALSE; @@ -553,7 +553,12 @@ class LogableBehavior extends ModelBehavior { $title = 'User ('. $Model->data[$Model->alias]['id'].') '. $Model->data[$Model->alias]['email']; break; case "Event": - $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '. $Model->data[$Model->alias]['info']; + $this->Events = new EventsController(); + $this->Events->constructClasses(); + debug($Model->data[$Model->alias]['id']); + debug($this->Events->getName($Model->data[$Model->alias]['id'])); + debug(false); + $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '.$this->Events->getName($Model->data[$Model->alias]['id']); $logData['Log']['title'] = $title; break; case "Attribute": @@ -565,7 +570,9 @@ class LogableBehavior extends ModelBehavior { } break; case "Server": - $title = 'Server ('. $Model->data[$Model->alias]['id'].'): '. $Model->data[$Model->alias]['url']; + $this->Servers = new ServersController(); + $this->Servers->constructClasses(); + $title = 'Server ('. $Model->data[$Model->alias]['id'].'): '. $this->Servers->getName($Model->data[$Model->alias]['id']); $logData['Log']['title'] = $title; break; default: From 7e23e3bc77ad64ff9b7249b4b9ff871de1c16e42 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 22 Aug 2012 15:19:28 +0200 Subject: [PATCH 08/30] Event.user_id rollback(-part). --- app/Controller/EventsController.php | 3 ++- app/Model/Event.php | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) mode change 100755 => 100644 app/Model/Event.php diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 157708d4d..9912f4e19 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -177,6 +177,7 @@ class EventsController extends AppController { */ public function _add(&$data, &$auth, $fromXml) { // force check userid and orgname to be from yourself + if (!$fromXml) $data['Event']['user_id'] = $auth->user('id'); $data['Event']['org'] = $auth->user('org'); unset ($data['Event']['id']); $this->Event->create(); @@ -200,7 +201,7 @@ class EventsController extends AppController { } $fieldList = array( - 'Event' => array('org', 'date', 'risk', 'info', 'published', 'uuid', 'private'), + 'Event' => array('org', 'date', 'risk', 'info', 'user_id', 'published', 'uuid', 'private'), 'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'revision', 'private') ); // this saveAssociated() function will save not only the event, but also the attributes diff --git a/app/Model/Event.php b/app/Model/Event.php old mode 100755 new mode 100644 index 476178da1..906aaa41c --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -77,6 +77,16 @@ class Event extends AppModel { //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), + 'user_id' => array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), 'published' => array( 'boolean' => array( 'rule' => array('boolean'), From 19bb9b0a81ce4d2a159b706b577712d4328b79a9 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 22 Aug 2012 15:57:22 +0200 Subject: [PATCH 09/30] LogableBehavior. removed some debug() and fixed writing to syslog when deleting event with attributes. --- app/Model/Behavior/LogableBehavior.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Model/Behavior/LogableBehavior.php b/app/Model/Behavior/LogableBehavior.php index 38a4a60e7..30012ea83 100755 --- a/app/Model/Behavior/LogableBehavior.php +++ b/app/Model/Behavior/LogableBehavior.php @@ -555,9 +555,6 @@ class LogableBehavior extends ModelBehavior { case "Event": $this->Events = new EventsController(); $this->Events->constructClasses(); - debug($Model->data[$Model->alias]['id']); - debug($this->Events->getName($Model->data[$Model->alias]['id'])); - debug(false); $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '.$this->Events->getName($Model->data[$Model->alias]['id']); $logData['Log']['title'] = $title; break; @@ -596,6 +593,10 @@ class LogableBehavior extends ModelBehavior { // write to syslogd as well $syslog = new SysLog(); - $syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']); + if (isset($logData['Log']['change'])) { + $syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']); + } else { + $syslog->write('notice', $logData['Log']['description']); + } } } \ No newline at end of file From 7d98c5f31e3002ad869806865d9640959651c47c Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 22 Aug 2012 16:04:55 +0200 Subject: [PATCH 10/30] GFI Sandbox upload. If add event, give a GFI Sandbox export file upload field option. Unzip, read .xml, add attachment malware, created files and ip-dst. --- app/Controller/EventsController.php | 195 +++++++++++++++++++++++++--- app/Model/Attribute.php | 60 +++++++++ app/Model/Event.php | 29 ++++- app/View/Events/add.ctp | 7 +- 4 files changed, 274 insertions(+), 17 deletions(-) mode change 100755 => 100644 app/Model/Attribute.php diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 9912f4e19..715245a08 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -147,21 +147,35 @@ class EventsController extends AppController { */ public function add() { if ($this->request->is('post')) { - if ($this->_add($this->request->data, $this->Auth, $this->_isRest())) { - if ($this->_isRest()) { - // REST users want to see the newly created event - $this->view($this->Event->getId()); - $this->render('view'); - } else { - // redirect to the view of the newly created event - $this->Session->setFlash(__('The event has been saved')); - $this->redirect(array('action' => 'view', $this->Event->getId())); - } - } else { - $this->Session->setFlash(__('The event could not be saved. Please, try again.'), 'default', array(), 'error'); - // TODO return error if REST - } - } + if (!empty($this->data)) { + App::uses('File', 'Utility'); + $file = new File($this->data['Event']['submittedfile']['name']); + $ext = $file->ext(); + if ($ext != 'zip' && $this->data['Event']['submittedfile']['size'] > 0 && + is_uploaded_file($this->data['Event']['submittedfile']['tmp_name'])) { + //return false; + $this->Session->setFlash('You may only upload GFI Sandbox zip files.'); + } else { + if ($this->_add($this->request->data, $this->Auth, $this->_isRest())) { + if ($this->_isRest()) { + // REST users want to see the newly created event + $this->view($this->Event->getId()); + $this->render('view'); + } else { + // TODO now save uploaded attributes using $this->Event->getId() .. + $this->addGfiZip($this->Event->getId()); + + // redirect to the view of the newly created event + $this->Session->setFlash(__('The event has been saved')); + $this->redirect(array('action' => 'view', $this->Event->getId())); + } + } else { + $this->Session->setFlash(__('The event could not be saved. Please, try again.'), 'default', array(), 'error'); + // TODO return error if REST + } + } + } + } // combobox for risks $risks = $this->Event->validate['risk']['rule'][1]; $risks = $this->_arrayToValuesIndexArray($risks); @@ -847,5 +861,156 @@ class EventsController extends AppController { return $name; } + function addGfiZip($id) { + if (!empty($this->data) && $this->data['Event']['submittedfile']['size'] > 0 && + is_uploaded_file($this->data['Event']['submittedfile']['tmp_name'])) { + $zipData = fread(fopen($this->data['Event']['submittedfile']['tmp_name'], "r"), + $this->data['Event']['submittedfile']['size']); + + // write + $root_dir = APP."files".DS.$id.DS; + App::uses('Folder', 'Utility'); + $dir = new Folder($root_dir, true); + $destpath = $root_dir; + $file = new File ($destpath); + $zipfile = new File ($destpath.DS.$this->data['Event']['submittedfile']['name']); + $result = $zipfile->write($zipData); + if (!$result) $this->Session->setFlash(__('Problem with writing the zip file. Please report to administrator.')); + + // extract zip.. + $exec_retval = ''; + exec("unzip ".$zipfile->path.' -d "'.addslashes($root_dir).'"', $exec_output, $exec_retval); + $exec_output = array(); + if($exec_retval != 0) { // not EXIT_SUCCESS + // do some? + } + + // now open the xml.. + $xml = $root_dir.DS.'Analysis'.DS.'analysis.xml'; + $fileData = fread(fopen($xml, "r"),$this->data['Event']['submittedfile']['size']); + + // read XML + $this->readGfiXML($fileData,$id); + } + } + + function readGfiXML($data,$id) { + $this->loadModel('Attribute'); + + // import XML class + App::uses('Xml', 'Utility'); + // now parse it + $parsed_xml =& Xml::build($data, array('return' => 'simplexml')); + + // xpath.. + + //Payload delivery -- malware-sample + $results = $parsed_xml->xpath('/analysis'); + foreach ($results as $result) { + foreach ( $result[0]->attributes() as $key => $val ) { + if ((string)$key == 'filename') $realFileName = (string)$val; + } + } + $root_dir = APP."files".DS.$id.DS; + $malware = $root_dir.DS.'sample'; + $this->Event->Attribute->uploadAttachment($malware,$realFileName,true,$id); + + //Artifacts dropped -- filename|md5 + $files = array(); + // TODO what about stored_modified_file ?? + $results = $parsed_xml->xpath('/analysis/processes/process/stored_files/stored_created_file'); + foreach ($results as $result) { + $arrayItemKey = ''; + $arrayItemValue = ''; + foreach ( $result[0]->attributes() as $key => $val ) { + if ($key == 'filename') $arrayItemKey = (string)$val; + if ($key == 'md5') $arrayItemValue = (string)$val; + } + $files[str_replace('C:\Users\John','%UserProfile%',$arrayItemKey)] = $arrayItemValue; + } + //$files = array_unique($files); + + // write content.. + foreach ($files as $key => $val) { + // add attribute.. + $this->Attribute->read(null, 1); + $this->Attribute->save(array( + 'event_id' => $id, + 'category' => 'Artifacts dropped', + 'type' => 'filename|md5', + 'value' => $key.'|'.$val, + 'to_ids' => false)); + + // the actual files.. + // seek $val in dirs and add.. + $ext = substr($key,strrpos ( $key , '.')); + $actualFileName = $val.$ext; + $realFileName = end(explode('\\',$key)); + // have the filename, now look at parents parent for the process number + $express = "/analysis/processes/process/stored_files/stored_created_file[@md5='".$val."']/../.."; + $results = $parsed_xml->xpath($express); + foreach ($results as $result) { + foreach ( $result[0]->attributes() as $key => $val ) { + if ((string)$key == 'index') $index = (string)$val; + } + } + $actualFile = $root_dir.DS.'Analysis'.DS.'proc_'.$index.DS.'modified_files'.DS.$actualFileName; + $this->Event->Attribute->uploadAttachment($actualFile,$realFileName,false,$id); + } + + //Network activity -- ip-dst + $ips = array(); + $results = $parsed_xml->xpath('/analysis/processes/process/networkpacket_section/connect_to_computer'); + foreach ($results as $result) { + foreach ( $result[0]->attributes() as $key => $val ) { + if ($key == 'remote_ip') $ips[] = (string)$val; + } + } + // write content.. + foreach ($ips as $ip) { + // add attribute.. + $this->Attribute->read(null, 1); + $this->Attribute->save(array( + 'event_id' => $id, + 'category' => 'Network activity', + 'type' => 'ip-dst', + 'value' => $ip, + 'to_ids' => false)); + } + + // Persistence mechanism -- regkey|value + $regs = array(); + $results = $parsed_xml->xpath('/analysis/processes/process/registry_section/set_value'); + foreach ($results as $result) { + $arrayItemKey = ''; + $arrayItemValue = ''; + foreach ( $result[0]->attributes() as $key => $val ) { + if ($key == 'key_name') $arrayItemKey = (string)$val; + if ($key == 'data') $arrayItemValue = (string)$val; + } + $arrayItemKey = preg_replace('@\\\REGISTRY\\\USER\\\S(-[0-9]{1}){2}-[0-9]{2}(-[0-9]{10}){2}-[0-9]{9}-[0-9]{4}@','HKEY_CURRENT_USER',$arrayItemKey); + $regs[$arrayItemKey] = str_replace('(UNICODE_0x00000000)','',$arrayItemValue); + } + //$regs = array_unique($regs); + + // write content.. + foreach ($regs as $key => $val) { + // add attribute.. + $this->Attribute->read(null, 1); + if ($val == '[binary_data]') { + $itsType = 'regkey'; + $itsValue = $key; + } else { + $itsType = 'regkey|value'; + $itsValue = $key.'|'.$val; + } + $this->Attribute->save(array( + 'event_id' => $id, + 'category' => 'Persistence mechanism', + 'type' => $itsType, + 'value' => $itsValue, + 'to_ids' => false)); + } + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php old mode 100755 new mode 100644 index 29c4dfb47..35cf6eb77 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -622,6 +622,66 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' } } +/** + * add_attachment method + * + * @return void + */ + public function uploadAttachment($fileP,$realFileName,$malware,$event_id = null) { + // Check if there were problems with the file upload + // only keep the last part of the filename, this should prevent directory attacks + $filename = basename($fileP); + $tmpfile = new File($fileP); + + // save the file-info in the database + $this->create(); + $this->data['Attribute']['event_id'] = $event_id; + if($malware) { + $this->data['Attribute']['category'] = "Payload delivery"; + $this->data['Attribute']['type'] = "malware-sample"; + $this->data['Attribute']['value'] = $realFileName.'|'.$tmpfile->md5(); // TODO gives problems with bigger files + $this->data['Attribute']['to_ids'] = 1; // LATER let user choose to send this to IDS + } + else { + $this->data['Attribute']['category'] = "Artifacts dropped"; + $this->data['Attribute']['type'] = "attachment"; + $this->data['Attribute']['value'] = $realFileName; + $this->data['Attribute']['to_ids'] = 0; + } + + if ($this->save($this->data)) { + // attribute saved correctly in the db + } else { + // do some? + } + + // no errors in file upload, entry already in db, now move the file where needed and zip it if required. + // no sanitization is required on the filename, path or type as we save + // create directory structure + $root_dir = APP.DS."files".DS.$event_id; + $dir = new Folder($root_dir, true); + // move the file to the correct location + $destpath = $root_dir.DS.$this->getId(); // id of the new attribute in the database + $file = new File ($destpath); + $zipfile = new File ($destpath.'.zip'); + $file_in_zip = new File($root_dir.DS.$filename); // FIXME do sanitization of the filename + + // zip and password protect the malware files + if($malware) { + // TODO check if CakePHP has no easy/safe wrapper to execute commands + $exec_retval = ''; $exec_output = array(); + exec("zip -j -P infected ".$zipfile->path.' "'.addslashes($file_in_zip->path).'"', $exec_output, $exec_retval); + if($exec_retval != 0) { // not EXIT_SUCCESS + // do some? + }; + $file_in_zip->delete(); // delete the original not-zipped-file + rename($zipfile->path, $file->path); // rename the .zip to .nothing + } else { + $file_attach = new File($fileP); + rename($file_attach->path, $file->path); + } + } + } diff --git a/app/Model/Event.php b/app/Model/Event.php index 906aaa41c..3e7d7e250 100644 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -30,7 +30,8 @@ class Event extends AppModel { public $field_descriptions = array( 'risk' => array('desc' => 'Risk levels: *low* means mass-malware, *medium* means APT malware, *high* means sophisticated APT malware or 0-day attack', 'formdesc' => 'Risk levels:
    low: mass-malware
    medium: APT malware
    high: sophisticated APT malware or 0-day attack'), 'private' => array('desc' => 'This field tells if the event should be shared with other CyDefSIG servers'), - 'classification' => array('desc' => 'Set the Traffic Light Protocol classification.

    1. TLP:AMBER- Share only within the organization on a need-to-know basis
    2. TLP:GREEN:NeedToKnow- Share within your constituency on the need-to-know basis.
    3. TLP:GREEN- Share within your constituency.
    ') + 'classification' => array('desc' => 'Set the Traffic Light Protocol classification.
    1. TLP:AMBER- Share only within the organization on a need-to-know basis
    2. TLP:GREEN:NeedToKnow- Share within your constituency on the need-to-know basis.
    3. TLP:GREEN- Share within your constituency.
    '), + 'submittedfile' => array('desc' => 'GFI sandbox: export upload', 'formdesc' => 'GFI sandbox:
    export upload'), ); /** @@ -172,6 +173,32 @@ class Event extends AppModel { ) ); + function beforeDelete() { + // delete event from the disk + $this->read(); // first read the event from the db + // FIXME secure this filesystem access/delete by not allowing to change directories or go outside of the directory container. + // only delete the file if it exists + $filepath = APP."files".DS.$this->data['Event']['id']; + App::uses('Folder', 'Utility'); + $file = new Folder ($filepath); + if(is_dir($filepath)) { + if(!$this->destroyDir($filepath)) { + throw new InternalErrorException('Delete of event file directory failed. Please report to administrator.'); + } + } + } + + function destroyDir($dir) { + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $file) { + if ($file == '.' || $file == '..') continue; + if (!$this->destroyDir($dir.DS.$file)) { + chmod($dir.DS.$file, 0777); + if (!$this->destroyDir($dir.DS.$file)) return false; + }; + } + return rmdir($dir); + } function beforeValidate() { // generate UUID if it doesn't exist diff --git a/app/View/Events/add.ctp b/app/View/Events/add.ctp index 6a67cae10..d0d583991 100755 --- a/app/View/Events/add.ctp +++ b/app/View/Events/add.ctp @@ -1,5 +1,5 @@
    -Form->create('Event');?> +Form->create('Event', array('type' => 'file'));?>
    Form->input('risk', array( 'before' => $this->Html->div('forminfo', isset($event_descriptions['risk']['formdesc']) ? $event_descriptions['risk']['formdesc'] : $event_descriptions['risk']['desc']))); echo $this->Form->input('info'); + echo $this->Form->input('Event.submittedfile', array( + 'label' => 'GFI sandbox', + 'between' => '
    ', + 'type' => 'file', + 'before' => $this->Html->div('forminfo', isset($event_descriptions['submittedfile']['formdesc']) ? $event_descriptions['submittedfile']['formdesc'] : $event_descriptions['submittedfile']['desc']))); ?>
    From 2459bca386759fb308cf6453f4a7b3563e6d6255 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 23 Aug 2012 10:25:14 +0200 Subject: [PATCH 11/30] (Audit) logs. The writing of the log in User was done by me using calls to the PHP db driver (during my second or third day). Very wrong given that is driver and db dependant. Now use CakePHPs calls to have abstraction. --- app/Controller/UsersController.php | 50 +++++++++++------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index e78b170cf..94441ebc1 100755 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -231,7 +231,7 @@ class UsersController extends AppController { $c++; } $fields_result_str = substr($fields_result_str, 2); - $this->extraLog("admin_modify", "user", $fields_result_str); // TODO Audit, check: modify User + $this->extraLog("edit", "user", $fields_result_str); // TODO Audit, check: modify User // TODO Audit, extraLog, fields compare END $this->Session->setFlash(__('The user has been saved')); $this->_refreshAuth(); // in case we modify ourselves @@ -410,42 +410,28 @@ class UsersController extends AppController { } public function extraLog($action = null, $description = null, $fields_result = null) { // TODO move audit to AuditsController? - // configuration - ClassRegistry::init('ConnectionManager'); - $dbh = ConnectionManager::getDataSource('default'); - $dbhost = $dbh->config['host']; - $dbport = $dbh->config['port']; - $dbname = $dbh->config['database']; - $dbuser = $dbh->config['login']; - $dbpass = $dbh->config['password']; - $dbprefix = $dbh->config['prefix']; // TODO Audit, extra, db prefix delimiter? - - // database connection - $conn = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname",$dbuser,$dbpass); - // new data $user_id = $this->Auth->user('id'); $model = 'User'; $model_id = $this->Auth->user('id'); - $org = $this->Auth->user('org'); - $email = $this->Auth->user('email'); - $action_date = new DateTime(); - $action_date_str = $action_date->format('Y-m-d H:i:sP'); - $description = "User (". $this->Auth->user('id')."): " .$this->Auth->user('email'); - - // query - $sql = "INSERT INTO ".$dbprefix."logs (org,email,created,action,title,`change`) VALUES (:org,:email,:created,:action,:title,:change)"; - $q = $conn->prepare($sql); - $q->execute(array(':org'=>$org, - ':email'=>$email, - ':created'=>$action_date_str, - ':action'=>$action, - ':title'=>$description, - ':change'=>$fields_result)); - - // database connection disconnect - $dbh = null; + if ($action == 'login') { + $description = "User (". $this->Auth->user('id')."): " .$this->data['User']['email']; + } elseif ($action == 'logout') { + $description = "User (". $this->Auth->user('id')."): " .$this->Auth->user('email'); + } else { // edit + $description = "User (". $this->User->id."): " .$this->data['User']['email']; + } + // query + $this->Log = ClassRegistry::init('Log'); + $this->Log->create(); + $this->Log->save(array( + 'org' => $this->Auth->user('org'), + 'email' => $this->Auth->user('email'), + 'action' => $action, + 'title' => $description, + 'change' => $fields_result)); + // write to syslogd as well App::import('Lib', 'SysLog.SysLog'); $syslog = new SysLog(); From a4c29a812fdf1c578b6e9e3e9eec43fa216753ed Mon Sep 17 00:00:00 2001 From: noud Date: Tue, 18 Sep 2012 16:50:07 +0200 Subject: [PATCH 12/30] XML related. Made tools/curl/input/event.xml more anonymous. Events/xml/view.ctp wrongly showed category_order. REST Event add did not work anymore given GFI sandbox import. --- app/Controller/EventsController.php | 46 ++++++++++++++++------------- app/View/Events/xml/view.ctp | 3 +- tools/curl/input/event.xml | 17 +++++------ 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 05f3196d6..d2913ec8f 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -43,12 +43,12 @@ class EventsController extends AppController { $this->Auth->allow('text'); $this->Auth->allow('dot'); - + // TODO Audit, activate logable in a Controller if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { $this->{$this->modelClass}->setUserData($this->activeUser); } - + // TODO ACL, if on ent/attr level, $this->set('isAcl', $this->checkAccess()); // convert uuid to id if present in the url, and overwrite id field @@ -232,10 +232,14 @@ class EventsController extends AppController { public function add() { if ($this->request->is('post')) { if (!empty($this->data)) { - App::uses('File', 'Utility'); - $file = new File($this->data['Event']['submittedfile']['name']); - $ext = $file->ext(); - if ($ext != 'zip' && $this->data['Event']['submittedfile']['size'] > 0 && + if (isset($this->data['Event']['submittedfile'])) { + App::uses('File', 'Utility'); + $file = new File($this->data['Event']['submittedfile']['name']); + $ext = $file->ext(); + } else { + $ext = ''; + } + if (isset($this->data['Event']['submittedfile']) && $ext != 'zip' && $this->data['Event']['submittedfile']['size'] > 0 && is_uploaded_file($this->data['Event']['submittedfile']['tmp_name'])) { //return false; $this->Session->setFlash('You may only upload GFI Sandbox zip files.'); @@ -248,7 +252,7 @@ class EventsController extends AppController { } else { // TODO now save uploaded attributes using $this->Event->getId() .. $this->addGfiZip($this->Event->getId()); - + // redirect to the view of the newly created event $this->Session->setFlash(__('The event has been saved')); $this->redirect(array('action' => 'view', $this->Event->getId())); @@ -429,13 +433,13 @@ class EventsController extends AppController { if (!$this->Event->exists()) { throw new NotFoundException(__('Invalid event')); } - + if ('true' == Configure::read('CyDefSIG.sync')) { // find the uuid $result = $this->Event->findById($id); $uuid = $result['Event']['uuid']; } - + if ($this->Event->delete()) { // delete the event from remote servers @@ -619,9 +623,9 @@ class EventsController extends AppController { $gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'))); // , 'debug' => true $gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); $body_signed = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR); - + $this->loadModel('User'); - + // // Build a list of the recipients that get a non-encrypted mail // But only do this if it is allowed in the bootstrap.php file. @@ -650,7 +654,7 @@ class EventsController extends AppController { // to reset the email fields using the reset method of the Email component. $this->Email->reset(); } - + // // Build a list of the recipients that wish to receive encrypted mails. // @@ -668,7 +672,7 @@ class EventsController extends AppController { $this->Email->subject = "[".Configure::read('CyDefSIG.name')."] Event ".$id." - ".$event['Event']['risk']." - TLP Amber"; $this->Email->template = 'body'; $this->Email->sendAs = 'text'; // both text or html - + // import the key of the user into the keyring // this is not really necessary, but it enables us to find // the correct key-id even if it is not the same as the emailaddress @@ -677,9 +681,9 @@ class EventsController extends AppController { try { $gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'))); $gpg->addEncryptKey($key_import_output['fingerprint']); // use the key that was given in the import - + $body_enc_sig = $gpg->encrypt($body_signed, true); - + $this->set('body', $body_enc_sig); $this->Email->send(); } catch (Exception $e){ @@ -696,7 +700,7 @@ class EventsController extends AppController { $this->log($e->getMessage()); return $e->getMessage(); } - + // LATER check if sending email succeeded and return appropriate result return true; @@ -1162,7 +1166,7 @@ class EventsController extends AppController { // now open the xml.. $xml = $root_dir.DS.'Analysis'.DS.'analysis.xml'; $fileData = fread(fopen($xml, "r"),$this->data['Event']['submittedfile']['size']); - + // read XML $this->readGfiXML($fileData,$id); } @@ -1170,14 +1174,14 @@ class EventsController extends AppController { function readGfiXML($data,$id) { $this->loadModel('Attribute'); - + // import XML class App::uses('Xml', 'Utility'); // now parse it $parsed_xml =& Xml::build($data, array('return' => 'simplexml')); // xpath.. - + //Payload delivery -- malware-sample $results = $parsed_xml->xpath('/analysis'); foreach ($results as $result) { @@ -1188,7 +1192,7 @@ class EventsController extends AppController { $root_dir = APP."files".DS.$id.DS; $malware = $root_dir.DS.'sample'; $this->Event->Attribute->uploadAttachment($malware,$realFileName,true,$id); - + //Artifacts dropped -- filename|md5 $files = array(); // TODO what about stored_modified_file ?? @@ -1214,7 +1218,7 @@ class EventsController extends AppController { 'type' => 'filename|md5', 'value' => $key.'|'.$val, 'to_ids' => false)); - + // the actual files.. // seek $val in dirs and add.. $ext = substr($key,strrpos ( $key , '.')); diff --git a/app/View/Events/xml/view.ctp b/app/View/Events/xml/view.ctp index 8ba927617..9820722ba 100755 --- a/app/View/Events/xml/view.ctp +++ b/app/View/Events/xml/view.ctp @@ -8,7 +8,8 @@ unset($event['Attribute']); // remove value1 and value2 from the output foreach($event['Event']['Attribute'] as $key => $value) { unset($event['Event']['Attribute'][$key]['value1']); - unset($event['Event']['Attribute'][$key]['value2']); + unset($event['Event']['Attribute'][$key]['value2']); + unset($event['Event']['Attribute'][$key]['category_order']); } // hide the private fields is we are not in sync mode diff --git a/tools/curl/input/event.xml b/tools/curl/input/event.xml index ab5e7dd75..48523c8ad 100644 --- a/tools/curl/input/event.xml +++ b/tools/curl/input/event.xml @@ -1,11 +1,11 @@ 14 - NCIRC + ORG 2012-04-12 Medium - TT6666: malixioious XLS (EDIT 234..5) - 3 + info + 1 0 4f8c2c4e-00dc-42c9-83ad-76e9ff32448e 0 @@ -31,8 +31,7 @@ 4f8c2d08-7e6c-4648-8730-50a7ff32448e 1 0 - Summary_report_Vienna_2012 27 March - ok_z.doc|b34a8fcf8e5c81de3f6f177bb6171929 + A.doc|3f6f1aaab6171925c81de9b34a8fcf8e c @@ -45,22 +44,22 @@ 4f8c2c69-9bf8-4279-8d03-2138ff32448e 1 0 - CVE-2010-3333 + CVE-XXXX-XXXX c 11 - 2012-04-03 + 2011-01-03 4f8812ff-ded0-4592-9227-0615ff32448e 9 - 2012-04-02 + 2011-02-02 4f85981e-d044-4b16-bc16-0a35ff32448e 6 - 2012-03-22 + 2011-03-01 4f7a9faa-91d4-4c91-8ec6-0878ff32448e From ef9b71120ba881dd47b68b012d6f663c11b00a40 Mon Sep 17 00:00:00 2001 From: noud Date: Tue, 18 Sep 2012 17:07:33 +0200 Subject: [PATCH 13/30] RBAC Terms page missed button deactivation. --- app/View/Users/terms.ctp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/View/Users/terms.ctp b/app/View/Users/terms.ctp index 66cfb7f08..1df8b7e9c 100644 --- a/app/View/Users/terms.ctp +++ b/app/View/Users/terms.ctp @@ -50,3 +50,8 @@ if (!$termsaccepted) { element('actions_menu'); ?>
    + From 03303de8b9a1151e5f0a52bddc795127a77db5a3 Mon Sep 17 00:00:00 2001 From: noud Date: Tue, 18 Sep 2012 17:32:34 +0200 Subject: [PATCH 14/30] RBAC Forgot to call saveAcl in Groups::add(). (to correct wrong behavior, edit group, do not change any and button submit.) --- app/Controller/GroupsController.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Controller/GroupsController.php b/app/Controller/GroupsController.php index 5fa3e0edf..457daaff1 100755 --- a/app/Controller/GroupsController.php +++ b/app/Controller/GroupsController.php @@ -16,7 +16,7 @@ class GroupsController extends AppController { ), 'Session' ); - + //public $components = array('Security'); public $paginate = array( 'limit' => 60, @@ -28,7 +28,7 @@ class GroupsController extends AppController { function beforeFilter() { parent::beforeFilter(); } - + /** * view method * @@ -42,7 +42,7 @@ class GroupsController extends AppController { } $this->set('group', $this->Group->read(null, $id)); } - + /** * admin_index method * @@ -76,6 +76,7 @@ class GroupsController extends AppController { if ($this->request->is('post')) { $this->Group->create(); if ($this->Group->save($this->request->data)) { + $this->saveAcl($this->Group, $this->data['Group']['perm_add'], $this->data['Group']['perm_modify'], $this->data['Group']['perm_publish']); // save to ACL as well $this->Session->setFlash(__('The group has been saved')); $this->redirect(array('action' => 'index')); } else { From 113b445bcf7d3af40a77088a9d3f2459f8727e57 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 20 Sep 2012 11:34:41 +0200 Subject: [PATCH 15/30] Better placement of plugins (touching RBAC & Audit log) If it's just an existing behavior or lib, place it in a plugin directory structure in /plugins. If there is a need to change an extern existing plugin, extend the existing plugin by a new plugin in /app/Plugin. This way there is a very clean devision between own and external code. The external code can be updated without touching own nor changed code. --- app/Config/bootstrap.php | 5 +- app/Controller/EventsController.php | 2 +- app/Model/AppModel.php | 1 + app/Model/Attribute.php | 38 +- app/Model/Event.php | 36 +- app/Model/Server.php | 10 +- app/Plugin/AclExtras/README.md | 27 - .../Case/Console/Command/AclExtrasTest.php | 240 ---- .../AclExtras/Test/test_controllers.php | 53 - app/Plugin/SysLog/Lib/SysLog.php | 91 -- .../Model/Behavior/SysLogLogableBehavior.php | 218 ++++ .../Console/Command/AclExtrasShell.php | 0 .../models/behaviors}/LogableBehavior.php | 1135 ++++++++--------- plugins/SysLog/Lib/SysLog.php | 91 ++ plugins/index.txt | 3 + 15 files changed, 894 insertions(+), 1056 deletions(-) delete mode 100755 app/Plugin/AclExtras/README.md delete mode 100755 app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php delete mode 100755 app/Plugin/AclExtras/Test/test_controllers.php delete mode 100755 app/Plugin/SysLog/Lib/SysLog.php create mode 100644 app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php rename {app/Plugin => plugins}/AclExtras/Console/Command/AclExtrasShell.php (100%) mode change 100755 => 100644 rename {app/Model/Behavior => plugins/Assets/models/behaviors}/LogableBehavior.php (74%) mode change 100755 => 100644 create mode 100644 plugins/SysLog/Lib/SysLog.php create mode 100644 plugins/index.txt diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index e629c2b58..8441231c7 100755 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -162,5 +162,8 @@ Configure::write('CyDefSIG.correlation', 'sql'); // correlation between a * */ -CakePlugin::load('SysLog'); CakePlugin::load('AclExtras'); + +CakePlugin::load('SysLog'); +CakePlugin::load('Assets'); // having Logable +CakePlugin::load('SysLogLogable'); diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index d2913ec8f..373299bff 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -45,7 +45,7 @@ class EventsController extends AppController { $this->Auth->allow('dot'); // TODO Audit, activate logable in a Controller - if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { + if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('SysLogLogable')) { $this->{$this->modelClass}->setUserData($this->activeUser); } diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 4c9b8bde7..fa18cd10b 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -21,6 +21,7 @@ */ App::uses('Model', 'Model'); +App::uses('LogableBehavior', 'Assets.models/behaviors'); /** * Application model for Cake. diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 34cd49f78..b3a74d6a3 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -11,14 +11,14 @@ App::uses('File', 'Utility'); class Attribute extends AppModel { var $combinedKeys = array('event_id', 'category', 'type'); - + var $name = 'Attribute'; // TODO general - var $actsAs = array('Logable' => array( // TODO Audit, logable - 'userModel' => 'User', - 'userKey' => 'user_id', + var $actsAs = array('SysLogLogable.SysLogLogable' => array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', 'change' => 'full' )); - + /** * Display field * @@ -29,14 +29,14 @@ class Attribute extends AppModel { public $virtualFields = array( 'value' => 'IF (Attribute.value2="", Attribute.value1, CONCAT(Attribute.value1, "|", Attribute.value2))', 'category_order' => 'IF (Attribute.category="Internal reference", "a", -IF (Attribute.category="Antivirus detection", "b", -IF (Attribute.category="Payload delivery", "c", -IF (Attribute.category="Payload installation", "d", -IF (Attribute.category="Artifacts dropped", "e", -IF (Attribute.category="Persistence mechanism", "f", -IF (Attribute.category="Network activity", "g", -IF (Attribute.category="Payload type", "h", -IF (Attribute.category="Attribution", "i", +IF (Attribute.category="Antivirus detection", "b", +IF (Attribute.category="Payload delivery", "c", +IF (Attribute.category="Payload installation", "d", +IF (Attribute.category="Artifacts dropped", "e", +IF (Attribute.category="Persistence mechanism", "f", +IF (Attribute.category="Network activity", "g", +IF (Attribute.category="Payload type", "h", +IF (Attribute.category="Attribution", "i", IF (Attribute.category="External analysis", "j", "k"))))))))))' ); // TODO hardcoded @@ -318,7 +318,7 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' // update correlation.. $this->_afterSaveCorrelation($this->data['Attribute']); } - + $result = true; // if the 'data' field is set on the $this->data then save the data to the correct file if (isset($this->data['Attribute']['type']) && $this->typeIsAttachment($this->data['Attribute']['type']) && !empty($this->data['Attribute']['data'])) { @@ -341,7 +341,7 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' } } } - + if ('db' == Configure::read('CyDefSIG.correlation')) { // update correlation.. $this->_beforeDeleteCorrelation($this->data['Attribute']['id']); @@ -355,7 +355,7 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' if (!isset($this->data['Attribute']['type'])) { return false; } - + switch($this->data['Attribute']['type']) { // lowercase these things case 'md5': @@ -608,12 +608,12 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' if (in_array($type, $this->zipped_definitions)) return true; else return false; } - + function typeIsAttachment($type) { if ((in_array($type, $this->zipped_definitions)) || (in_array($type, $this->upload_definitions))) return true; else return false; } - + function base64EncodeAttachment($attribute) { $filepath = APP."files".DS.$attribute['event_id'].DS.$attribute['id']; $file = new File($filepath); @@ -695,7 +695,7 @@ IF (Attribute.category="External analysis", "j", "k"))))))))))' rename($file_attach->path, $file->path); } } - + function _afterSaveCorrelation($attribute) { $this->_beforeDeleteCorrelation($attribute); // re-add diff --git a/app/Model/Event.php b/app/Model/Event.php index e881d3ce9..05f5bf2ce 100644 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -7,14 +7,14 @@ App::uses('AppModel', 'Model'); * @property Attribute $Attribute */ class Event extends AppModel { - + var $name = 'Event'; // TODO general - var $actsAs = array('Logable' => array( // TODO Audit, logable - 'userModel' => 'User', - 'userKey' => 'user_id', + var $actsAs = array('SysLogLogable.SysLogLogable' => array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', 'change' => 'full' )); - + /** * Display field * @@ -192,23 +192,23 @@ class Event extends AppModel { App::uses('Folder', 'Utility'); $file = new Folder ($filepath); if(is_dir($filepath)) { - if(!$this->destroyDir($filepath)) { + if(!$this->destroyDir($filepath)) { throw new InternalErrorException('Delete of event file directory failed. Please report to administrator.'); } } } - - function destroyDir($dir) { - if (!is_dir($dir) || is_link($dir)) return unlink($dir); - foreach (scandir($dir) as $file) { - if ($file == '.' || $file == '..') continue; - if (!$this->destroyDir($dir.DS.$file)) { - chmod($dir.DS.$file, 0777); - if (!$this->destroyDir($dir.DS.$file)) return false; - }; - } - return rmdir($dir); - } + + function destroyDir($dir) { + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $file) { + if ($file == '.' || $file == '..') continue; + if (!$this->destroyDir($dir.DS.$file)) { + chmod($dir.DS.$file, 0777); + if (!$this->destroyDir($dir.DS.$file)) return false; + }; + } + return rmdir($dir); + } function beforeValidate() { // generate UUID if it doesn't exist diff --git a/app/Model/Server.php b/app/Model/Server.php index 0587dbdc7..cf9b29ddc 100755 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -5,14 +5,14 @@ App::uses('AppModel', 'Model'); * */ class Server extends AppModel { - + var $name = 'Server'; // TODO general - var $actsAs = array('Logable' => array( // TODO Audit, logable, check: 'userModel' and 'userKey' can be removed given default - 'userModel' => 'User', - 'userKey' => 'user_id', + var $actsAs = array('SysLogLogable.SysLogLogable' => array( // TODO Audit, logable, check: 'userModel' and 'userKey' can be removed given default + 'userModel' => 'User', + 'userKey' => 'user_id', 'change' => 'full' )); - + /** * Display field * diff --git a/app/Plugin/AclExtras/README.md b/app/Plugin/AclExtras/README.md deleted file mode 100755 index 590216c60..000000000 --- a/app/Plugin/AclExtras/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Acl Extras - -Acl Extras provides a console app that helps you manage DbAcl records more easily. Its main feature and purpose is to make generating Aco nodes for all your controllers and actions easier. It also includes some helper methods for verifying and recovering corrupted trees. - -## Installation - -Clone the repo or download a tarball and install it into `app/Plugin/AclExtras` or in any of your pluginPaths. - -## Usage - -You can find a list of commands by running `Console/cake AclExtras.AclExtras -h` from your command line. - -### Setting up the contorller - -You'll need to configure AuthComponent to use the Actions authorization method. -In your `beforeFilter` add the following: - - $this->Auth->authorize = 'actions'; - $this->Auth->actionPath = 'controllers/'; - -## Issues - -If you find an issue in the code or want to suggest something, please use the tickets at http://github.com/markstory/acl_extras/issues - -## License - -Acl Extras is licensed under the MIT license. \ No newline at end of file diff --git a/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php b/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php deleted file mode 100755 index 83983ce0d..000000000 --- a/app/Plugin/AclExtras/Test/Case/Console/Command/AclExtrasTest.php +++ /dev/null @@ -1,240 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php The MIT License - */ -App::uses('Shell', 'Console'); -App::uses('Aco', 'Model'); -App::uses('AclComponent', 'Controller/Component'); -App::uses('Controller', 'Controller'); -App::uses('AclExtrasShell', 'AclExtras.Console/Command'); - - -//Mock::generate('Aco', 'MockAco', array('children', 'verify', 'recover')); - -//import test controller class names. -include dirname(dirname(dirname(dirname(__FILE__)))) . DS . 'test_controllers.php'; - -/** - * AclExtras Shell Test case - * - * @package acl_extras.tests.cases - */ -class AclExtrasShellTestCase extends CakeTestCase { - - public $fixtures = array('core.aco', 'core.aro', 'core.aros_aco'); - -/** - * startTest - * - * @return void - * @access public - */ - function setUp() { - parent::setUp(); - Configure::write('Acl.classname', 'DbAcl'); - Configure::write('Acl.database', 'test'); - - $out = $this->getMock('ConsoleOutput', array(), array(), '', false); - $in = $this->getMock('ConsoleInput', array(), array(), '', false); - - $this->Task = $this->getMock( - 'AclExtrasShell', - array('in', 'out', 'hr', 'createFile', 'error', 'err', 'clear', 'getControllerList'), - array($out, $out, $in) - ); - } - -/** - * end the test - * - * @return void - **/ - function tearDown() { - parent::tearDown(); - unset($this->Task); - } - -/** - * test recover - * - * @return void - **/ - function testRecover() { - $this->Task->startup(); - $this->Task->args = array('Aco'); - $this->Task->Acl->Aco = $this->getMock('Aco', array('recover')); - $this->Task->Acl->Aco->expects($this->once()) - ->method('recover') - ->will($this->returnValue(true)); - - $this->Task->expects($this->once()) - ->method('out') - ->with($this->matchesRegularExpression('/recovered/')); - - $this->Task->recover(); - } - -/** - * test verify - * - * @return void - **/ - function testVerify() { - $this->Task->startup(); - $this->Task->args = array('Aco'); - $this->Task->Acl->Aco = $this->getMock('Aco', array('verify')); - $this->Task->Acl->Aco->expects($this->once()) - ->method('verify') - ->will($this->returnValue(true)); - - $this->Task->expects($this->once()) - ->method('out') - ->with($this->matchesRegularExpression('/valid/')); - - $this->Task->verify(); - } - -/** - * test startup - * - * @return void - **/ - function testStartup() { - $this->assertEqual($this->Task->Acl, null); - $this->Task->startup(); - $this->assertInstanceOf('AclComponent', $this->Task->Acl); - } - -/** - * clean fixtures and setup mock - * - * @return void - **/ - function _cleanAndSetup() { - $tableName = $this->db->fullTableName('acos'); - $this->db->execute('DELETE FROM ' . $tableName); - $this->Task->expects($this->any()) - ->method('getControllerList') - ->will($this->returnValue(array('CommentsController', 'PostsController', 'BigLongNamesController'))); - - $this->Task->startup(); - } -/** - * Test aco_update method. - * - * @return void - **/ - function testAcoUpdate() { - $this->_cleanAndSetup(); - $this->Task->aco_update(); - - $Aco = $this->Task->Acl->Aco; - - $result = $Aco->node('controllers/Comments'); - $this->assertEqual($result[0]['Aco']['alias'], 'Comments'); - - $result = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($result), 3); - $this->assertEqual($result[0]['Aco']['alias'], 'add'); - $this->assertEqual($result[1]['Aco']['alias'], 'index'); - $this->assertEqual($result[2]['Aco']['alias'], 'delete'); - - $result = $Aco->node('controllers/Posts'); - $this->assertEqual($result[0]['Aco']['alias'], 'Posts'); - $result = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($result), 3); - - $result = $Aco->node('controllers/BigLongNames'); - $this->assertEqual($result[0]['Aco']['alias'], 'BigLongNames'); - $result = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($result), 4); - } - -/** - * test syncing of Aco records - * - * @return void - **/ - function testAcoSyncRemoveMethods() { - $this->_cleanAndSetup(); - $this->Task->aco_update(); - - $Aco = $this->Task->Acl->Aco; - $Aco->cacheQueries = false; - - $result = $Aco->node('controllers/Comments'); - $new = array( - 'parent_id' => $result[0]['Aco']['id'], - 'alias' => 'some_method' - ); - $Aco->create($new); - $Aco->save(); - $children = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($children), 4); - - $this->Task->aco_sync(); - $children = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($children), 3); - - $method = $Aco->node('controllers/Commments/some_method'); - $this->assertFalse($method); - } - -/** - * test adding methods with aco_update - * - * @return void - **/ - function testAcoUpdateAddingMethods() { - $this->_cleanAndSetup(); - $this->Task->aco_update(); - - $Aco = $this->Task->Acl->Aco; - $Aco->cacheQueries = false; - - $result = $Aco->node('controllers/Comments'); - $children = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($children), 3); - - $Aco->delete($children[0]['Aco']['id']); - $Aco->delete($children[1]['Aco']['id']); - $this->Task->aco_update(); - - $children = $Aco->children($result[0]['Aco']['id']); - $this->assertEqual(count($children), 3); - } - -/** - * test adding controllers on sync - * - * @return void - **/ - function testAddingControllers() { - $this->_cleanAndSetup(); - $this->Task->aco_update(); - - $Aco = $this->Task->Acl->Aco; - $Aco->cacheQueries = false; - - $result = $Aco->node('controllers/Comments'); - $Aco->delete($result[0]['Aco']['id']); - - $this->Task->aco_update(); - $newResult = $Aco->node('controllers/Comments'); - $this->assertNotEqual($newResult[0]['Aco']['id'], $result[0]['Aco']['id']); - } -} diff --git a/app/Plugin/AclExtras/Test/test_controllers.php b/app/Plugin/AclExtras/Test/test_controllers.php deleted file mode 100755 index 714e7616a..000000000 --- a/app/Plugin/AclExtras/Test/test_controllers.php +++ /dev/null @@ -1,53 +0,0 @@ -isWindows()) { - $default_facility = LOG_USER; - } else { - $default_facility= LOG_LOCAL0; - } - $options += array('ident' => LOGS, 'facility' => $default_facility); - $this->_ident = $options['ident']; - $this->_facility = $options['facility']; - } - -/** -* Utilty method to identify if we're running on a Windows box. -* -* @return boolean if running on windows. -*/ - function isWindows() { - return (DIRECTORY_SEPARATOR == '\\' ? true : false); - } - -/** -* Implements writing to the specified syslog -* -* @param string $type The type of log you are making. -* @param string $message The message you want to log. -* @return boolean success of write. -*/ - function write($type, $message) { - $debugTypes = array('notice', 'info', 'debug'); - $priority = LOG_INFO; - if ($type == 'error' || $type == 'warning') { - $priority = LOG_ERR; - } elseif (in_array($type, $debugTypes)) { - $priority = LOG_DEBUG; - } - $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; - if (!openlog($this->_ident, LOG_PID | LOG_PERROR, $this->_facility)) { - return false; - } - $result = syslog($priority, $output); - closelog(); - return $result; - } -} -?> \ No newline at end of file diff --git a/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php new file mode 100644 index 000000000..058e0a76b --- /dev/null +++ b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php @@ -0,0 +1,218 @@ +settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['add']) && $this->settings[$Model->alias]['skip']['add'] && $created) { + return true; + } elseif (isset($this->settings[$Model->alias]['skip']['edit']) && $this->settings[$Model->alias]['skip']['edit'] && !$created) { + return true; + } + $keys = array_keys($Model->data[$Model->alias]); + $diff = array_diff($keys, $this->settings[$Model->alias]['ignore']); + if (sizeof($diff) == 0 && empty($Model->logableAction)) { + return false; + } + if ($Model->id) { + $id = $Model->id; + } elseif ($Model->insertId) { + $id = $Model->insertId; + } + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']])) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; + } + if (isset($this->schema['description'])) { + $logData['Log']['description'] = $Model->alias . ' '; + if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { + $logData['Log']['description'] .= '"' . $Model->data[$Model->alias][$Model->displayField] . '" '; + } + + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= '(' . $id . ') '; + } + + if ($created) { + $logData['Log']['description'] .= __('added', TRUE); + } else { + $logData['Log']['description'] .= __('updated', TRUE); + } + } + if (isset($this->schema['action'])) { + if ($created) { + $logData['Log']['action'] = 'add'; + } else { + $logData['Log']['action'] = 'edit'; + } + + } + if (isset($this->schema['change'])) { + $logData['Log']['change'] = ''; + $db_fields = array_keys($Model->schema()); + $changed_fields = array(); + foreach ( $Model->data[$Model->alias] as $key => $value ) { + if (isset($Model->data[$Model->alias][$Model->primaryKey]) && !empty($this->old) && isset($this->old[$Model->alias][$key])) { + $old = $this->old[$Model->alias][$key]; + } else { + $old = ''; + } + // TODO Audit, removed 'revision' as well + if ($key != 'modified' && $key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { + if ($this->settings[$Model->alias]['change'] == 'full') { + $changed_fields[] = $key . ' (' . $old . ') => (' . $value . ')'; + } else if ($this->settings[$Model->alias]['change'] == 'serialize') { + $changed_fields[$key] = array( + 'old' => $old, + 'value' => $value); + } else { + $changed_fields[] = $key; + } + } + } + $changes = sizeof($changed_fields); + if ($changes == 0) { + return true; + } + if ($this->settings[$Model->alias]['change'] == 'serialize') { + $logData['Log']['change'] = serialize($changed_fields); + } else { + $logData['Log']['change'] = implode(', ', $changed_fields); + } + $logData['Log']['changes'] = $changes; + } + $this->_saveLog($Model, $logData); + } + + function _saveLog(&$Model, $logData, $title = null) { + + if ($title !== NULL) { + $logData['Log']['title'] = $title; + } elseif ($Model->displayField == $Model->primaryKey) { + $logData['Log']['title'] = $Model->alias . ' (' . $Model->id . ')'; + } elseif (isset($Model->data[$Model->alias][$Model->displayField])) { + $logData['Log']['title'] = $Model->data[$Model->alias][$Model->displayField]; + } else { + $logData['Log']['title'] = $Model->field($Model->displayField); + } + + if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { + // by miha nahtigal + $logData['Log'][$this->settings[$Model->alias]['classField']] = $Model->name; + } + + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && !isset($logData['Log'][$this->settings[$Model->alias]['foreignKey']])) { + if ($Model->id) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->id; + } elseif ($Model->insertId) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->insertId; + } + } + + if (!isset($this->schema['action'])) { + unset($logData['Log']['action']); + } elseif (isset($Model->logableAction) && !empty($Model->logableAction)) { + $logData['Log']['action'] = implode(',', $Model->logableAction); // . ' ' . $logData['Log']['action']; + unset($Model->logableAction); + } + + if (isset($this->schema['version_id']) && isset($Model->version_id)) { + $logData['Log']['version_id'] = $Model->version_id; + unset($Model->version_id); + } + + if (isset($this->schema['ip']) && $this->userIP) { + $logData['Log']['ip'] = $this->userIP; + } + + if (isset($this->schema[$this->settings[$Model->alias]['userKey']]) && $this->user) { + $logData['Log'][$this->settings[$Model->alias]['userKey']] = $this->user[$this->UserModel->alias][$this->UserModel->primaryKey]; + } + + if (isset($this->schema['description'])) { + if ($this->user && $this->UserModel) { + $logData['Log']['description'] .= ' by ' . $this->settings[$Model->alias]['userModel'] . ' "' . $this->user[$this->UserModel->alias][$this->UserModel->displayField] . '"'; + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= ' (' . $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] . ')'; + } + + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['description'] .= ' by System'; + } + $logData['Log']['description'] .= '.'; + } + if (isset($this->schema['email'])) { // TODO Audit, LogableBehevior email + if ($this->user && $this->UserModel) { + $logData['Log']['email'] = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['email'] = 'SYS'; + } + } + if (isset($this->schema['org'])) { // TODO Audit, LogableBehevior org CHECK!!! + if ($this->user && $this->UserModel) { + $logData['Log']['org'] = $this->user[$this->UserModel->alias][$this->UserModel->orgField]; + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['org'] = 'SYS'; + } + } + if (isset($this->schema['title'])) { // TODO LogableBehevior title + if ($this->user && $this->UserModel) { // $Model->data[$Model->alias][$Model->displayField] + switch ($Model->alias) { + case "User": // TODO Audit, not used here but done in UsersController + $title = 'User ('. $Model->data[$Model->alias]['id'].') '. $Model->data[$Model->alias]['email']; + break; + case "Event": + $this->Events = new EventsController(); + $this->Events->constructClasses(); + $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '.$this->Events->getName($Model->data[$Model->alias]['id']); + $logData['Log']['title'] = $title; + break; + case "Attribute": + if (isset($Model->combinedKeys)) { + if (is_array($Model->combinedKeys)) { + $title = 'Attribute ('. $Model->data[$Model->alias]['id'].') '.'from Event ('. $Model->data[$Model->alias]['event_id'].'): '. $Model->data[$Model->alias][$Model->combinedKeys[1]].'/'. $Model->data[$Model->alias][$Model->combinedKeys[2]].' '. $Model->data[$Model->alias]['value1']; + $logData['Log']['title'] = $title; + } + } + break; + case "Server": + $this->Servers = new ServersController(); + $this->Servers->constructClasses(); + $title = 'Server ('. $Model->data[$Model->alias]['id'].'): '. $this->Servers->getName($Model->data[$Model->alias]['id']); + $logData['Log']['title'] = $title; + break; + default: + if (isset($Model->combinedKeys)) { + if (is_array($Model->combinedKeys)) { + $title = ''; + foreach ($Model->combinedKeys as $combinedKey) { + $title .= '/'. $Model->data[$Model->alias][$combinedKey]; + } + $title = substr($title ,1); + $logData['Log']['title'] = $title; + } + } + } + } + } + $this->Log->create($logData); + $this->Log->save(null, array( + 'validate' => false, + 'callbacks' => false)); + + // write to syslogd as well + $syslog = new SysLog(); + if (isset($logData['Log']['change'])) { + $syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']); + } else { + $syslog->write('notice', $logData['Log']['description']); + } + } +} \ No newline at end of file diff --git a/app/Plugin/AclExtras/Console/Command/AclExtrasShell.php b/plugins/AclExtras/Console/Command/AclExtrasShell.php old mode 100755 new mode 100644 similarity index 100% rename from app/Plugin/AclExtras/Console/Command/AclExtrasShell.php rename to plugins/AclExtras/Console/Command/AclExtrasShell.php diff --git a/app/Model/Behavior/LogableBehavior.php b/plugins/Assets/models/behaviors/LogableBehavior.php old mode 100755 new mode 100644 similarity index 74% rename from app/Model/Behavior/LogableBehavior.php rename to plugins/Assets/models/behaviors/LogableBehavior.php index 30012ea83..e17f0ba93 --- a/app/Model/Behavior/LogableBehavior.php +++ b/plugins/Assets/models/behaviors/LogableBehavior.php @@ -1,602 +1,535 @@ - (Alek), age (28) => (29)] or [name, age] - * - * - "version_id" [int] : cooperates with RevisionBehavior to link the the shadow table (thus linking to old data) - * - * Remember that Logable behavior needs to be added after RevisionBehavior. In fact, just put it last to be safe. - * - * Optionally register what user was responisble for the activity : - * - * - Supply configuration only if defaults are wrong. Example given with defaults : - * - * class Apple extends AppModel { - * var $name = 'Apple'; - * var $actsAs = array('Logable' => array('userModel' => 'User', 'userKey' => 'user_id')); - * [..] - * - * - In AppController (or single controller if only needed once) add these lines to beforeFilter : - * - * if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { - * $this->{$this->modelClass}->setUserData($this->activeUser); - * } - * - * This is not used any longer, as AuthComponent collect the user data instead. - * - * Where "$activeUser" should be an array in the standard format for the User model used : - * - * $activeUser = array( $UserModel->alias => array( $UserModel->primaryKey => 123, $UserModel->displayField => 'Alexander')); - * // any other key is just ignored by this behaviour. - * - * @author Alexander Morland (alexander#maritimecolours.no) - * @co-author Eskil Mjelva Saatvedt - * @co-author Ronny Vindenes - * @co-author Carl Erik Fyllingen - * @contributor Miha - * @category Behavior - * @version 2.3 - * @modified 15.november 2011 by Eskil - */ - -class LogableBehavior extends ModelBehavior { - - public $user = NULL; - - public $UserModel = FALSE; - - public $settings = array(); - - public $defaults = array( -'enabled' => true, -'userModel' => 'User', -'userKey' => 'user_id', -'change' => 'list', -'description_ids' => TRUE, -'skip' => array(), -'ignore' => array(), -'classField' => 'model', -'foreignKey' => 'model_id'); - - public $schema = array(); - - /** - * Cake called intializer - * Config options are : - * userModel : 'User'. Class name of the user model you want to use (User by default), if you want to save User in log - * userKey : 'user_id'. The field for saving the user to (user_id by default). - * change : 'list' > [name, age]. Set to 'full' for [name (alek) => (Alek), age (28) => (29)] - * description_ids : TRUE. Set to FALSE to not include model id and user id in the title field - * skip : array(). String array of actions to not log - * - * @param Object $Model - * @param array $config - */ - function setup(&$Model, $config = array()) { - - if (!is_array($config)) { - $config = array(); - } - $this->settings[$Model->alias] = array_merge($this->defaults, $config); - $this->settings[$Model->alias]['ignore'][] = $Model->primaryKey; - - $this->Log = & ClassRegistry::init('Log'); - if ($this->settings[$Model->alias]['userModel'] != $Model->alias) { - $this->UserModel = & ClassRegistry::init($this->settings[$Model->alias]['userModel']); - } else { - $this->UserModel = $Model; - } - $this->schema = $this->Log->schema(); - App::import('Component', 'Auth'); - $this->user[$this->settings[$Model->alias]['userModel']] = AuthComponent::user(); - } - - function settings(&$Model) { - - return $this->settings[$Model->alias]; - } - - function enableLog(&$Model, $enable = null) { - - if ($enable !== null) { - $this->settings[$Model->alias]['enabled'] = $enable; - } - return $this->settings[$Model->alias]['enabled']; - } - - /** - * Useful for getting logs for a model, takes params to narrow find. - * This method can actually also be used to find logs for all models or - * even another model. Using no params will return all activities for - * the models it is called from. - * - * Possible params : - * 'model' : mixed (NULL) String with className, NULL to get current or FALSE to get everything - * 'action' : string (NULL) String with action (add/edit/delete), NULL gets all - * 'order' : string ('created DESC') String with custom order - * 'conditions : array (array()) Add custom conditions - * 'model_id' : int (NULL) Add a int - * - * (remember to use your own user key if you're not using 'user_id') - * 'user_id' : int (NULL) Defaults to all users, supply id if you want for only one User - * - * @param Object $Model - * @param array $params - * @return array - */ - function findLog(&$Model, $params = array()) { - - $defaults = array( - $this->settings[$Model->alias]['classField'] => NULL, -'action' => NULL, -'order' => 'created DESC', - $this->settings[$Model->alias]['userKey'] => NULL, -'conditions' => array(), - $this->settings[$Model->alias]['foreignKey'] => NULL, -'fields' => array(), -'limit' => 50); - $params = array_merge($defaults, $params); - $options = array( -'order' => $params['order'], -'conditions' => $params['conditions'], -'fields' => $params['fields'], -'limit' => $params['limit']); - if ($params[$this->settings[$Model->alias]['classField']] === NULL) { - $params[$this->settings[$Model->alias]['classField']] = $Model->alias; - } - if ($params[$this->settings[$Model->alias]['classField']]) { - if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { - $options['conditions'][$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; - } elseif (isset($this->schema['description'])) { - $options['conditions']['description LIKE '] = $params[$this->settings[$Model->alias]['classField']] . '%'; - } else { - return FALSE; - } - } - if ($params['action'] && isset($this->schema['action'])) { - $options['conditions']['action'] = $params['action']; - } - if ($params[$this->settings[$Model->alias]['userKey']] && $this->UserModel && is_numeric($params[$this->settings[$Model->alias]['userKey']])) { - $options['conditions'][$this->settings[$Model->alias]['userKey']] = $params[$this->settings[$Model->alias]['userKey']]; - } - if ($params[$this->settings[$Model->alias]['foreignKey']] && is_numeric($params[$this->settings[$Model->alias]['foreignKey']])) { - $options['conditions'][$this->settings[$Model->alias]['foreignKey']] = $params[$this->settings[$Model->alias]['foreignKey']]; - } - return $this->Log->find('all', $options); - } - - /** - * Get list of actions for one user. - * Params for getting (one line) activity descriptions - * and/or for just one model - * - * @example $this->Model->findUserActions(301,array('model' => 'BookTest')); - * @example $this->Model->findUserActions(301,array('events' => true)); - * @example $this->Model->findUserActions(301,array('fields' => array('id','model'),'model' => 'BookTest'); - * @param Object $Model - * @param int $user_id - * @param array $params - * @return array - */ - function findUserActions(&$Model, $user_id, $params = array()) { - - if (!$this->UserModel) { - return NULL; - } - // if logged in user is asking for her own log, use the data we allready have - if (isset($this->user) && isset($this->user[$this->UserModel->alias][$this->UserModel->primaryKey]) && $user_id == $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] && isset($this->user[$this->UserModel->alias][$this->UserModel->displayField])) { - $username = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; - } else { - $this->UserModel->recursive = -1; - $user = $this->UserModel->find(array( - $this->UserModel->primaryKey => $user_id)); - $username = $user[$this->UserModel->alias][$this->UserModel->displayField]; - } - $fields = array(); - if (isset($params['fields'])) { - if (is_array($params['fields'])) { - $fields = $params['fields']; - } else { - $fields = array( - $params['fields']); - } - } - $conditions = array( - $this->settings[$Model->alias]['userKey'] => $user_id); - if (isset($params[$this->settings[$Model->alias]['classField']])) { - $conditions[$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; - } - $data = $this->Log->find('all', array( -'conditions' => $conditions, -'recursive' => -1, -'fields' => $fields)); - if (!isset($params['events']) || ( isset($params['events']) && $params['events'] == false )) { - return $data; - } - $result = array(); - foreach ( $data as $key => $row ) { - $one = $row['Log']; - $result[$key]['Log']['id'] = $one['id']; - $result[$key]['Log']['event'] = $username; - // have all the detail models and change as list : - if (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one['change']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { - if ($one['action'] == 'edit') { - $result[$key]['Log']['event'] .= ' edited ' . $one['change'] . ' of ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - - // ' at '.$one['created']; - } elseif ($one['action'] == 'add') { - $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - } elseif ($one['action'] == 'delete') { - $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - } - - } elseif (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { // have model,model_id and action - if ($one['action'] == 'edit') { - $result[$key]['Log']['event'] .= ' edited ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - - // ' at '.$one['created']; - } elseif ($one['action'] == 'add') { - $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - } elseif ($one['action'] == 'delete') { - $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; - } - } else { // only description field exist - $result[$key]['Log']['event'] = $one['description']; - } - - } - return $result; - } - - /** - * Use this to supply a model with the data of the logged in User. - * Intended to be called in AppController::beforeFilter like this : - * - * if ($this->{$this->modelClass}->Behaviors->attached('Logable')) { - * $this->{$this->modelClass}->setUserData($activeUser);/ - * } - * - * The $userData array is expected to look like the result of a - * User::find(array('id'=>123)); - * - * @param Object $Model - * @param array $userData - */ - function setUserData(&$Model, $userData = null) { - - if ($userData) { - $this->user = $userData; - } - } - - /** - * Used for logging custom actions that arent crud, like login or download. - * - * @example $this->Boat->customLog('ship', 66, array('title' => 'Titanic heads out')); - * @param Object $Model - * @param string $action name of action that is taking place (dont use the crud ones) - * @param int $id id of the logged item (ie model_id in logs table) - * @param array $values optional other values for your logs table - */ - function customLog(&$Model, $action, $id, $values = array()) { - - $logData['Log'] = $values; - /** @todo clean up $logData */ - if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && is_numeric($id)) { - $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; - } - $title = NULL; - if (isset($values['title'])) { - $title = $values['title']; - unset($logData['Log']['title']); - } - $logData['Log']['action'] = $action; - $this->_saveLog($Model, $logData, $title); - } - - function clearUserData(&$Model) { - - $this->user = NULL; - } - - function setUserIp(&$Model, $userIP = null) { - - $this->userIP = $userIP; - } - - function beforeDelete(&$Model) { - - if (!$this->settings[$Model->alias]['enabled']) { - return true; - } - if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { - return true; - } - $Model->recursive = -1; - $Model->read(); - return true; - } - - function afterDelete(&$Model) { - - if (!$this->settings[$Model->alias]['enabled']) { - return true; - } - if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { - return true; - } - $logData = array(); - if (isset($this->schema['description'])) { - $logData['Log']['description'] = $Model->alias; - if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { - $logData['Log']['description'] .= ' "' . $Model->data[$Model->alias][$Model->displayField] . '"'; - } - if ($this->settings[$Model->alias]['description_ids']) { - $logData['Log']['description'] .= ' (' . $Model->id . ') '; - } - $logData['Log']['description'] .= __('deleted', TRUE); - } - $logData['Log']['action'] = 'delete'; - $this->_saveLog($Model, $logData); - } - - function beforeSave(&$Model) { - - if (isset($this->schema['change']) && $Model->id) { - $this->old = $Model->find('first', array( -'conditions' => array( - $Model->alias . '.' . $Model->primaryKey => $Model->id), -'recursive' => -1)); - } - return true; - } - - function afterSave(&$Model, $created) { - - if (!$this->settings[$Model->alias]['enabled']) { - return true; - } - if (isset($this->settings[$Model->alias]['skip']['add']) && $this->settings[$Model->alias]['skip']['add'] && $created) { - return true; - } elseif (isset($this->settings[$Model->alias]['skip']['edit']) && $this->settings[$Model->alias]['skip']['edit'] && !$created) { - return true; - } - $keys = array_keys($Model->data[$Model->alias]); - $diff = array_diff($keys, $this->settings[$Model->alias]['ignore']); - if (sizeof($diff) == 0 && empty($Model->logableAction)) { - return false; - } - if ($Model->id) { - $id = $Model->id; - } elseif ($Model->insertId) { - $id = $Model->insertId; - } - if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']])) { - $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; - } - if (isset($this->schema['description'])) { - $logData['Log']['description'] = $Model->alias . ' '; - if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { - $logData['Log']['description'] .= '"' . $Model->data[$Model->alias][$Model->displayField] . '" '; - } - - if ($this->settings[$Model->alias]['description_ids']) { - $logData['Log']['description'] .= '(' . $id . ') '; - } - - if ($created) { - $logData['Log']['description'] .= __('added', TRUE); - } else { - $logData['Log']['description'] .= __('updated', TRUE); - } - } - if (isset($this->schema['action'])) { - if ($created) { - $logData['Log']['action'] = 'add'; - } else { - $logData['Log']['action'] = 'edit'; - } - - } - if (isset($this->schema['change'])) { - $logData['Log']['change'] = ''; - $db_fields = array_keys($Model->schema()); - $changed_fields = array(); - foreach ( $Model->data[$Model->alias] as $key => $value ) { - if (isset($Model->data[$Model->alias][$Model->primaryKey]) && !empty($this->old) && isset($this->old[$Model->alias][$key])) { - $old = $this->old[$Model->alias][$key]; - } else { - $old = ''; - } - // TODO Audit, removed 'revision' as well - if ($key != 'revision' && $key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { - if ($this->settings[$Model->alias]['change'] == 'full') { - $changed_fields[] = $key . ' (' . $old . ') => (' . $value . ')'; - } else if ($this->settings[$Model->alias]['change'] == 'serialize') { - $changed_fields[$key] = array( -'old' => $old, -'value' => $value); - } else { - $changed_fields[] = $key; - } - } - } - $changes = sizeof($changed_fields); - if ($changes == 0) { - return true; - } - if ($this->settings[$Model->alias]['change'] == 'serialize') { - $logData['Log']['change'] = serialize($changed_fields); - } else { - $logData['Log']['change'] = implode(', ', $changed_fields); - } - $logData['Log']['changes'] = $changes; - } - $this->_saveLog($Model, $logData); - } - - /** - * Does the actual saving of the Log model. Also adds the special field if possible. - * - * If model field in table, add the Model->alias - * If action field is NOT in table, remove it from dataset - * If the userKey field in table, add it to dataset - * If userData is supplied to model, add it to the title - * - * @param Object $Model - * @param array $logData - */ - function _saveLog(&$Model, $logData, $title = null) { - - if ($title !== NULL) { - $logData['Log']['title'] = $title; - } elseif ($Model->displayField == $Model->primaryKey) { - $logData['Log']['title'] = $Model->alias . ' (' . $Model->id . ')'; - } elseif (isset($Model->data[$Model->alias][$Model->displayField])) { - $logData['Log']['title'] = $Model->data[$Model->alias][$Model->displayField]; - } else { - $logData['Log']['title'] = $Model->field($Model->displayField); - } - - if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { - // by miha nahtigal - $logData['Log'][$this->settings[$Model->alias]['classField']] = $Model->name; - } - - if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && !isset($logData['Log'][$this->settings[$Model->alias]['foreignKey']])) { - if ($Model->id) { - $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->id; - } elseif ($Model->insertId) { - $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->insertId; - } - } - - if (!isset($this->schema['action'])) { - unset($logData['Log']['action']); - } elseif (isset($Model->logableAction) && !empty($Model->logableAction)) { - $logData['Log']['action'] = implode(',', $Model->logableAction); // . ' ' . $logData['Log']['action']; - unset($Model->logableAction); - } - - if (isset($this->schema['version_id']) && isset($Model->version_id)) { - $logData['Log']['version_id'] = $Model->version_id; - unset($Model->version_id); - } - - if (isset($this->schema['ip']) && $this->userIP) { - $logData['Log']['ip'] = $this->userIP; - } - - if (isset($this->schema[$this->settings[$Model->alias]['userKey']]) && $this->user) { - $logData['Log'][$this->settings[$Model->alias]['userKey']] = $this->user[$this->UserModel->alias][$this->UserModel->primaryKey]; - } - - if (isset($this->schema['description'])) { - if ($this->user && $this->UserModel) { - $logData['Log']['description'] .= ' by ' . $this->settings[$Model->alias]['userModel'] . ' "' . $this->user[$this->UserModel->alias][$this->UserModel->displayField] . '"'; - if ($this->settings[$Model->alias]['description_ids']) { - $logData['Log']['description'] .= ' (' . $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] . ')'; - } - - } else { - // UserModel is active, but the data hasnt been set. Assume system action. - $logData['Log']['description'] .= ' by System'; - } - $logData['Log']['description'] .= '.'; - } - if (isset($this->schema['email'])) { // TODO Audit, LogableBehevior email - if ($this->user && $this->UserModel) { - $logData['Log']['email'] = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; - } else { - // UserModel is active, but the data hasnt been set. Assume system action. - $logData['Log']['email'] = 'SYS'; - } - } - if (isset($this->schema['org'])) { // TODO Audit, LogableBehevior org CHECK!!! - if ($this->user && $this->UserModel) { - $logData['Log']['org'] = $this->user[$this->UserModel->alias][$this->UserModel->orgField]; - } else { - // UserModel is active, but the data hasnt been set. Assume system action. - $logData['Log']['org'] = 'SYS'; - } - } - if (isset($this->schema['title'])) { // TODO LogableBehevior title - if ($this->user && $this->UserModel) { // $Model->data[$Model->alias][$Model->displayField] - switch ($Model->alias) { - case "User": // TODO Audit, not used here but done in UsersController - $title = 'User ('. $Model->data[$Model->alias]['id'].') '. $Model->data[$Model->alias]['email']; - break; - case "Event": - $this->Events = new EventsController(); - $this->Events->constructClasses(); - $title = 'Event ('. $Model->data[$Model->alias]['id'].'): '.$this->Events->getName($Model->data[$Model->alias]['id']); - $logData['Log']['title'] = $title; - break; - case "Attribute": - if (isset($Model->combinedKeys)) { - if (is_array($Model->combinedKeys)) { - $title = 'Attribute ('. $Model->data[$Model->alias]['id'].') '.'from Event ('. $Model->data[$Model->alias]['event_id'].'): '. $Model->data[$Model->alias][$Model->combinedKeys[1]].'/'. $Model->data[$Model->alias][$Model->combinedKeys[2]].' '. $Model->data[$Model->alias]['value1']; - $logData['Log']['title'] = $title; - } - } - break; - case "Server": - $this->Servers = new ServersController(); - $this->Servers->constructClasses(); - $title = 'Server ('. $Model->data[$Model->alias]['id'].'): '. $this->Servers->getName($Model->data[$Model->alias]['id']); - $logData['Log']['title'] = $title; - break; - default: - if (isset($Model->combinedKeys)) { - if (is_array($Model->combinedKeys)) { - $title = ''; - foreach ($Model->combinedKeys as $combinedKey) { - $title .= '/'. $Model->data[$Model->alias][$combinedKey]; - } - $title = substr($title ,1); - $logData['Log']['title'] = $title; - } - } - } - } - } - $this->Log->create($logData); - $this->Log->save(null, array( -'validate' => false, -'callbacks' => false)); - - // write to syslogd as well - $syslog = new SysLog(); - if (isset($logData['Log']['change'])) { - $syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']); - } else { - $syslog->write('notice', $logData['Log']['description']); - } - } + (Alek), age (28) => (29)] or [name, age] + * + * - "version_id" [int] : cooperates with RevisionBehavior to link the the shadow table (thus linking to old data) + * + * Remember that Logable behavior needs to be added after RevisionBehavior. In fact, just put it last to be safe. + * + * Optionally register what user was responisble for the activity : + * + * - Supply configuration only if defaults are wrong. Example given with defaults : + * + * class Apple extends AppModel { + * var $name = 'Apple'; + * var $actsAs = array('Logable' => array('userModel' => 'User', 'userKey' => 'user_id')); + * [..] + * + * - In AppController (or single controller if only needed once) add these lines to beforeFilter : + * + * if (sizeof($this->uses) && $this->{$this->modelClass}->Behaviors->attached('Logable')) { + * $this->{$this->modelClass}->setUserData($this->activeUser); + * } + * + * This is not used any longer, as AuthComponent collect the user data instead. + * + * Where "$activeUser" should be an array in the standard format for the User model used : + * + * $activeUser = array( $UserModel->alias => array( $UserModel->primaryKey => 123, $UserModel->displayField => 'Alexander')); + * // any other key is just ignored by this behaviour. + * + * @author Alexander Morland (alexander#maritimecolours.no) + * @co-author Eskil Mjelva Saatvedt + * @co-author Ronny Vindenes + * @co-author Carl Erik Fyllingen + * @contributor Miha + * @category Behavior + * @version 2.3 + * @modified 15.november 2011 by Eskil + */ + +class LogableBehavior extends ModelBehavior { + + public $user = NULL; + + public $UserModel = FALSE; + + public $settings = array(); + + public $defaults = array( + 'enabled' => true, + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'list', + 'description_ids' => TRUE, + 'skip' => array(), + 'ignore' => array(), + 'classField' => 'model', + 'foreignKey' => 'model_id'); + + public $schema = array(); + + /** + * Cake called intializer + * Config options are : + * userModel : 'User'. Class name of the user model you want to use (User by default), if you want to save User in log + * userKey : 'user_id'. The field for saving the user to (user_id by default). + * change : 'list' > [name, age]. Set to 'full' for [name (alek) => (Alek), age (28) => (29)] + * description_ids : TRUE. Set to FALSE to not include model id and user id in the title field + * skip : array(). String array of actions to not log + * + * @param Object $Model + * @param array $config + */ + function setup(&$Model, $config = array()) { + + if (!is_array($config)) { + $config = array(); + } + $this->settings[$Model->alias] = array_merge($this->defaults, $config); + $this->settings[$Model->alias]['ignore'][] = $Model->primaryKey; + + $this->Log = & ClassRegistry::init('Log'); + if ($this->settings[$Model->alias]['userModel'] != $Model->alias) { + $this->UserModel = & ClassRegistry::init($this->settings[$Model->alias]['userModel']); + } else { + $this->UserModel = $Model; + } + $this->schema = $this->Log->schema(); + App::import('Component', 'Auth'); + $this->user[$this->settings[$Model->alias]['userModel']] = AuthComponent::user(); + } + + function settings(&$Model) { + + return $this->settings[$Model->alias]; + } + + function enableLog(&$Model, $enable = null) { + + if ($enable !== null) { + $this->settings[$Model->alias]['enabled'] = $enable; + } + return $this->settings[$Model->alias]['enabled']; + } + + /** + * Useful for getting logs for a model, takes params to narrow find. + * This method can actually also be used to find logs for all models or + * even another model. Using no params will return all activities for + * the models it is called from. + * + * Possible params : + * 'model' : mixed (NULL) String with className, NULL to get current or FALSE to get everything + * 'action' : string (NULL) String with action (add/edit/delete), NULL gets all + * 'order' : string ('created DESC') String with custom order + * 'conditions : array (array()) Add custom conditions + * 'model_id' : int (NULL) Add a int + * + * (remember to use your own user key if you're not using 'user_id') + * 'user_id' : int (NULL) Defaults to all users, supply id if you want for only one User + * + * @param Object $Model + * @param array $params + * @return array + */ + function findLog(&$Model, $params = array()) { + + $defaults = array( + $this->settings[$Model->alias]['classField'] => NULL, + 'action' => NULL, + 'order' => 'created DESC', + $this->settings[$Model->alias]['userKey'] => NULL, + 'conditions' => array(), + $this->settings[$Model->alias]['foreignKey'] => NULL, + 'fields' => array(), + 'limit' => 50); + $params = array_merge($defaults, $params); + $options = array( + 'order' => $params['order'], + 'conditions' => $params['conditions'], + 'fields' => $params['fields'], + 'limit' => $params['limit']); + if ($params[$this->settings[$Model->alias]['classField']] === NULL) { + $params[$this->settings[$Model->alias]['classField']] = $Model->alias; + } + if ($params[$this->settings[$Model->alias]['classField']]) { + if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { + $options['conditions'][$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; + } elseif (isset($this->schema['description'])) { + $options['conditions']['description LIKE '] = $params[$this->settings[$Model->alias]['classField']] . '%'; + } else { + return FALSE; + } + } + if ($params['action'] && isset($this->schema['action'])) { + $options['conditions']['action'] = $params['action']; + } + if ($params[$this->settings[$Model->alias]['userKey']] && $this->UserModel && is_numeric($params[$this->settings[$Model->alias]['userKey']])) { + $options['conditions'][$this->settings[$Model->alias]['userKey']] = $params[$this->settings[$Model->alias]['userKey']]; + } + if ($params[$this->settings[$Model->alias]['foreignKey']] && is_numeric($params[$this->settings[$Model->alias]['foreignKey']])) { + $options['conditions'][$this->settings[$Model->alias]['foreignKey']] = $params[$this->settings[$Model->alias]['foreignKey']]; + } + return $this->Log->find('all', $options); + } + + /** + * Get list of actions for one user. + * Params for getting (one line) activity descriptions + * and/or for just one model + * + * @example $this->Model->findUserActions(301,array('model' => 'BookTest')); + * @example $this->Model->findUserActions(301,array('events' => true)); + * @example $this->Model->findUserActions(301,array('fields' => array('id','model'),'model' => 'BookTest'); + * @param Object $Model + * @param int $user_id + * @param array $params + * @return array + */ + function findUserActions(&$Model, $user_id, $params = array()) { + + if (!$this->UserModel) { + return NULL; + } + // if logged in user is asking for her own log, use the data we allready have + if (isset($this->user) && isset($this->user[$this->UserModel->alias][$this->UserModel->primaryKey]) && $user_id == $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] && isset($this->user[$this->UserModel->alias][$this->UserModel->displayField])) { + $username = $this->user[$this->UserModel->alias][$this->UserModel->displayField]; + } else { + $this->UserModel->recursive = -1; + $user = $this->UserModel->find(array( + $this->UserModel->primaryKey => $user_id)); + $username = $user[$this->UserModel->alias][$this->UserModel->displayField]; + } + $fields = array(); + if (isset($params['fields'])) { + if (is_array($params['fields'])) { + $fields = $params['fields']; + } else { + $fields = array( + $params['fields']); + } + } + $conditions = array( + $this->settings[$Model->alias]['userKey'] => $user_id); + if (isset($params[$this->settings[$Model->alias]['classField']])) { + $conditions[$this->settings[$Model->alias]['classField']] = $params[$this->settings[$Model->alias]['classField']]; + } + $data = $this->Log->find('all', array( + 'conditions' => $conditions, + 'recursive' => -1, + 'fields' => $fields)); + if (!isset($params['events']) || ( isset($params['events']) && $params['events'] == false )) { + return $data; + } + $result = array(); + foreach ( $data as $key => $row ) { + $one = $row['Log']; + $result[$key]['Log']['id'] = $one['id']; + $result[$key]['Log']['event'] = $username; + // have all the detail models and change as list : + if (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one['change']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { + if ($one['action'] == 'edit') { + $result[$key]['Log']['event'] .= ' edited ' . $one['change'] . ' of ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + + // ' at '.$one['created']; + } elseif ($one['action'] == 'add') { + $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } elseif ($one['action'] == 'delete') { + $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } + + } elseif (isset($one[$this->settings[$Model->alias]['classField']]) && isset($one['action']) && isset($one[$this->settings[$Model->alias]['foreignKey']])) { // have model,model_id and action + if ($one['action'] == 'edit') { + $result[$key]['Log']['event'] .= ' edited ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + + // ' at '.$one['created']; + } elseif ($one['action'] == 'add') { + $result[$key]['Log']['event'] .= ' added a ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } elseif ($one['action'] == 'delete') { + $result[$key]['Log']['event'] .= ' deleted the ' . low($one[$this->settings[$Model->alias]['classField']]) . '(id ' . $one[$this->settings[$Model->alias]['foreignKey']] . ')'; + } + } else { // only description field exist + $result[$key]['Log']['event'] = $one['description']; + } + + } + return $result; + } + + /** + * Use this to supply a model with the data of the logged in User. + * Intended to be called in AppController::beforeFilter like this : + * + * if ($this->{$this->modelClass}->Behaviors->attached('Logable')) { + * $this->{$this->modelClass}->setUserData($activeUser);/ + * } + * + * The $userData array is expected to look like the result of a + * User::find(array('id'=>123)); + * + * @param Object $Model + * @param array $userData + */ + function setUserData(&$Model, $userData = null) { + + if ($userData) { + $this->user = $userData; + } + } + + /** + * Used for logging custom actions that arent crud, like login or download. + * + * @example $this->Boat->customLog('ship', 66, array('title' => 'Titanic heads out')); + * @param Object $Model + * @param string $action name of action that is taking place (dont use the crud ones) + * @param int $id id of the logged item (ie model_id in logs table) + * @param array $values optional other values for your logs table + */ + function customLog(&$Model, $action, $id, $values = array()) { + + $logData['Log'] = $values; + /** @todo clean up $logData */ + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && is_numeric($id)) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; + } + $title = NULL; + if (isset($values['title'])) { + $title = $values['title']; + unset($logData['Log']['title']); + } + $logData['Log']['action'] = $action; + $this->_saveLog($Model, $logData, $title); + } + + function clearUserData(&$Model) { + + $this->user = NULL; + } + + function setUserIp(&$Model, $userIP = null) { + + $this->userIP = $userIP; + } + + function beforeDelete(&$Model) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { + return true; + } + $Model->recursive = -1; + $Model->read(); + return true; + } + + function afterDelete(&$Model) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['delete']) && $this->settings[$Model->alias]['skip']['delete']) { + return true; + } + $logData = array(); + if (isset($this->schema['description'])) { + $logData['Log']['description'] = $Model->alias; + if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { + $logData['Log']['description'] .= ' "' . $Model->data[$Model->alias][$Model->displayField] . '"'; + } + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= ' (' . $Model->id . ') '; + } + $logData['Log']['description'] .= __('deleted', TRUE); + } + $logData['Log']['action'] = 'delete'; + $this->_saveLog($Model, $logData); + } + + function beforeSave(&$Model) { + + if (isset($this->schema['change']) && $Model->id) { + $this->old = $Model->find('first', array( + 'conditions' => array( + $Model->alias . '.' . $Model->primaryKey => $Model->id), + 'recursive' => -1)); + } + return true; + } + + function afterSave(&$Model, $created) { + + if (!$this->settings[$Model->alias]['enabled']) { + return true; + } + if (isset($this->settings[$Model->alias]['skip']['add']) && $this->settings[$Model->alias]['skip']['add'] && $created) { + return true; + } elseif (isset($this->settings[$Model->alias]['skip']['edit']) && $this->settings[$Model->alias]['skip']['edit'] && !$created) { + return true; + } + $keys = array_keys($Model->data[$Model->alias]); + $diff = array_diff($keys, $this->settings[$Model->alias]['ignore']); + if (sizeof($diff) == 0 && empty($Model->logableAction)) { + return false; + } + if ($Model->id) { + $id = $Model->id; + } elseif ($Model->insertId) { + $id = $Model->insertId; + } + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']])) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $id; + } + if (isset($this->schema['description'])) { + $logData['Log']['description'] = $Model->alias . ' '; + if (isset($Model->data[$Model->alias][$Model->displayField]) && $Model->displayField != $Model->primaryKey) { + $logData['Log']['description'] .= '"' . $Model->data[$Model->alias][$Model->displayField] . '" '; + } + + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= '(' . $id . ') '; + } + + if ($created) { + $logData['Log']['description'] .= __('added', TRUE); + } else { + $logData['Log']['description'] .= __('updated', TRUE); + } + } + if (isset($this->schema['action'])) { + if ($created) { + $logData['Log']['action'] = 'add'; + } else { + $logData['Log']['action'] = 'edit'; + } + + } + if (isset($this->schema['change'])) { + $logData['Log']['change'] = ''; + $db_fields = array_keys($Model->schema()); + $changed_fields = array(); + foreach ( $Model->data[$Model->alias] as $key => $value ) { + if (isset($Model->data[$Model->alias][$Model->primaryKey]) && !empty($this->old) && isset($this->old[$Model->alias][$key])) { + $old = $this->old[$Model->alias][$key]; + } else { + $old = ''; + } + if ($key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { + if ($this->settings[$Model->alias]['change'] == 'full') { + $changed_fields[] = $key . ' (' . $old . ') => (' . $value . ')'; + } else if ($this->settings[$Model->alias]['change'] == 'serialize') { + $changed_fields[$key] = array( + 'old' => $old, + 'value' => $value); + } else { + $changed_fields[] = $key; + } + } + } + $changes = sizeof($changed_fields); + if ($changes == 0) { + return true; + } + if ($this->settings[$Model->alias]['change'] == 'serialize') { + $logData['Log']['change'] = serialize($changed_fields); + } else { + $logData['Log']['change'] = implode(', ', $changed_fields); + } + $logData['Log']['changes'] = $changes; + } + $this->_saveLog($Model, $logData); + } + + /** + * Does the actual saving of the Log model. Also adds the special field if possible. + * + * If model field in table, add the Model->alias + * If action field is NOT in table, remove it from dataset + * If the userKey field in table, add it to dataset + * If userData is supplied to model, add it to the title + * + * @param Object $Model + * @param array $logData + */ + function _saveLog(&$Model, $logData, $title = null) { + + if ($title !== NULL) { + $logData['Log']['title'] = $title; + } elseif ($Model->displayField == $Model->primaryKey) { + $logData['Log']['title'] = $Model->alias . ' (' . $Model->id . ')'; + } elseif (isset($Model->data[$Model->alias][$Model->displayField])) { + $logData['Log']['title'] = $Model->data[$Model->alias][$Model->displayField]; + } else { + $logData['Log']['title'] = $Model->field($Model->displayField); + } + + if (isset($this->schema[$this->settings[$Model->alias]['classField']])) { + // by miha nahtigal + $logData['Log'][$this->settings[$Model->alias]['classField']] = $Model->name; + } + + if (isset($this->schema[$this->settings[$Model->alias]['foreignKey']]) && !isset($logData['Log'][$this->settings[$Model->alias]['foreignKey']])) { + if ($Model->id) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->id; + } elseif ($Model->insertId) { + $logData['Log'][$this->settings[$Model->alias]['foreignKey']] = $Model->insertId; + } + } + + if (!isset($this->schema['action'])) { + unset($logData['Log']['action']); + } elseif (isset($Model->logableAction) && !empty($Model->logableAction)) { + $logData['Log']['action'] = implode(',', $Model->logableAction); // . ' ' . $logData['Log']['action']; + unset($Model->logableAction); + } + + if (isset($this->schema['version_id']) && isset($Model->version_id)) { + $logData['Log']['version_id'] = $Model->version_id; + unset($Model->version_id); + } + + if (isset($this->schema['ip']) && $this->userIP) { + $logData['Log']['ip'] = $this->userIP; + } + + if (isset($this->schema[$this->settings[$Model->alias]['userKey']]) && $this->user) { + $logData['Log'][$this->settings[$Model->alias]['userKey']] = $this->user[$this->UserModel->alias][$this->UserModel->primaryKey]; + } + + if (isset($this->schema['description'])) { + if ($this->user && $this->UserModel) { + $logData['Log']['description'] .= ' by ' . $this->settings[$Model->alias]['userModel'] . ' "' . $this->user[$this->UserModel->alias][$this->UserModel->displayField] . '"'; + if ($this->settings[$Model->alias]['description_ids']) { + $logData['Log']['description'] .= ' (' . $this->user[$this->UserModel->alias][$this->UserModel->primaryKey] . ')'; + } + + } else { + // UserModel is active, but the data hasnt been set. Assume system action. + $logData['Log']['description'] .= ' by System'; + } + $logData['Log']['description'] .= '.'; + } + $this->Log->create($logData); + $this->Log->save(null, array( + 'validate' => false, + 'callbacks' => false)); + } } \ No newline at end of file diff --git a/plugins/SysLog/Lib/SysLog.php b/plugins/SysLog/Lib/SysLog.php new file mode 100644 index 000000000..3116ffcd3 --- /dev/null +++ b/plugins/SysLog/Lib/SysLog.php @@ -0,0 +1,91 @@ +isWindows()) { + $default_facility = LOG_USER; + } else { + $default_facility= LOG_LOCAL0; + } + $options += array('ident' => LOGS, 'facility' => $default_facility); + $this->_ident = $options['ident']; + $this->_facility = $options['facility']; + } + +/** + * Utilty method to identify if we're running on a Windows box. + * + * @return boolean if running on windows. + */ + function isWindows() { + return (DIRECTORY_SEPARATOR == '\\' ? true : false); + } + +/** + * Implements writing to the specified syslog + * + * @param string $type The type of log you are making. + * @param string $message The message you want to log. + * @return boolean success of write. + */ + function write($type, $message) { + $debugTypes = array('notice', 'info', 'debug'); + $priority = LOG_INFO; + if ($type == 'error' || $type == 'warning') { + $priority = LOG_ERR; + } elseif (in_array($type, $debugTypes)) { + $priority = LOG_DEBUG; + } + $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; + if (!openlog($this->_ident, LOG_PID | LOG_PERROR, $this->_facility)) { + return false; + } + $result = syslog($priority, $output); + closelog(); + return $result; + } +} +?> \ No newline at end of file diff --git a/plugins/index.txt b/plugins/index.txt new file mode 100644 index 000000000..2685fc909 --- /dev/null +++ b/plugins/index.txt @@ -0,0 +1,3 @@ +https://github.com/markstory/acl_extras +https://github.com/eskil-saatvedt/CakePHP-Assets (LogableBehavior) +http://bakery.cakephp.org/articles/rikdc/2010/06/07/syslog-component \ No newline at end of file From 3199839286408abe509c325a50a1207a33f04016 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 20 Sep 2012 12:07:19 +0200 Subject: [PATCH 16/30] GFI sandbox import. do not load non existing stored_created_file. --- app/Controller/EventsController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 373299bff..dd661ebe9 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1233,7 +1233,10 @@ class EventsController extends AppController { } } $actualFile = $root_dir.DS.'Analysis'.DS.'proc_'.$index.DS.'modified_files'.DS.$actualFileName; - $this->Event->Attribute->uploadAttachment($actualFile,$realFileName,false,$id); + $file = new File($actualFile); + if ($file->exists()) { + $this->Event->Attribute->uploadAttachment($actualFile,$realFileName,false,$id); + } } //Network activity -- ip-dst From 64a354678d4f1bd650984b64410420b7cdbf07d1 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 20 Sep 2012 13:27:36 +0200 Subject: [PATCH 17/30] GFI sandbox import. Replace Windows environment variables %UserProfile% and %AllUsersProfile%. --- app/Controller/EventsController.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index dd661ebe9..5515fb90d 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1204,7 +1204,13 @@ class EventsController extends AppController { if ($key == 'filename') $arrayItemKey = (string)$val; if ($key == 'md5') $arrayItemValue = (string)$val; } - $files[str_replace('C:\Users\John','%UserProfile%',$arrayItemKey)] = $arrayItemValue; + // replace Windows Environment Variables + $arrayItemKey = str_replace('C:\Users\John', '%UserProfile%', $arrayItemKey); + $arrayItemKey = str_replace('C:\Documents and Settings\James Cocks', '%UserProfile%', $arrayItemKey); + $arrayItemKey = str_replace('C:\DOCUME~1\JAMESC~1', '%UserProfile%', $arrayItemKey); + $arrayItemKey = str_replace('C:\Documents and Settings\All Users', '%AllUsersProfile%', $arrayItemKey); + + $files[$arrayItemKey] = $arrayItemValue; } //$files = array_unique($files); From 60c67d4e141d6ea1b58f95a99dddebb40d22386b Mon Sep 17 00:00:00 2001 From: noud Date: Mon, 24 Sep 2012 16:17:54 +0200 Subject: [PATCH 18/30] Audit log. After change plugins, forgot to skip revision in SysLogLogableBehavior. --- .../SysLogLogable/Model/Behavior/SysLogLogableBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php index 036c4332d..560b5f9ee 100644 --- a/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php +++ b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php @@ -64,7 +64,7 @@ class SysLogLogableBehavior extends LogableBehavior { $old = ''; } // TODO Audit, removed 'revision' as well - if ($key != 'modified' && $key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { + if ($key != 'revision' && $key != 'modified' && !in_array($key, $this->settings[$Model->alias]['ignore']) && $value != $old && in_array($key, $db_fields)) { if ($this->settings[$Model->alias]['change'] == 'full') { $changed_fields[] = $key . ' (' . $old . ') => (' . $value . ')'; } else if ($this->settings[$Model->alias]['change'] == 'serialize') { From 503d5bcb0e271cb1209918ea48d5ea1097742471 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 26 Sep 2012 17:13:19 +0200 Subject: [PATCH 19/30] Audit log. Edit user (now?) needs an extra check on the second password. --- app/Controller/UsersController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 6e6f46125..1ba51153e 100755 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -230,7 +230,7 @@ class UsersController extends AppController { $fieldsResultStr = ''; $c = 0; foreach ($fields as $field) { - if ($fieldsOldValues[$c] != $fieldsNewValues[$c]) { + if (isset($fieldsOldValues[$c]) && $fieldsOldValues[$c] != $fieldsNewValues[$c]) { if($field != 'confirm_password') $fieldsResultStr = $fieldsResultStr . ', ' . $field . ' (' . $fieldsOldValues[$c] . ') => (' . $fieldsNewValues[$c] . ')'; } $c++; From 7bf0e2f882f2d0588cfc685040d0805d4148584d Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 10 Oct 2012 10:53:52 +0200 Subject: [PATCH 20/30] Groups Do not delete group if there is still Users as children. --- app/Controller/GroupsController.php | 2 +- app/Model/Group.php | 2 +- .../Behavior/OrphansProtectableBehavior.php | 125 +++++++++++++++++ plugins/index.txt | 3 +- .../models/behaviors/orphans_protectable.php | 126 ++++++++++++++++++ 5 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 app/Plugin/MagicTools/Model/Behavior/OrphansProtectableBehavior.php create mode 100644 plugins/magic_tools/models/behaviors/orphans_protectable.php diff --git a/app/Controller/GroupsController.php b/app/Controller/GroupsController.php index 457daaff1..d6ef4ba34 100755 --- a/app/Controller/GroupsController.php +++ b/app/Controller/GroupsController.php @@ -138,7 +138,7 @@ class GroupsController extends AppController { if (!$this->Group->exists()) { throw new NotFoundException(__('Invalid group')); } - if ($this->Group->delete()) { + if ($this->Group->delete(null, false)) { $this->Session->setFlash(__('Group deleted')); $this->redirect(array('action' => 'index')); } diff --git a/app/Model/Group.php b/app/Model/Group.php index f9860013e..0cf91d66f 100755 --- a/app/Model/Group.php +++ b/app/Model/Group.php @@ -51,7 +51,7 @@ class Group extends AppModel { * * @var unknown_type */ - public $actsAs = array('Acl' => array('type' => 'requester')); + public $actsAs = array('Acl' => array('type' => 'requester'), 'MagicTools.OrphansProtectable'); /** * TODO ACL: 2: hook Group into CakePHP ACL system (so link to aros) diff --git a/app/Plugin/MagicTools/Model/Behavior/OrphansProtectableBehavior.php b/app/Plugin/MagicTools/Model/Behavior/OrphansProtectableBehavior.php new file mode 100644 index 000000000..399c935b1 --- /dev/null +++ b/app/Plugin/MagicTools/Model/Behavior/OrphansProtectableBehavior.php @@ -0,0 +1,125 @@ +_deletionError = null; // Stores the deletion error message + $Model->orphansProtectableOptions = array_merge(array( + 'listPossibleOrphans' => true, + 'htmlError' => false + ), $settings); + } + + /** + * Checks if there would be orphaned record left behind after deletion of this record; if so, deletion is prevented. + * + * @param $model Model + * @param $cascade boolean + * @return boolean + */ + function beforeDelete(&$Model, $cascade) { + if($cascade) return true; + return !$Model->wouldLeaveOrphanedRecordsBehind(); + } + + /** + * Checks if deletion of this record would leave orphaned associated records behind. + * + * @param $model Model + * @return boolean + */ + function wouldLeaveOrphanedRecordsBehind(&$Model) { + $possibleOrphans = array(); + + foreach($Model->hasMany as $model => $settings) { + // Is relationship is dependent? + if($settings['dependent']){ // Yes! Possible orphans are deleted, too! + // Do nothing + } else { // No! Possible orphans should be protected! + // Is there a possible orphan for this relation? + $Model->{$model}->recursive = -1; + $objects = $Model->{$model}->find('all', array('conditions' => array($settings['className'].'.'.$settings['foreignKey'] => $Model->id), 'order' => 'id asc')); + if(count($objects) > 0) { // Yes, there is at least one possible orphan! + $objectIds = array(); + foreach($objects as $object) { + $objectIds[] = $object[$model]['id']; + } + $possibleOrphans[$model] = $objectIds; + } + } + } + + // Would orphans be left behind? + if(count($possibleOrphans) > 0) { // Yes! Create deletion error message! + $Model->_deletionError = $Model->createDeletionError($possibleOrphans); + return true; + } else { // No! + return false; + } + } + + /** + * Returns the deletion error message (if there is one). + * + * @param $model Model + * @return string + */ + function getDeletionError(&$Model) { + return $Model->_deletionError; + } + + /** + * Creates the deletion error message and returns it. + * + * @param $model Model + * @param $possibleOrphans array + * @return string + */ + function createDeletionError(&$Model, $possibleOrphans) { + $errorParts = array(); + foreach($possibleOrphans as $model => $ids) { + $count = count($ids); + $modelName = $count > 1 ? Inflector::pluralize($model) : $model; + $errorParts[] = $count.' '.__($modelName, true).' (ID: '.$Model->createDeletionErrorIds($model, $ids).')'; + } + return __('it has the following dependent items', true).': '.implode($errorParts, ', '); + } + + /** + * Creates a string containing HTML-links to comma separated IDs of the potentially orphaned records of the specified model. + * + * @param $model Model + * @param $orphanModel string + * @param $ids array + * @return string + */ + function createDeletionErrorIds(&$Model, $orphanModel, $ids) { + $messageParts = array(); + if($Model->orphansProtectableOptions['htmlError']) { + foreach($ids as $id) { + $messageParts[] = ''.$id.''; // TODO: Noch unschön! --zivi-muh + } + } else { + $messageParts = $ids; + } + return implode($messageParts, ', '); + } +} +?> \ No newline at end of file diff --git a/plugins/index.txt b/plugins/index.txt index 2685fc909..96470e1ca 100644 --- a/plugins/index.txt +++ b/plugins/index.txt @@ -1,3 +1,4 @@ https://github.com/markstory/acl_extras https://github.com/eskil-saatvedt/CakePHP-Assets (LogableBehavior) -http://bakery.cakephp.org/articles/rikdc/2010/06/07/syslog-component \ No newline at end of file +http://bakery.cakephp.org/articles/rikdc/2010/06/07/syslog-component +http://xp-dev.com/trac/cakephp/browser/cakephp/libs/magic_tools \ No newline at end of file diff --git a/plugins/magic_tools/models/behaviors/orphans_protectable.php b/plugins/magic_tools/models/behaviors/orphans_protectable.php new file mode 100644 index 000000000..c4e2d61f4 --- /dev/null +++ b/plugins/magic_tools/models/behaviors/orphans_protectable.php @@ -0,0 +1,126 @@ +_deletionError = null; // Stores the deletion error message + $Model->orphansProtectableOptions = array_merge(array( + 'listPossibleOrphans' => true, + 'htmlError' => false + ), $settings); + } + + /** + * Checks if there would be orphaned record left behind after deletion of this record; if so, deletion is prevented. + * + * @param $model Model + * @param $cascade boolean + * @return boolean + */ + function beforeDelete(&$model, $cascade) { + if($cascade) return true; + return !$Model->wouldLeaveOrphanedRecordsBehind(); + } + + /** + * Checks if deletion of this record would leave orphaned associated records behind. + * + * @param $model Model + * @return boolean + */ + function wouldLeaveOrphanedRecordsBehind(&$model) { + $possibleOrphans = array(); + + foreach($Model->hasMany as $model => $settings) { + // Is relationship is dependent? + if($settings['dependent']){ // Yes! Possible orphans are deleted, too! + // Do nothing + } else { // No! Possible orphans should be protected! + // Is there a possible orphan for this relation? + $Model->{$model}->recursive = -1; + $objects = $Model->{$model}->find('all', array('conditions' => array($settings['className'].'.'.$settings['foreignKey'] => $Model->id), 'order' => 'id asc')); + if(count($objects) > 0) { // Yes, there is at least one possible orphan! + $objectIds = array(); + foreach($objects as $object) { + $objectIds[] = $object[$model]['id']; + } + $possibleOrphans[$model] = $objectIds; + } + } + } + + // Would orphans be left behind? + if(count($possibleOrphans) > 0) { // Yes! Create deletion error message! + $Model->_deletionError = $Model->createDeletionError($possibleOrphans); + return true; + } else { // No! + return false; + } + } + + /** + * Returns the deletion error message (if there is one). + * + * @param $model Model + * @return string + */ + function getDeletionError(&$model) { + return $Model->_deletionError; + } + + /** + * Creates the deletion error message and returns it. + * + * @param $model Model + * @param $possibleOrphans array + * @return string + */ + function createDeletionError(&$model, $possibleOrphans) { + $errorParts = array(); + foreach($possibleOrphans as $model => $ids) { + $count = count($ids); + $modelName = $count > 1 ? Inflector::pluralize($model) : $model; + $errorParts[] = $count.' '.__($modelName, true).' (ID: '.$Model->createDeletionErrorIds($model, $ids).')'; + } + return __('it has the following dependent items', true).': '.implode($errorParts, ', '); + } + + /** + * Creates a string containing HTML-links to comma separated IDs of the potentially orphaned records of the specified model. + * + * @param $model Model + * @param $orphanModel string + * @param $ids array + * @return string + */ + function createDeletionErrorIds(&$model, $orphanModel, $ids) { + $messageParts = array(); + if($Model->orphansProtectableOptions['htmlError']) { + foreach($ids as $id) { + $messageParts[] = ''.$id.''; // TODO: Noch unschön! --zivi-muh + } + } else { + $messageParts = $ids; + } + return implode($messageParts, ', '); + } +} +?> \ No newline at end of file From aea3aa59348eea2124735406f0539b9a4b671bf5 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 10 Oct 2012 11:43:04 +0200 Subject: [PATCH 21/30] xxx.default.php put plugins loading into bootstrap.default.php --- app/Config/bootstrap.default.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Config/bootstrap.default.php b/app/Config/bootstrap.default.php index 525dd8e86..946fc8efc 100644 --- a/app/Config/bootstrap.default.php +++ b/app/Config/bootstrap.default.php @@ -183,6 +183,12 @@ Configure::write('CyDefSIG.correlation', 'sql'); // correlation between a * */ +CakePlugin::load('AclExtras'); + +CakePlugin::load('SysLog'); +CakePlugin::load('Assets'); // having Logable +CakePlugin::load('SysLogLogable'); +CakePlugin::load('MagicTools'); // having OrphansProtectable /** * You can attach event listeners to the request lifecyle as Dispatcher Filter . By Default CakePHP bundles two filters: From e1aed1c4c1080a87a968031c46068eb5a9967f61 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 10 Oct 2012 15:51:18 +0200 Subject: [PATCH 22/30] RBAC removed a leftover on in-activating buttons that did show on IE. --- app/View/Elements/actions_menu.ctp | 4 ++-- app/View/Users/admin_view.ctp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/View/Elements/actions_menu.ctp b/app/View/Elements/actions_menu.ctp index 21ffdfe19..727c845ba 100755 --- a/app/View/Elements/actions_menu.ctp +++ b/app/View/Elements/actions_menu.ctp @@ -1,5 +1,5 @@ -
  • Html->link(__('New Event', true), array('controller' => 'events', 'action' => 'add'), array('id' => $buttonAddStatus,'class' => $buttonAddStatus, 'disabled' => 'disabled', 'readonly' => 'readonly')); ?>
  • +
  • Html->link(__('New Event', true), array('controller' => 'events', 'action' => 'add'), array('id' => $buttonAddStatus,'class' => $buttonAddStatus)); ?>
  • Html->link(__('List Events', true), array('controller' => 'events', 'action' => 'index')); ?>
  • Html->link(__('List Attributes', true), array('controller' => 'attributes', 'action' => 'index')); ?>
  • Html->link(__('Search Attributes', true), array('controller' => 'attributes', 'action' => 'search')); ?>
  • @@ -31,5 +31,5 @@
  •  
  • Html->link(__('List Logs', true), array('controller' => 'logs', 'action' => 'index', 'admin' => true)); ?>
  • -
  • Html->link(__('Search Logs', true), array('controller' => 'logs', 'action' => 'admin_search', 'admin' => true, 'disabled' => 'disabled', 'readonly' => 'readonly')); ?>
  • +
  • Html->link(__('Search Logs', true), array('controller' => 'logs', 'action' => 'admin_search', 'admin' => true)); ?>
  • Html->link(__('Delete User'), array('admin' => true, 'action' => 'delete', $user['User']['id']), array('id' => $buttonModifyStatus . $buttonCounter++, 'class' => $buttonModifyStatus)); ?>
  • Html->link(__('List Users'), array('admin' => true, 'action' => 'index')); ?>
  • -
  • Html->link(__('New User'), array('admin' => true, 'action' => 'add'), array('id' => $buttonAddStatus, 'class' => $buttonAddStatus, 'disabled' => 'disabled', 'readonly' => 'readonly')); ?>
  • +
  • Html->link(__('New User'), array('admin' => true, 'action' => 'add'), array('id' => $buttonAddStatus . $buttonCounter++, 'class' => $buttonAddStatus)); ?>
  • Html->link(__('List Events'), array('controller' => 'events', 'action' => 'index')); ?>
  • -
  • Html->link(__('New Event'), array('controller' => 'events', 'action' => 'add'), array('id' => $buttonAddStatus, 'class' => $buttonAddStatus, 'disabled' => 'disabled', 'readonly' => 'readonly')); ?>
  • +
  • Html->link(__('New Event'), array('controller' => 'events', 'action' => 'add'), array('id' => $buttonAddStatus . $buttonCounter++, 'class' => $buttonAddStatus)); ?>
  • + + \ No newline at end of file diff --git a/app/View/Users/admin_view.ctp b/app/View/Users/admin_view.ctp index 94d4c6c71..78ec41ce6 100755 --- a/app/View/Users/admin_view.ctp +++ b/app/View/Users/admin_view.ctp @@ -129,3 +129,35 @@ $buttonCounter = 0; + \ No newline at end of file diff --git a/app/View/Users/view.ctp b/app/View/Users/view.ctp index f217c8c65..fb3a4ee9c 100755 --- a/app/View/Users/view.ctp +++ b/app/View/Users/view.ctp @@ -60,3 +60,36 @@ element('actions_menu'); ?> + + \ No newline at end of file From 9435419ebc7cda2acc980c081e90c4a974a00e56 Mon Sep 17 00:00:00 2001 From: noud Date: Thu, 11 Oct 2012 17:17:21 +0200 Subject: [PATCH 24/30] RBAC Group in user profile is no link. --- app/View/Users/view.ctp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/View/Users/view.ctp b/app/View/Users/view.ctp index fb3a4ee9c..333767746 100755 --- a/app/View/Users/view.ctp +++ b/app/View/Users/view.ctp @@ -21,7 +21,7 @@
    - Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?> +  
    From f8a0e0c6cfdd2fe41e0b0665908d90ba03c51f46 Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 17 Oct 2012 09:32:07 +0200 Subject: [PATCH 25/30] Routes (logs pagination) recommitted to be sure it's in repo. --- app/Config/routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Config/routes.php b/app/Config/routes.php index 327a2e1f4..10feeebb5 100755 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -31,7 +31,7 @@ Router::connect('/users/admin_index/*', array('controller' => 'users', 'action' => 'index', 'admin' => true)); Router::connect('/groups/admin_index/*', array('controller' => 'groups', 'action' => 'index', 'admin' => true)); Router::connect('/logs/admin_index/*', array('controller' => 'logs', 'action' => 'index', 'admin' => true)); - + // Activate REST Router::mapResources(array('events')); Router::parseExtensions('xml'); From 5bef441abac1327063a728f7c5eedf9ff07fedfd Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 17 Oct 2012 10:42:09 +0200 Subject: [PATCH 26/30] GFI Sandbox --- app/Controller/EventsController.php | 68 +++++++++++++++++++++-------- app/Model/Attribute.php | 12 ++--- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index d40153390..11316cbe5 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1186,10 +1186,17 @@ class EventsController extends AppController { if ((string)$key == 'filename') $realFileName = (string)$val; } } + $realMalware = $realFileName; $rootDir = APP . "files" . DS . $id . DS; $malware = $rootDir . DS . 'sample'; $this->Event->Attribute->uploadAttachment($malware, $realFileName, true, $id); + //Network activity -- .pcap + $realFileName = 'analysis.pcap'; + $rootDir = APP . "files" . DS . $id . DS; + $malware = $rootDir . DS . 'Analysis' . DS . 'analysis.pcap'; + $this->Event->Attribute->uploadAttachment($malware, $realFileName, false, $id, 'Network activity'); + //Artifacts dropped -- filename|md5 $files = array(); // TODO what about stored_modified_file ?? @@ -1201,11 +1208,6 @@ class EventsController extends AppController { if ($key == 'filename') $arrayItemKey = (string)$val; if ($key == 'md5') $arrayItemValue = (string)$val; } - // replace Windows Environment Variables - $arrayItemKey = str_replace('C:\Users\John', '%UserProfile%', $arrayItemKey); - $arrayItemKey = str_replace('C:\Documents and Settings\James Cocks', '%UserProfile%', $arrayItemKey); - $arrayItemKey = str_replace('C:\DOCUME~1\JAMESC~1', '%UserProfile%', $arrayItemKey); - $arrayItemKey = str_replace('C:\Documents and Settings\All Users', '%AllUsersProfile%', $arrayItemKey); $files[$arrayItemKey] = $arrayItemValue; } @@ -1213,19 +1215,25 @@ class EventsController extends AppController { // write content.. foreach ($files as $key => $val) { - // add attribute.. - $this->Attribute->read(null, 1); - $this->Attribute->save(array( - 'event_id' => $id, - 'category' => 'Artifacts dropped', - 'type' => 'filename|md5', - 'value' => $key . '|' . $val, - 'to_ids' => false)); + $keyName = $key; + // replace Windows Environment Variables + $keyName = str_replace('C:\Users\John', '%UserProfile%', $keyName); + $keyName = str_replace('C:\Documents and Settings\James Cocks', '%UserProfile%', $keyName); + $keyName = str_replace('C:\DOCUME~1\JAMESC~1', '%UserProfile%', $keyName); + $keyName = str_replace('C:\Documents and Settings\All Users', '%AllUsersProfile%', $keyName); + + if (!strpos($key, $realMalware)) { + $itsType = 'malware-sample'; + } else { + $itsType = 'filename|md5'; + } // the actual files.. // seek $val in dirs and add.. $ext = substr($key, strrpos($key, '.')); $actualFileName = $val . $ext; + $actualFileNameBase = str_replace('\\', '/', $key); + $actualFileNameArray[] = basename($actualFileNameBase); $realFileName = end(explode('\\', $key)); // have the filename, now look at parents parent for the process number $express = "/analysis/processes/process/stored_files/stored_created_file[@md5='" . $val . "']/../.."; @@ -1236,9 +1244,10 @@ class EventsController extends AppController { } } $actualFile = $rootDir . DS . 'Analysis' . DS . 'proc_' . $index . DS . 'modified_files' . DS . $actualFileName; + $extraPath = 'Analysis' . DS . 'proc_' . $index . DS . 'modified_files' . DS; $file = new File($actualFile); - if ($file->exists()) { - $this->Event->Attribute->uploadAttachment($actualFile, $realFileName, false, $id); + if ($file->exists()) { // TODO put in array for test later + $this->Event->Attribute->uploadAttachment($actualFile, $realFileName, true, $id, null, $extraPath, $keyName); // TODO was false } } @@ -1282,18 +1291,41 @@ class EventsController extends AppController { // add attribute.. $this->Attribute->read(null, 1); if ($val == '[binary_data]') { + $itsCategory = 'Persistence mechanism'; $itsType = 'regkey'; $itsValue = $key; } else { - $itsType = 'regkey|value'; - $itsValue = $key . '|' . $val; + if ($this->strposarray($val,$actualFileNameArray)) { + $itsCategory = 'Persistence mechanism'; + $itsType = 'regkey|value'; + $itsValue = $key . '|' . $val; + } else { + // replace Windows Environment Variables + $val = str_replace('C:\Users\John', '%UserProfile%', $val); + $val = str_replace('C:\Documents and Settings\James Cocks', '%UserProfile%', $val); + $val = str_replace('C:\DOCUME~1\JAMESC~1', '%UserProfile%', $val); + $val = str_replace('C:\Documents and Settings\All Users', '%AllUsersProfile%', $val); + + $itsCategory = 'Artifacts dropped'; // Persistence mechanism + $itsType = 'regkey|value'; + $itsValue = $key . '|' . $val; + } } $this->Attribute->save(array( 'event_id' => $id, - 'category' => 'Persistence mechanism', + 'category' => $itsCategory, // 'Persistence mechanism' 'type' => $itsType, 'value' => $itsValue, 'to_ids' => false)); } } + public function strposarray($string, $array) { + $toReturn = false; + foreach ($array as $item) { + if (strpos($string,$item)) { + $toReturn = true; + } + } + return $toReturn; + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index d8411230e..1ca089700 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -703,7 +703,7 @@ class Attribute extends AppModel { * * @return void */ - public function uploadAttachment($fileP, $realFileName, $malware, $eventId = null) { + public function uploadAttachment($fileP, $realFileName, $malware, $eventId = null, $category = null, $extraPath = '', $fullFileName = '') { // Check if there were problems with the file upload // only keep the last part of the filename, this should prevent directory attacks $filename = basename($fileP); @@ -713,14 +713,14 @@ class Attribute extends AppModel { $this->create(); $this->data['Attribute']['event_id'] = $eventId; if ($malware) { - $this->data['Attribute']['category'] = "Payload delivery"; + $this->data['Attribute']['category'] = $category ? $category : "Payload delivery"; $this->data['Attribute']['type'] = "malware-sample"; - $this->data['Attribute']['value'] = $realFileName . '|' . $tmpfile->md5(); // TODO gives problems with bigger files + $this->data['Attribute']['value'] = $fullFileName ? $fullFileName . '|' . $tmpfile->md5() : $filename . '|' . $tmpfile->md5(); // TODO gives problems with bigger files $this->data['Attribute']['to_ids'] = 1; // LATER let user choose to send this to IDS } else { - $this->data['Attribute']['category'] = "Artifacts dropped"; + $this->data['Attribute']['category'] = $category ? $category : "Artifacts dropped"; $this->data['Attribute']['type'] = "attachment"; - $this->data['Attribute']['value'] = $realFileName; + $this->data['Attribute']['value'] = $fullFileName ? $fullFileName : $realFileName; $this->data['Attribute']['to_ids'] = 0; } @@ -739,7 +739,7 @@ class Attribute extends AppModel { $destpath = $rootDir . DS . $this->getId(); // id of the new attribute in the database $file = new File ($destpath); $zipfile = new File ($destpath . '.zip'); - $fileInZip = new File($rootDir . DS . $filename); // FIXME do sanitization of the filename + $fileInZip = new File($rootDir . DS . $extraPath . $filename); // FIXME do sanitization of the filename // zip and password protect the malware files if ($malware) { From 67e50cb612e1394a049c314fe950eef3b0282c7f Mon Sep 17 00:00:00 2001 From: noud Date: Wed, 17 Oct 2012 14:45:26 +0200 Subject: [PATCH 27/30] Private Private events are true private and running a server in 2 modes (private and sync), so real private (red) or private to server (amber) or full distributable (green). Mind this needs a change to tables events, attributes and correlation. These are in MYSQL.private.sql. --- app/Config/bootstrap.default.php | 4 + app/Controller/AppController.php | 2 +- app/Controller/AttributesController.php | 77 +++++++++++++++++- app/Controller/EventsController.php | 100 +++++++++++++++++++++--- app/MYSQL.correlation.sql | 7 +- app/MYSQL.private.sql | 5 ++ app/Model/Attribute.php | 60 +++++++++++++- app/Model/Event.php | 57 ++++++++++++++ app/View/Attributes/add.ctp | 12 ++- app/View/Attributes/add_attachment.ctp | 7 +- app/View/Attributes/edit.ctp | 16 ++-- app/View/Attributes/xml/index.ctp | 4 + app/View/Events/add.ctp | 7 +- app/View/Events/edit.ctp | 9 ++- app/View/Events/view.ctp | 8 ++ app/View/Events/xml/index.ctp | 4 + app/View/Events/xml/view.ctp | 9 +++ 17 files changed, 357 insertions(+), 31 deletions(-) create mode 100644 app/MYSQL.private.sql diff --git a/app/Config/bootstrap.default.php b/app/Config/bootstrap.default.php index 946fc8efc..e31a08d49 100644 --- a/app/Config/bootstrap.default.php +++ b/app/Config/bootstrap.default.php @@ -111,6 +111,10 @@ Configure::write('CyDefSIG.logo', 'orgs/MIL.be.png'); // used in Events::ind Configure::write('CyDefSIG.showorg', 'true'); // show the name/flag of the organisation that uploaded the data Configure::write('CyDefSIG.showowner', 'false'); // show the email of the owner that uploaded the data Configure::write('CyDefSIG.sync', 'false'); // enable features related to syncing with other CyDefSIG instances +Configure::write('CyDefSIG.private', 'true'); // respect private to org or server. +if ('true' == Configure::read('CyDefSIG.private')) { + Configure::write('CyDefSIG.sync', 'true'); +} Configure::write('CyDefSIG.email', 'no-reply@sig.mil.be'); // email from for all the mails Configure::write('GnuPG.onlyencrypted', 'true'); // only allow encrypted email, do not allow plaintext mails diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 752dbb8ea..6a43ade53 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -322,7 +322,7 @@ class AppController extends Controller { $this->loadModel('Correlation'); $this->loadModel('Attribute'); - $fields = array('Attribute.id', 'Attribute.event_id', 'Event.date'); + $fields = array('Attribute.id', 'Attribute.event_id', 'Attribute.private', 'Event.date', 'Event.org'); // get all attributes.. $attributes = $this->Attribute->find('all',array('recursive' => 0)); // for all attributes.. diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index 8a9040d15..afd8925d3 100755 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -41,6 +41,31 @@ class AttributesController extends AppController { $this->params->addParams(array('pass' => array($id))); // FIXME find better way to change id variable if uuid is found. params->url and params->here is not modified accordingly now } } + + // do not show private to other groups + if ('true' == Configure::read('CyDefSIG.private')) { + // if not admin or own org, check private as well.. + if (!$this->_IsAdmin()) { + $this->paginate = Set::merge($this->paginate,array( + 'conditions' => + array("OR" => array( + array('Event.org =' => $this->Auth->user('org')), + array("AND" => array('Event.org !=' => $this->Auth->user('org')), array('Event.private !=' => 1), array('Attribute.private !=' => 1)))), + ) + ); + } + } + + // do not show cluster outside server + if ('true' == Configure::read('CyDefSIG.private')) { + if ($this->_isRest()) { + $this->paginate = Set::merge($this->paginate,array( + 'conditions' => + array("AND" => array('Event.cluster !=' => true),array('Attribute.cluster !=' => true)), + //array("AND" => array(array('Event.private !=' => 2))), + )); + } + } } public function isAuthorized($user) { @@ -114,6 +139,9 @@ class AttributesController extends AppController { $this->Attribute->create(); $this->request->data['Attribute']['value'] = $attribute; // set the value as the content of the single line + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Attribute->massageData(&$this->request->data); + } if ($this->Attribute->save($this->request->data)) { $successes .= " " . ($key + 1); } else { @@ -140,6 +168,10 @@ class AttributesController extends AppController { // create the attribute $this->Attribute->create(); + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Attribute->massageData(&$this->request->data); + } + if ($this->Attribute->save($this->request->data)) { // inform the user and redirect $this->Session->setFlash(__('The attribute has been saved')); @@ -162,6 +194,12 @@ class AttributesController extends AppController { $categories = $this->_arrayToValuesIndexArray($categories); $this->set('categories',compact('categories')); + if ('true' == Configure::read('CyDefSIG.private')) { + $sharings = array('Org','Server','All'); + $sharings = $this->_arrayToValuesIndexArray($sharings); + $this->set('sharings',compact('sharings')); + } + $this->set('attrDescriptions', $this->Attribute->fieldDescriptions); $this->set('typeDefinitions', $this->Attribute->typeDefinitions); $this->set('categoryDefinitions', $this->Attribute->categoryDefinitions); @@ -191,10 +229,10 @@ class AttributesController extends AppController { $this->viewClass = 'Media'; $params = array( 'id' => $file->path, - 'name' => $filename, + 'name' => $filename, 'extension' => $fileExt, 'download' => true, - 'path' => DS + 'path' => DS ); $this->set($params); } @@ -241,6 +279,9 @@ class AttributesController extends AppController { } $this->request->data['Attribute']['uuid'] = String::uuid(); $this->request->data['Attribute']['batch_import'] = 0; + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Attribute->massageData(&$this->request->data); + } if ($this->Attribute->save($this->request->data)) { // attribute saved correctly in the db @@ -329,6 +370,12 @@ class AttributesController extends AppController { $this->set('zippedDefinitions', $this->Attribute->zippedDefinitions); $this->set('uploadDefinitions', $this->Attribute->uploadDefinitions); + + if ('true' == Configure::read('CyDefSIG.private')) { + $sharings = array('Org','Server','All'); + $sharings = $this->_arrayToValuesIndexArray($sharings); + $this->set('sharings',compact('sharings')); + } } /** @@ -358,8 +405,12 @@ class AttributesController extends AppController { } if ($this->request->is('post') || $this->request->is('put')) { + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Attribute->massageData(&$this->request->data); + } + // say what fields are to be updated - $fieldList = array('category', 'type', 'value1', 'value2', 'to_ids', 'private'); + $fieldList = array('category', 'type', 'value1', 'value2', 'to_ids', 'private', 'cluster'); if ($this->Attribute->save($this->request->data)) { $this->Session->setFlash(__('The attribute has been saved')); @@ -385,6 +436,12 @@ class AttributesController extends AppController { $categories = $this->_arrayToValuesIndexArray($categories); $this->set('categories',compact('categories')); + if ('true' == Configure::read('CyDefSIG.private')) { + $sharings = array('Org','Server','All'); + $sharings = $this->_arrayToValuesIndexArray($sharings); + $this->set('sharings',compact('sharings')); + } + $this->set('attrDescriptions', $this->Attribute->fieldDescriptions); $this->set('typeDefinitions', $this->Attribute->typeDefinitions); $this->set('categoryDefinitions', $this->Attribute->categoryDefinitions); @@ -492,6 +549,20 @@ class AttributesController extends AppController { $this->paginate = array( 'conditions' => $conditions ); + + if ('true' == Configure::read('CyDefSIG.private')) { + if (!$this->_IsAdmin()) { + // merge in private conditions + $this->paginate = Set::merge($this->paginate,array( + 'conditions' => + array("OR" => array( + array('Event.org =' => $this->Auth->user('org')), + array("AND" => array('Event.org !=' => $this->Auth->user('org')), array('Event.private !=' => 1), array('Attribute.private !=' => 1)))), + ) + ); + } + } + $this->set('attributes', $this->paginate()); // and store into session diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 11316cbe5..cf63a9152 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -64,6 +64,30 @@ class EventsController extends AppController { $this->params->addParams(array('pass' => array($id))); // FIXME find better way to change id variable if uuid is found. params->url and params->here is not modified accordingly now } } + + // do not show private to other groups + if ('true' == Configure::read('CyDefSIG.private')) { + // if not admin or own org, check private as well.. + if (!$this->_IsAdmin()) { + $this->paginate = Set::merge($this->paginate,array( + 'conditions' => + array("OR" => array( + array('Event.org =' => $this->Auth->user('org')), + array("AND" => array('Event.org !=' => $this->Auth->user('org')), array('Event.private !=' => 1)))), + )); + } + } + + // do not show cluster outside server + if ('true' == Configure::read('CyDefSIG.private')) { + if ($this->_isRest()) { + $this->paginate = Set::merge($this->paginate,array( + 'conditions' => + array(array('Event.cluster !=' => true)), + //array("AND" => array(array('Event.private !=' => 2))), + )); + } + } } public function isAuthorized($user) { @@ -110,21 +134,44 @@ class EventsController extends AppController { } $this->Event->read(null, $id); + if ('true' == Configure::read('CyDefSIG.private')) { + if (!$this->_IsAdmin()) { + // check for non-private and re-read + if ($this->Event->data['Event']['org'] != $this->Auth->user('org')) { + $this->Event->hasMany['Attribute']['conditions'] = array('Attribute.private !=' => 1); + $this->Event->read(null, $id); + } + + // check private + if (($this->Event->data['Event']['private']) && ($this->Event->data['Event']['org'] != $this->Auth->user('org'))) { + $this->Session->setFlash('Invalid event.'); + $this->redirect(array('controller' => 'users', 'action' => 'terms')); + } + } + } + $relatedAttributes = array(); $this->loadModel('Attribute'); if ('db' == Configure::read('CyDefSIG.correlation')) { $this->loadModel('Correlation'); $fields = array('Correlation.event_id', 'Correlation.attribute_id', 'Correlation.date'); - $fields2 = array('Correlation.1_attribute_id','Correlation.event_id', 'Correlation.attribute_id', 'Correlation.date'); + $fields2 = array('Correlation.1_attribute_id','Correlation.event_id', 'Correlation.attribute_id', 'Correlation.date', 'Correlation.private', 'Correlation.org'); $relatedAttributes2 = array(); - $relatedAttributes2 = $this->Correlation->find('all',array( - 'fields' => $fields2, - 'conditions' => array( - 'OR' => array( - 'Correlation.1_event_id' => $id - ) - ), - 'recursive' => 0)); + if ('true' == Configure::read('CyDefSIG.private')) { + $conditionsCorrelation = + array('AND' => array('Correlation.1_event_id' => $id,), + array("OR" => array( + array('Correlation.org =' => $this->Event->data['Event']['org']), + array("AND" => array('Correlation.org !=' => $this->Event->data['Event']['org']), array('Correlation.private !=' => 1))))); + } else { + $conditionsCorrelation = + array('AND' => array('Correlation.1_event_id' => $id,)); + } + $relatedAttributes2 = $this->Correlation->find('all',array( + 'fields' => $fields2, + 'conditions' => $conditionsCorrelation, + 'recursive' => 0)); + if (empty($relatedAttributes2)) { $relatedEvents = null; } else { @@ -231,6 +278,12 @@ class EventsController extends AppController { */ public function add() { if ($this->request->is('post')) { + + // TODO or massageData here + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Event->massageData(&$this->request->data); + } + if (!empty($this->data)) { if (isset($this->data['Event']['submittedfile'])) { App::uses('File', 'Utility'); @@ -244,6 +297,7 @@ class EventsController extends AppController { //return false; $this->Session->setFlash('You may only upload GFI Sandbox zip files.'); } else { + // TODO or massageData here if ($this->_add($this->request->data, $this->Auth, $this->_isRest(),'')) { if ($this->_isRest()) { // REST users want to see the newly created event @@ -269,6 +323,12 @@ class EventsController extends AppController { $risks = $this->_arrayToValuesIndexArray($risks); $this->set('risks',compact('risks')); + if ('true' == Configure::read('CyDefSIG.private')) { + $sharings = array('Org','Server','All'); + $sharings = $this->_arrayToValuesIndexArray($sharings); + $this->set('sharings',compact('sharings')); + } + $this->set('eventDescriptions', $this->Event->fieldDescriptions); } @@ -322,9 +382,14 @@ class EventsController extends AppController { } $fieldList = array( - 'Event' => array('org', 'date', 'risk', 'info', 'user_id', 'published', 'uuid', 'private'), - 'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'revision', 'private') + 'Event' => array('org', 'date', 'risk', 'info', 'user_id', 'published', 'uuid', 'private', 'cluster'), + 'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'revision', 'private', 'cluster') ); + + if ('true' == Configure::read('CyDefSIG.private')) { + $data = $this->Event->massageData(&$data); + } + // this saveAssociated() function will save not only the event, but also the attributes // from the attributes attachments are also saved to the disk thanks to the afterSave() fonction of Attribute if ($this->Event->saveAssociated($data, array('validate' => true, 'fieldList' => $fieldList))) { @@ -390,7 +455,7 @@ class EventsController extends AppController { } // say what fields are to be updated - $fieldList = array('date', 'risk', 'info', 'published', 'private'); + $fieldList = array('date', 'risk', 'info', 'published', 'private', 'cluster'); // always force the org, but do not force it for admins if ($this->_isAdmin()) { // set the same org as existed before @@ -400,6 +465,10 @@ class EventsController extends AppController { // we probably also want to remove the published flag $this->request->data['Event']['published'] = 0; + if ('true' == Configure::read('CyDefSIG.private')) { + $this->request->data = $this->Event->massageData(&$this->request->data); + } + if ($this->Event->save($this->request->data, true, $fieldList)) { $this->Session->setFlash(__('The event has been saved')); $this->redirect(array('action' => 'view', $id)); @@ -415,7 +484,14 @@ class EventsController extends AppController { $risks = $this->_arrayToValuesIndexArray($risks); $this->set('risks',compact('risks')); + if ('true' == Configure::read('CyDefSIG.private')) { + $sharings = array('Org', 'Server', 'All'); + $sharings = $this->_arrayToValuesIndexArray($sharings); + $this->set('sharings', compact('sharings')); + } + $this->set('eventDescriptions', $this->Event->fieldDescriptions); + $this->set('privateDefinitions', $this->Event->privateDefinitions); } /** diff --git a/app/MYSQL.correlation.sql b/app/MYSQL.correlation.sql index cb5e42243..355bf8cbf 100755 --- a/app/MYSQL.correlation.sql +++ b/app/MYSQL.correlation.sql @@ -5,6 +5,11 @@ CREATE TABLE `correlations` ( `1_attribute_id` int(11) NOT NULL, `event_id` int(11) NOT NULL, `attribute_id` int(11) NOT NULL, - `date` date NOT NULL, + `org` varchar(255) COLLATE utf8_bin NOT NULL, + `private` tinyint(1) NOT NULL, + `date` date NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=118 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- ALTER TABLE `correlations` ADD private tinyint(1) NOT NULL; +-- ALTER TABLE `correlations` ADD org varchar(255) COLLATE utf8_bin NOT NULL; \ No newline at end of file diff --git a/app/MYSQL.private.sql b/app/MYSQL.private.sql new file mode 100644 index 000000000..a130d0207 --- /dev/null +++ b/app/MYSQL.private.sql @@ -0,0 +1,5 @@ +ALTER TABLE `events` ADD `cluster` tinyint(1) NOT NULL; +ALTER TABLE `attributes` ADD `cluster` tinyint(1) NOT NULL; + +ALTER TABLE `correlations` ADD private tinyint(1) NOT NULL; +ALTER TABLE `correlations` ADD org varchar(255) COLLATE utf8_bin NOT NULL; \ No newline at end of file diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 1ca089700..82dbf637b 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -273,6 +273,42 @@ class Attribute extends AppModel { ), ); + public function __construct($id = false, $table = null, $ds = null) { + parent::__construct($id, $table, $ds); + + if ('true' == Configure::read('CyDefSIG.private')) { + + $this->virtualFields = Set::merge($this->virtualFields,array( + 'sharing' => 'IF (Attribute.private=true, "Org", IF (Attribute.cluster=true, "Server", "All"))', + )); + + $this->fieldDescriptions = Set::merge($this->fieldDescriptions,array( + 'sharing' => array('desc' => 'This field tells how and if the attribute should be shared with other CyDefSIG users'), + )); + + $this->validate = Set::merge($this->validate,array( + 'cluster' => array( + 'boolean' => array( + 'rule' => array('boolean'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + 'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'sharing' => array( + 'rule' => array('inList', array('Org','Server','All')), + //'message' => 'Your custom message here', + 'allowEmpty' => false, + 'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + )); + } + } + //The Associations below have been created with all possible keys, those that are not needed can be removed /** @@ -361,6 +397,24 @@ class Attribute extends AppModel { } } + public function massageData(&$data) { + switch ($data['Attribute']['sharing']) { + case 'Org': + $data['Attribute']['private'] = true; + $data['Attribute']['cluster'] = false; + break; + case 'Server': + $data['Attribute']['private'] = false; + $data['Attribute']['cluster'] = true; + break; + case 'All': + $data['Attribute']['private'] = false; + $data['Attribute']['cluster'] = false; + break; + } + return $data; + } + public function beforeValidate() { // remove leading and trailing blanks $this->data['Attribute']['value'] = trim($this->data['Attribute']['value']); @@ -761,7 +815,7 @@ class Attribute extends AppModel { private function __afterSaveCorrelation($attribute) { $this->__beforeDeleteCorrelation($attribute); // re-add - $this->setRelatedAttributes($attribute, array('Attribute.id', 'Attribute.event_id', 'Event.date')); + $this->setRelatedAttributes($attribute, array('Attribute.id', 'Attribute.event_id', 'Attribute.private', 'Event.date', 'Event.org')); } private function __beforeDeleteCorrelation($attribute) { @@ -817,7 +871,7 @@ class Attribute extends AppModel { $params = array( 'conditions' => array('Event.id' => $relatedAttribute['Attribute']['event_id']), 'recursive' => 0, - 'fields' => array('Event.date') + 'fields' => array('Event.date', 'Event.org') ); $eventDate = $this->Event->find('first', $params); $this->Correlation = ClassRegistry::init('Correlation'); @@ -826,6 +880,8 @@ class Attribute extends AppModel { 'Correlation' => array( '1_event_id' => $attribute['event_id'], '1_attribute_id' => $attribute['id'], 'event_id' => $relatedAttribute['Attribute']['event_id'], 'attribute_id' => $relatedAttribute['Attribute']['id'], + 'org' => $eventDate['Event']['org'], + 'private' => $relatedAttribute['Attribute']['private'], 'date' => $eventDate['Event']['date'])) ); } diff --git a/app/Model/Event.php b/app/Model/Event.php index 94248c946..3dddf13df 100644 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -23,6 +23,8 @@ class Event extends AppModel { */ public $displayField = 'id'; + public $virtualFields = array(); + /** * Description field * @@ -139,6 +141,42 @@ class Event extends AppModel { //), ); + public function __construct($id = false, $table = null, $ds = null) { + parent::__construct($id, $table, $ds); + + if ('true' == Configure::read('CyDefSIG.private')) { + + $this->virtualFields = Set::merge($this->virtualFields,array( + 'sharing' => 'IF (Event.private=true, "Org", IF (Event.cluster=true, "Server", "All"))', + )); + + $this->fieldDescriptions = Set::merge($this->fieldDescriptions,array( + 'sharing' => array('desc' => 'This field tells how and if the event should be shared with other CyDefSIG users'), + )); + + $this->validate = Set::merge($this->validate,array( + 'cluster' => array( + 'boolean' => array( + 'rule' => array('boolean'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + 'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'sharing' => array( + 'rule' => array('inList', array('Org','Server')), + //'message' => 'Your custom message here', + 'allowEmpty' => false, + 'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + )); + } + } + //The Associations below have been created with all possible keys, those that are not needed can be removed /** @@ -212,12 +250,31 @@ class Event extends AppModel { } public function beforeValidate() { + parent::beforeValidate(); // generate UUID if it doesn't exist if (empty($this->data['Event']['uuid'])) { $this->data['Event']['uuid'] = String::uuid(); } } + public function massageData(&$data) { + switch ($data['Event']['sharing']) { + case 'Org': + $data['Event']['private'] = true; + $data['Event']['cluster'] = false; + break; + case 'Server': + $data['Event']['private'] = false; + $data['Event']['cluster'] = true; + break; + case 'All': + $data['Event']['private'] = false; + $data['Event']['cluster'] = false; + break; + } + return $data; + } + public function isOwnedByOrg($eventid, $org) { return $this->field('id', array('id' => $eventid, 'org' => $org)) === $eventid; } diff --git a/app/View/Attributes/add.ctp b/app/View/Attributes/add.ctp index 8ac677f89..782093bec 100755 --- a/app/View/Attributes/add.ctp +++ b/app/View/Attributes/add.ctp @@ -14,9 +14,15 @@ echo $this->Form->input('type', array( 'empty' => '(first choose category)' )); if ('true' == Configure::read('CyDefSIG.sync')) { - echo $this->Form->input('private', array( - 'before' => $this->Html->div('forminfo', isset($attrDescriptions['private']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']), - )); + if ('true' == Configure::read('CyDefSIG.private')) { + echo $this->Form->input('sharing', array('label' => 'Private', + 'before' => $this->Html->div('forminfo', isset($attrDescriptions['sharing']['formdesc']) ? $attrDescriptions['sharing']['formdesc'] : $attrDescriptions['sharing']['desc']), + )); + } else { + echo $this->Form->input('private', array( + 'before' => $this->Html->div('forminfo', isset($attrDescriptions['private']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']), + )); + } } echo $this->Form->input('to_ids', array( 'checked' => true, diff --git a/app/View/Attributes/add_attachment.ctp b/app/View/Attributes/add_attachment.ctp index affc7a2bc..e729e4dbd 100755 --- a/app/View/Attributes/add_attachment.ctp +++ b/app/View/Attributes/add_attachment.ctp @@ -14,8 +14,13 @@ echo $this->Form->input('malware', array( 'after' => '
    Tick this box to neutralize the sample. Every malware sample will be zipped with the password "infected"', )); if ('true' == Configure::read('CyDefSIG.sync')) { - echo $this->Form->input('private', array( + if ('true' == Configure::read('CyDefSIG.private')) { + echo $this->Form->input('sharing', array('label' => 'Private', + 'before' => $this->Html->div('forminfo', isset($attrDescriptions['sharing']['formdesc']) ? $attrDescriptions['sharing']['formdesc'] : $attrDescriptions['sharing']['desc']),)); + } else { + echo $this->Form->input('private', array( 'before' => $this->Html->div('forminfo', isset($attrDescriptions['private']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']),)); + } } // link an onchange event to the form elements $this->Js->get('#AttributeType')->event('change', 'showFormInfo("#AttributeType")'); diff --git a/app/View/Attributes/edit.ctp b/app/View/Attributes/edit.ctp index 8a3d1641f..a0183c93c 100755 --- a/app/View/Attributes/edit.ctp +++ b/app/View/Attributes/edit.ctp @@ -16,9 +16,15 @@ if ($attachment) { echo $this->Form->input('type', array('between' => $this->Html->div('forminfo', '', array('id' => 'AttributeTypeDiv')))); } if ('true' == Configure::read('CyDefSIG.sync')) { - echo $this->Form->input('private', array( - 'before' => $this->Html->div('forminfo', isset($attrDescriptions['private']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']), - )); + if ('true' == Configure::read('CyDefSIG.private')) { + echo $this->Form->input('sharing', array('label' => 'Private', + 'before' => $this->Html->div('forminfo', isset($attrDescriptions['sharing']['formdesc']) ? $attrDescriptions['sharing']['formdesc'] : $attrDescriptions['sharing']['desc']), + )); + } else { + echo $this->Form->input('private', array( + 'before' => $this->Html->div('forminfo', isset($attrDescriptions['private']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']), + )); + } } echo $this->Form->input('to_ids', array( 'before' => $this->Html->div('forminfo', isset($attrDescriptions['signature']['formdesc']) ? $attrDescriptions['private']['formdesc'] : $attrDescriptions['private']['desc']), @@ -102,8 +108,8 @@ function showFormInfo(id) { // LATER use nice animations //$(idDiv).hide('fast'); // change the content - var value = $(id).val(); // get the selected value - $(idDiv).html(formInfoValues[value]); // search in a lookup table + var value = $(id).val(); // get the selected value + $(idDiv).html(formInfoValues[value]); // search in a lookup table // show it again $(idDiv).fadeIn('slow'); diff --git a/app/View/Attributes/xml/index.ctp b/app/View/Attributes/xml/index.ctp index 249d8d9aa..257402f4c 100755 --- a/app/View/Attributes/xml/index.ctp +++ b/app/View/Attributes/xml/index.ctp @@ -10,6 +10,10 @@ foreach ($attributes as $key => $attribute) { if ('true' != Configure::read('CyDefSIG.sync')) { unset($attributes[$key]['private']); } + if ('true' == Configure::read('CyDefSIG.private')) { + unset($attributes[$key]['sharing']); + unset($attributes[$key]['cluster']); + } } diff --git a/app/View/Events/add.ctp b/app/View/Events/add.ctp index f64f13402..2f416cc38 100755 --- a/app/View/Events/add.ctp +++ b/app/View/Events/add.ctp @@ -5,8 +5,13 @@ Form->input('date'); if ('true' == Configure::read('CyDefSIG.sync')) { - echo $this->Form->input('private', array( + if ('true' == Configure::read('CyDefSIG.private')) { + echo $this->Form->input('sharing', array('label' => 'Private', + 'before' => $this->Html->div('forminfo', isset($eventDescriptions['sharing']['formdesc']) ? $eventDescriptions['sharing']['formdesc'] : $eventDescriptions['sharing']['desc']),)); + } else { + echo $this->Form->input('private', array( 'before' => $this->Html->div('forminfo', isset($eventDescriptions['private']['formdesc']) ? $eventDescriptions['private']['formdesc'] : $eventDescriptions['private']['desc']),)); + } } echo $this->Form->input('risk', array( 'before' => $this->Html->div('forminfo', isset($eventDescriptions['risk']['formdesc']) ? $eventDescriptions['risk']['formdesc'] : $eventDescriptions['risk']['desc']))); diff --git a/app/View/Events/edit.ctp b/app/View/Events/edit.ctp index ff4654001..ebe34e7d8 100755 --- a/app/View/Events/edit.ctp +++ b/app/View/Events/edit.ctp @@ -8,8 +8,13 @@ echo $this->Form->input('date'); echo $this->Form->input('risk', array( 'before' => $this->Html->div('forminfo', isset($eventDescriptions['risk']['formdesc']) ? $eventDescriptions['risk']['formdesc'] : $eventDescriptions['risk']['desc']))); if ('true' == Configure::read('CyDefSIG.sync')) { - echo $this->Form->input('private', array( - 'before' => $this->Html->div('forminfo', isset($eventDescriptions['private']['formdesc']) ? $eventDescriptions['private']['formdesc'] : $eventDescriptions['private']['desc']),)); + if ('true' == Configure::read('CyDefSIG.private')) { + echo $this->Form->input('sharing', array('label' => 'Private', + 'before' => $this->Html->div('forminfo', isset($eventDescriptions['sharing']['formdesc']) ? $eventDescriptions['sharing']['formdesc'] : $eventDescriptions['sharing']['desc']),)); + } else { + echo $this->Form->input('private', array( + 'before' => $this->Html->div('forminfo', isset($eventDescriptions['private']['formdesc']) ? $eventDescriptions['private']['formdesc'] : $eventDescriptions['private']['desc']),)); + } } echo $this->Form->input('info'); ?> diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index d0ba0bca5..476e1590c 100755 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -63,12 +63,20 @@ if ($mayPublish) {   + +
    Private
    +
    + +   +
    +
    Private
     
    +
    Paginator->sort('id');?> Paginator->sort('org');?>Paginator->sort('group_id');?> Paginator->sort('email');?> Paginator->sort('autoalert');?> Paginator->sort('gpgkey');?>   + Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?>     Html->link(__('View'), array('admin' => true, 'action' => 'view', $user['User']['id'])); ?> - Html->link(__('Edit'), array('admin' => true, 'action' => 'edit', $user['User']['id'])); ?> - Form->postLink(__('Delete'), array('admin' => true, 'action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete # %s?', $user['User']['id'])); ?> + Html->link(__('Edit'), array('admin' => true, 'action' => 'edit', $user['User']['id']), $isAclModify||($user['User']['org'] == $me['org']) ? null:array('id' => $button_modify_status.$buttonCounter++, 'class' => $button_modify_status)); ?> + Form->postLink(__('Delete'), array('admin' => true, 'action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete # %s?', $user['User']['id'])); + else echo $this->Html->link(__('Delete'), array('admin' => true, 'action' => 'delete', $user['User']['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + ?>
    Html->link(__('View'), array('controller' => 'events', 'action' => 'view', $event['id'])); ?> - Html->link(__('Edit'), array('controller' => 'events', 'action' => 'edit', $event['id'])); ?> - Form->postLink(__('Delete'), array('controller' => 'events', 'action' => 'delete', $event['id']), null, __('Are you sure you want to delete # %s?', $event['id'])); ?> + Html->link(__('Edit'), array('controller' => 'events', 'action' => 'edit', $event['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); ?> + Form->postLink(__('Delete'), array('controller' => 'events', 'action' => 'delete', $event['id']), null, __('Are you sure you want to delete # %s?', $event['id'])); + else echo $this->Html->link(__('Delete'), array('controller' => 'events', 'action' => 'delete', $event['id']), array('id' => $button_modify_status.$buttonCounter++,'class' => $button_modify_status)); + ?>