\n";
+
+ $this->set('rules', $rules);
+
+ }
+
+
+ public function text($key, $type="") {
+ // check if the key is valid -> search for users based on key
+ $this->loadModel('User');
+ // no input sanitization necessary, it's done by model
+ $user = $this->User->findByAuthkey($key);
+ if (empty($user)) {
+ throw new UnauthorizedException('Incorrect authentication key');
+ }
+
+ $this->header('Content-Type: text/plain'); // set the content type
+ $this->layout = 'text/default';
+
+ $this->loadModel('Signature');
+ $params = array(
+ 'conditions' => array('Signature.type' => $type), //array of conditions
+ 'recursive' => 0, //int
+ 'fields' => array('Signature.value'), //array of field names
+ 'order' => array('Signature.value'), //string or array defining order
+ 'group' => array('Signature.value'), //fields to GROUP BY
+ );
+ $signatures = $this->Signature->find('all', $params);
+
+ $this->set('signatures', $signatures);
+ }
+
+
+ /**
+ * // LATER move _dnsNameToRawFormat($name) function to a better place
+ * Converts a DNS name to a raw format usable in NIDS like Snort.
+ * example: foobar.com becomes |06|foobar|03|com|00|
+ * @param string $name dns name to be converted
+ * @return string raw snort compatible format of the dns name
+ */
+ private function _dnsNameToRawFormat($name) {
+ $rawName = "";
+ // explode using the dot
+ $explodedNames = explode('.', $name);
+ // for each part
+ foreach ($explodedNames as $explodedName) {
+ // count the lenght of the part, and add |length| before
+ $length = strlen($explodedName);
+ if ($length > 255) exit('ERROR: dns name is to long for RFC'); // LATER log correctly without dying
+ $hexLength = dechex($length);
+ if (1 == strlen($hexLength)) $hexLength = '0'.$hexLength;
+ $rawName .= '|'.$hexLength.'|'.$explodedName;
+ }
+ // put all together
+ $rawName .= '|00|';
+ // and append |00| to terminate the name
+ return $rawName;
+ }
+
+
+
+}
diff --git a/app/Controller/PagesController.php b/app/Controller/PagesController.php
new file mode 100644
index 000000000..194ac4299
--- /dev/null
+++ b/app/Controller/PagesController.php
@@ -0,0 +1,82 @@
+redirect('/');
+ }
+ $page = $subpage = $title_for_layout = null;
+
+ if (!empty($path[0])) {
+ $page = $path[0];
+ }
+ if (!empty($path[1])) {
+ $subpage = $path[1];
+ }
+ if (!empty($path[$count - 1])) {
+ $title_for_layout = Inflector::humanize($path[$count - 1]);
+ }
+ $this->set(compact('page', 'subpage', 'title_for_layout'));
+ $this->render(implode('/', $path));
+ }
+}
diff --git a/app/Controller/SignaturesController.php b/app/Controller/SignaturesController.php
new file mode 100644
index 000000000..53facdb91
--- /dev/null
+++ b/app/Controller/SignaturesController.php
@@ -0,0 +1,189 @@
+set('me', $this->Auth->user());
+ $this->set('isAdmin', $this->_isAdmin());
+ }
+
+
+/**
+ * index method
+ *
+ * @return void
+ */
+ public function index() {
+ $this->Signature->recursive = 0;
+ $this->set('signatures', $this->paginate());
+ }
+
+/**
+ * add method
+ *
+ * @return void
+ */
+ public function add($event_id = null) {
+ if ($this->request->is('post')) {
+ $this->loadModel('Event');
+ // only own signatures
+ $this->Event->recursive = 0;
+ $event = $this->Event->findById($this->request->data['Signature']['event_id']);
+ if (!$this->_isAdmin() && $this->Auth->user('org') != $event['Event']['org']) {
+ throw new UnauthorizedException('You can only add signatures for your own organisation.');
+ }
+
+ // remove the alerted flag from the event
+ $this->Event->id = $this->request->data['Signature']['event_id'];
+ $this->Event->saveField('alerted', 0);
+
+ //
+ // multiple signatures in batch import
+ //
+ if ($this->data['Signature']['batch_import'] == 1) {
+ // make array from value field
+ $signatures = explode("\n", $this->request->data['Signature']['value']);
+
+ $fails = ""; // will be used to keep a list of the lines that failed or succeeded
+ $successes = "";
+ foreach ($signatures as $key => $signature) {
+ $signature = trim($signature);
+ if (strlen($signature) == 0 )
+ continue; // don't do anything for empty lines
+
+ $this->Signature->create();
+ $this->request->data['Signature']['value'] = $signature; // set the value as the content of the single line
+
+ if ($this->Signature->save($this->request->data)) {
+ $successes .= " ".($key+1);
+ } else {
+ $fails .= " ".($key+1);
+ }
+
+ }
+ // we added all the signatures,
+ if ($fails) {
+ // list the ones that failed
+ $this->Session->setFlash(__('The lines'.$fails.' could not be saved. Please, try again.', true), 'default', array(), 'error');
+ }
+ if ($successes) {
+ // list the ones that succeeded
+ $this->Session->setFlash(__('The lines'.$successes.' have been saved', true));
+ }
+
+ $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id']));
+
+ }
+
+ else {
+ //
+ // single signature
+ //
+ // create the signature
+ $this->Signature->create();
+ if ($this->Signature->save($this->request->data)) {
+ // inform the user and redirect
+ $this->Session->setFlash(__('The signature has been saved'));
+ $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id']));
+ } else {
+ $this->Session->setFlash(__('The signature could not be saved. Please, try again.'));
+ }
+ }
+ } else {
+ // set the event_id in the form
+ $this->request->data['Signature']['event_id'] = $event_id;
+ }
+
+ // combobox for types
+ $types = $this->Signature->validate['type']['rule'][1];
+ $types = $this->_arrayToValuesIndexArray($types);
+ $this->set('types',compact('types'));
+ }
+
+/**
+ * edit method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function edit($id = null) {
+ $this->Signature->id = $id;
+ if (!$this->Signature->exists()) {
+ throw new NotFoundException(__('Invalid signature'));
+ }
+ // only own signatures
+ $this->Signature->read();
+ if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Signature->data['Event']['org']) {
+ throw new UnauthorizedException('You can only edit signatures from your own organisation.');
+ }
+ if ($this->request->is('post') || $this->request->is('put')) {
+
+ if ($this->Signature->save($this->request->data)) {
+ $this->Session->setFlash(__('The signature has been saved'));
+ $this->redirect(array('action' => 'index'));
+ } else {
+ $this->Session->setFlash(__('The signature could not be saved. Please, try again.'));
+ }
+ } else {
+ $this->request->data = $this->Signature->read(null, $id);
+ }
+
+ }
+
+/**
+ * delete method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function delete($id = null) {
+ if (!$this->request->is('post')) {
+ throw new MethodNotAllowedException();
+ }
+ $this->Signature->id = $id;
+ if (!$this->Signature->exists()) {
+ throw new NotFoundException(__('Invalid signature'));
+ }
+
+ // only own signatures
+ $this->Signature->read();
+ if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Signature->data['Event']['org']) {
+ throw new UnauthorizedException('You can only delete signatures from your own organisation.');
+ }
+
+ if ($this->Signature->delete()) {
+ $this->Session->setFlash(__('Signature deleted'));
+ } else {
+ $this->Session->setFlash(__('Signature was not deleted'));
+ }
+
+ $this->redirect($this->referer());
+ }
+
+
+
+ public function search() {
+ if ($this->request->is('post')) {
+ $keyword = $this->request->data['Signature']['keyword'];
+
+ // search the db
+ $this->Signature->recursive = 0;
+ $this->paginate = array(
+ 'conditions' => array('Signature.value LIKE' => '%'.$keyword.'%'),
+ );
+ $this->set('signatures', $this->paginate());
+
+ // set the same view as the index page
+ $this->render('index');
+ }
+ }
+
+}
diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php
new file mode 100644
index 000000000..e811aaa2f
--- /dev/null
+++ b/app/Controller/UsersController.php
@@ -0,0 +1,309 @@
+Auth->allow('login', 'logout');
+
+ // These variables are required for every view
+ $this->set('me', $this->Auth->user());
+ $this->set('isAdmin', $this->_isAdmin());
+ }
+
+
+/**
+ * view method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function view($id = null) {
+ if ("me" == $id) $id = $this->Auth->user('id');
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ // Only own profile
+ if ($this->Auth->user('id') != $id) {
+ throw new ForbiddenException('You are not authorized to access this profile.');
+ }
+ $this->set('user', $this->User->read(null, $id));
+ }
+
+
+/**
+ * edit method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function edit($id = null) {
+ if ("me" == $id) $id = $this->Auth->user('id');
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ // Only own profile
+ if ($this->Auth->user('id') != $id) {
+ throw new ForbiddenException('You are not authorized to edit this profile.');
+ }
+ 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' );
+ if ("" != $this->data['User']['password'])
+ $fieldList[] = 'password';
+ // Save the data
+ if ($this->User->save($this->request->data, true ,$fieldList)) {
+ $this->Session->setFlash(__('The profile has been updated'));
+ $this->redirect(array('action' => 'view', $id));
+ } else {
+ $this->Session->setFlash(__('The profile could not be updated. Please, try again.'));
+ }
+ } else {
+ $this->User->recursive=0;
+ $this->User->read(null, $id);
+ $this->User->set('password', '');
+ $this->request->data = $this->User->data;
+ }
+ $this->request->data['User']['org']=$this->Auth->user('org');
+ }
+
+/**
+ * delete method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function delete($id = null) {
+ if ("me" == $id) $id = $this->Auth->user('id');
+ if (!$this->request->is('post')) {
+ throw new MethodNotAllowedException();
+ }
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ // Only own profile
+ if ($this->Auth->user('id') != $id) {
+ throw new ForbiddenException('You are not authorized to delete this profile.');
+ }
+ if ($this->User->delete()) {
+ $this->Session->setFlash(__('User deleted'));
+ $this->redirect(array('action' => 'index'));
+ }
+ $this->Session->setFlash(__('User was not deleted'));
+ $this->redirect(array('action' => 'index'));
+ }
+/**
+ * admin_index method
+ *
+ * @return void
+ */
+ public function admin_index() {
+ $this->User->recursive = 0;
+ $this->set('users', $this->paginate());
+ }
+
+/**
+ * admin_view method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function admin_view($id = null) {
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ $this->set('user', $this->User->read(null, $id));
+ }
+
+/**
+ * admin_add method
+ *
+ * @return void
+ */
+ public function admin_add() {
+ if ($this->request->is('post')) {
+ $this->User->create();
+ if ($this->User->save($this->request->data)) {
+ $this->Session->setFlash(__('The user has been saved'));
+ $this->redirect(array('action' => 'index'));
+ } else {
+ $this->Session->setFlash(__('The user could not be saved. Please, try again.'));
+ }
+ }
+ }
+
+/**
+ * admin_edit method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function admin_edit($id = null) {
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ if ($this->request->is('post') || $this->request->is('put')) {
+ if ($this->User->save($this->request->data)) {
+ $this->Session->setFlash(__('The user has been saved'));
+ $this->redirect(array('action' => 'index'));
+ } else {
+ $this->Session->setFlash(__('The user could not be saved. Please, try again.'));
+ }
+ } else {
+ $this->request->data = $this->User->read(null, $id);
+ }
+ }
+
+/**
+ * admin_delete method
+ *
+ * @param string $id
+ * @return void
+ */
+ public function admin_delete($id = null) {
+ if (!$this->request->is('post')) {
+ throw new MethodNotAllowedException();
+ }
+ $this->User->id = $id;
+ if (!$this->User->exists()) {
+ throw new NotFoundException(__('Invalid user'));
+ }
+ if ($this->User->delete()) {
+ $this->Session->setFlash(__('User deleted'));
+ $this->redirect(array('action' => 'index'));
+ }
+ $this->Session->setFlash(__('User was not deleted'));
+ $this->redirect(array('action' => 'index'));
+ }
+
+
+ public function login() {
+ // FIXME implement authentication brute-force protection
+ if ($this->Auth->login()) {
+ $this->redirect($this->Auth->redirect());
+ } else {
+ $this->Session->setFlash(__('Invalid username or password, try again'));
+ }
+ }
+
+ public function routeafterlogin() {
+ // Terms and Conditions Page
+ if (!$this->Auth->user('termsaccepted')) {
+ $this->redirect(array('action' => 'terms'));
+ }
+
+ // News page
+ $new_newsdate = new DateTime("2012-03-15");
+ $newsdate = new DateTime($this->Auth->user('newsread'));
+ if ($new_newsdate > $newsdate) {
+ $this->redirect(array('action' => 'news'));
+ }
+
+ // Events list
+ $this->redirect(array('controller' => 'events', 'action' => 'index'));
+ }
+
+ public function logout() {
+ $this->Session->setFlash('Good-Bye');
+ $this->redirect($this->Auth->logout());
+ }
+
+
+ public function resetauthkey($id = null) {
+ if (!$id) {
+ $this->Session->setFlash(__('Invalid id for user', true), 'default', array(), 'error');
+ $this->redirect(array('action'=>'index'));
+ }
+ if ('me' == $id ) $id = $this->Auth->user('id');
+
+ // only allow reset key for own account, except for admins
+ if (!$this->_isAdmin() && $id != $this->Auth->user('id')) {
+ throw new ForbiddenException('Not authorized to reset the key for this user');
+ }
+
+ // reset the key
+ $this->User->id = $id;
+ $newkey = $this->User->generateAuthKey();
+ $this->User->saveField('authkey', $newkey);
+ $this->Session->setFlash(__('New authkey generated.', true));
+ $this->redirect($this->referer());
+ }
+
+ public function memberslist() {
+ $this->loadModel('Signature');
+ $this->loadModel('Event');
+
+ // Orglist
+ $fields = array('User.org', 'count(User.id) as `num_members`');
+ $params = array('recursive' => 0,
+ 'fields' => $fields,
+ 'group' => array('User.org'),
+ 'order' => array('User.org'),
+ );
+ $orgs = $this->User->find('all', $params);
+ $this->set('orgs', $orgs);
+
+ // $fields = array('User.org', 'count(User.id) as `num_members`', 'count(Event.id) as `num_events`');
+ // $params = array('recursive' => 0,
+ // 'fields' => $fields,
+ // 'group' => array('User.org'),
+ // 'order' => array('User.org'),
+ // );
+ // $orgs = $this->Event->find('all', $params);
+ // $this->set('orgs', $orgs);
+
+
+
+
+ // What org posted what type of signature
+ // LATER beautify types_histogram
+ $this->loadModel('Signature');
+ $fields = array('Event.org', 'Signature.type', 'count(Signature.type) as `num_types`');
+ $params = array('recursive' => 0,
+ 'fields' => $fields,
+ 'group' => array('Signature.type', 'Event.org'),
+ 'order' => array('Event.org', 'num_types DESC'),
+ );
+ $types_histogram = $this->Signature->find('all', $params);
+ $this->set('types_histogram', $types_histogram);
+
+
+ }
+
+ public function terms() {
+ if ($this->request->is('post') || $this->request->is('put')) {
+ $this->User->id = $this->Auth->user('id');
+ $this->User->saveField('termsaccepted', true);
+
+ $this->_refreshAuth(); // refresh auth info
+ $this->Session->setFlash(__('You accepted the Terms and Conditions.'));
+ $this->redirect(array('action' => 'routeafterlogin'));
+ }
+ $this->set('termsaccepted', $this->Auth->user('termsaccepted'));
+ }
+
+ public function news() {
+ $this->User->id = $this->Auth->user('id');
+ $this->User->saveField('newsread', date("Y-m-d"));
+ $this->_refreshAuth(); // refresh auth info
+ }
+
+
+
+
+}
diff --git a/app/models/behaviors/empty b/app/Lib/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/models/behaviors/empty
rename to app/Lib/empty
diff --git a/app/models/datasources/empty b/app/Locale/eng/LC_MESSAGES/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/models/datasources/empty
rename to app/Locale/eng/LC_MESSAGES/empty
diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php
new file mode 100644
index 000000000..4c9b8bde7
--- /dev/null
+++ b/app/Model/AppModel.php
@@ -0,0 +1,34 @@
+ 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
+ ),
+ ),
+ 'date' => array(
+ 'date' => array(
+ 'rule' => array('date'),
+ //'message' => 'Your custom message here',
+ //'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ ),
+ 'risk' => array(
+ 'rule' => array('inList', array('Undefined', 'Low','Medium','High')),
+ 'message' => 'Options : Undefined, Low, Medium, High',
+ //'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ 'info' => 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
+ ),
+ ),
+ '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
+ ),
+ ),
+ 'alerted' => 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
+ ),
+ ),
+ 'uuid' => array(
+ 'uuid' => array(
+ 'rule' => array('uuid'),
+ //'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
+
+/**
+ * belongsTo associations
+ *
+ * @var array
+ */
+ public $belongsTo = array(
+ 'User' => array(
+ 'className' => 'User',
+ 'foreignKey' => 'user_id',
+ 'conditions' => '',
+ 'fields' => '',
+ 'order' => ''
+ )
+ );
+
+/**
+ * hasMany associations
+ *
+ * @var array
+ */
+ public $hasMany = array(
+ 'Signature' => array(
+ 'className' => 'Signature',
+ 'foreignKey' => 'event_id',
+ 'dependent' => true, // cascade deletes
+ 'conditions' => '',
+ 'fields' => '',
+ 'order' => 'Signature.type ASC',
+ 'limit' => '',
+ 'offset' => '',
+ 'exclusive' => '',
+ 'finderQuery' => '',
+ 'counterQuery' => ''
+ )
+ );
+
+ function getRelatedEvents() {
+ // first get a list of related event_ids
+ // then do a single query to search for all the events with that id
+ $relatedEventIds = Array();
+ foreach ($this->data['Signature'] as $signature ) {
+ if ($signature['type'] == 'other')
+ continue; // sigs of type 'other' should not be matched against the others
+ $conditions = array('Signature.value =' => $signature['value'], 'Signature.type =' => $signature['type']);
+ $similar_signatures = $this->Signature->find('all',array('conditions' => $conditions));
+ foreach ($similar_signatures as $similar_signature) {
+ if ($this->id == $similar_signature['Signature']['event_id'])
+ continue; // same as this event, not needed in the list
+ $relatedEventIds[] = $similar_signature['Signature']['event_id'];
+ }
+ }
+ $conditions = array("Event.id" => $relatedEventIds);
+ $relatedEvents= $this->find('all',
+ array('conditions' => $conditions,
+ 'recursive' => 0,
+ 'order' => 'Event.date DESC',
+ 'fields' => 'Event.*'
+ )
+ );
+ return $relatedEvents;
+ }
+}
diff --git a/app/Model/Signature.php b/app/Model/Signature.php
new file mode 100644
index 000000000..06f926ad4
--- /dev/null
+++ b/app/Model/Signature.php
@@ -0,0 +1,249 @@
+ "DESC", "Signature.type" => "ASC");
+/**
+ * Validation rules
+ *
+ * @var array
+ */
+ public $validate = array(
+ 'event_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
+ ),
+ ),
+ 'type' => array(
+ 'rule' => array('inList', array('md5','sha1',
+ 'filename',
+ 'ip-src',
+ 'ip-dst',
+ 'domain',
+ 'email-src',
+ 'email-dst',
+ 'email-subject',
+ 'email-attachment',
+ 'url',
+ 'user-agent',
+ 'regkey',
+ 'AS',
+ 'snort',
+ 'pattern-in-file',
+ 'other')),
+ 'message' => 'Options : md5, sha1, filename, ip, domain, email, url, regkey, AS, other, ...',
+ //'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+
+ ),
+ 'value' => array(
+ 'notempty' => array(
+ 'rule' => array('notempty'),
+ 'message' => 'Please fill in this field',
+ //'allowEmpty' => false,
+ //'required' => false,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ 'userdefined' => array(
+ 'rule' => array('validateSignatureValue'),
+ 'message' => 'Value not in the right type/format. Please double check the value or select "other" for a type.',
+ //'allowEmpty' => false,
+ //'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ ),
+ 'to_ids' => 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
+ ),
+ ),
+ 'uuid' => array(
+ 'uuid' => array(
+ 'rule' => array('uuid'),
+ //'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
+
+/**
+ * belongsTo associations
+ *
+ * @var array
+ */
+ public $belongsTo = array(
+ 'Event' => array(
+ 'className' => 'Event',
+ 'foreignKey' => 'event_id',
+ 'conditions' => '',
+ 'fields' => '',
+ 'order' => ''
+ )
+ );
+
+ function validateSignatureValue ($fields) {
+ $value = $fields['value'];
+ $event_id = $this->data['Signature']['event_id'];
+ $type = $this->data['Signature']['type'];
+
+ // check if the signature already exists in the same event
+ $params = array('recursive' => 0,
+ 'conditions' => array('Signature.event_id' => $event_id,
+ 'Signature.type' => $type,
+ 'Signature.value' => $value),
+ );
+ if (0 != $this->find('count', $params) )
+ return 'Signature already exists for this event.';
+
+
+ // check data validation
+ switch($this->data['Signature']['type']) {
+ case 'md5':
+ if (preg_match("#^[0-9a-f]{32}$#i", $value))
+ return true;
+ return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.';
+ break;
+ case 'sha1':
+ if (preg_match("#^[0-9a-f]{40}$#i", $value))
+ return true;
+ return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.';
+ break;
+ case 'filename':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'ip-src':
+ $parts = explode("/", $value);
+ // [0] = the ip
+ // [1] = the network address
+ if (count($parts) <= 2 ) {
+ // ipv4 and ipv6 matching
+ if (filter_var($parts[0],FILTER_VALIDATE_IP)) {
+ // ip is validated, now check if we have a valid network mask
+ if (empty($parts[1]))
+ return true;
+ else if(is_numeric($parts[1]) && $parts[1] < 129)
+ return true;
+ }
+ }
+ return 'IP address has invalid format. Please double check the value or select "other" for a type.';
+ break;
+ case 'ip-dst':
+ $parts = explode("/", $value);
+ // [0] = the ip
+ // [1] = the network address
+ if (count($parts) <= 2 ) {
+ // ipv4 and ipv6 matching
+ if (filter_var($parts[0],FILTER_VALIDATE_IP)) {
+ // ip is validated, now check if we have a valid network mask
+ if (empty($parts[1]))
+ return true;
+ else if(is_numeric($parts[1]) && $parts[1] < 129)
+ return true;
+ }
+ }
+ return 'IP address has invalid format. Please double check the value or select "other" for a type.';
+ break;
+ case 'domain':
+ if(preg_match("#^[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value))
+ return true;
+ return 'Domain name has invalid format. Please double check the value or select "other" for a type.';
+ break;
+ case 'email-src':
+ // we don't use the native function to prevent issues with partial email addresses
+ if(preg_match("#^[A-Z0-9._%+-]*@[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value))
+ return true;
+ return 'Email address has invalid format. Please double check the value or select "other" for a type.';
+ break;
+ case 'email-dst':
+ // we don't use the native function to prevent issues with partial email addresses
+ if(preg_match("#^[A-Z0-9._%+-]*@[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value))
+ return true;
+ return 'Email address has invalid format. Please double check the value or select "other" for a type.';
+ break;
+ case 'email-subject':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'email-attachment':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'url':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'user-agent':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'regkey':
+ // no newline
+ if (!preg_match("#\n#", $value))
+ return true;
+ break;
+ case 'snort':
+ // no validation yet. TODO implement data validation on snort signature type
+ case 'other':
+ return true;
+ break;
+ }
+
+ // default action is to return false
+ return true;
+
+ }
+
+
+
+ function getRelatedSignatures($signature) {
+ // LATER getRelatedSignatures($signature) this might become a performance bottleneck
+ $conditions = array('Signature.value =' => $signature['value'],
+ 'Signature.id !=' => $signature['id'],
+ 'Signature.type =' => $signature['type'], );
+ // $fields = array('Event.*');
+ $fields = array('Signature.*');
+
+ $similar_events = $this->find('all',array('conditions' => $conditions,
+ 'fields' => $fields,
+ 'order' => 'Signature.event_id DESC', )
+ );
+ return $similar_events;
+ }
+
+}
diff --git a/app/Model/User.php b/app/Model/User.php
new file mode 100644
index 000000000..66a1dc8bc
--- /dev/null
+++ b/app/Model/User.php
@@ -0,0 +1,244 @@
+ 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
+ ),
+ ),
+ 'password' => array(
+ 'minlength' => array(
+ 'rule' => array('minlength', 6),
+ 'message' => 'A password of a minimum length of 6 is required.',
+ //'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ 'complexity' => array(
+ 'rule' => array('complexPassword'),
+ 'message' => 'The password must contain at least one upper-case, one lower-case, one (digits or special character).', // TODO password strength requirements
+ //'allowEmpty' => false,
+ //'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ ),
+ 'org' => array(
+ 'notempty' => array(
+ 'rule' => array('notempty'),
+ 'message' => 'Please specify the organisation where you are working.',
+ //'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'),
+ 'message' => 'Please enter a valid email address.',
+ //'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ 'unique' => array(
+ 'rule' => 'isUnique',
+ 'message' => 'An account with this email address already exists.'
+ ),
+ ),
+ 'autoalert' => 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
+ ),
+ ),
+ 'authkey' => 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
+ ),
+ ),
+ 'invited_by' => 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
+ ),
+ ),
+ 'gpgkey' => array(
+ 'notempty' => array(
+ 'rule' => array('validateGpgkey'),
+ 'message' => 'GPG key not valid, please enter a valid key.',
+ //'allowEmpty' => false,
+ //'required' => false,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ ),
+ 'nids_sid' => array(
+ 'numeric' => array(
+ 'rule' => array('numeric'),
+ 'message' => 'A SID should be an integer.',
+ 'allowEmpty' => false,
+ 'required' => true,
+ //'last' => false, // Stop validation after this rule
+ //'on' => 'create', // Limit validation to 'create' or 'update' operations
+ ),
+ ),
+ 'termsaccepted' => 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
+ ),
+ ),
+ 'newsread' => array(
+ 'date' => array(
+ 'rule' => array('date'),
+ //'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(
+ 'Event' => array(
+ 'className' => 'Event',
+ 'foreignKey' => 'user_id',
+ 'dependent' => false,
+ 'conditions' => '',
+ 'fields' => '',
+ 'order' => '',
+ 'limit' => '',
+ 'offset' => '',
+ 'exclusive' => '',
+ 'finderQuery' => '',
+ 'counterQuery' => ''
+ )
+ );
+
+// var $actsAs = array('Acl' => array('type' => 'requester'));
+//
+// public function beforeValidate() {
+// // Fix issue with an empty password being automagically hashed
+// App::import('Core', 'Security'); // not sure whether this is necessary
+// if ($this->data['User']['password'] == Security::hash('', null, true)) {
+// $this->data['User']['password'] = '';
+// }
+// return true;
+// }
+
+ public function beforeSave() {
+ if (isset($this->data[$this->alias]['password'])) {
+ $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
+ }
+ return true;
+ }
+
+
+ /**
+ * Checks if the GPG key is a valid key
+ * But also import it in the keychain.
+ */
+ function validateGpgkey($check) {
+ // LATER first remove the old gpgkey from the keychain
+
+ // empty value
+ if (empty($check['gpgkey']))
+ return true;
+
+ // key is entered
+ require_once 'Crypt/GPG.php';
+ $gpg = new Crypt_GPG();
+ try {
+ $key_import_output = $gpg->importKey($check['gpgkey']);
+ if (!empty($key_import_output['fingerprint'])) {
+ return true;
+ }
+ } catch (Exception $e) {
+ debug($e);
+ return false;
+ }
+ }
+
+
+ function complexPassword($check) {
+ /*
+ 6 characters minimum
+ 1 or more upper-case letters
+ 1 or more lower-case letters
+ 1 or more digits or special characters
+ example: "EasyPeasy34"
+ */
+ $value = array_values($check);
+ $value = $value[0];
+ return preg_match('/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/', $value);
+ }
+
+ /**
+ * Generates an authentication key for each user
+ */
+ function generateAuthKey() {
+ //$key = sha1(mt_rand(30, 30).time());
+ $length = 40;
+ $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $char_len = strlen($characters)-1;
+ $key = '';
+ for ($p = 0; $p < $length; $p++) {
+ $key .= $characters[rand(0, $char_len)];
+ }
+
+ return $key;
+ }
+}
diff --git a/app/tests/cases/components/empty b/app/Plugin/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/cases/components/empty
rename to app/Plugin/empty
diff --git a/app/README.txt b/app/README.txt
index bc3924b80..3a3af51f8 100755
--- a/app/README.txt
+++ b/app/README.txt
@@ -1,21 +1,16 @@
-
-
-
+
TODOs
-----
Auth
-- Use captcha authentication
-- cleanup ACL and do it using the CakePHP concept
-- password strength requirements
+- Prevent bruteforce auth attempts
implement auditing/logging system
- add / edit events and signatures
- failed / success logins (with source IP, headers,...)
Security
-- apply CSRF checks on the delete parameters by enabling security modules and rewriting some parts
- force cookie reset after login
@@ -26,10 +21,10 @@ Download CyDefSIG using git in the /var/www/ directory.
cd /var/www/
git clone git@code.lab.modiss.be:cydefsig.git
-Download and extract CakePHP 1.3 to the web root directory:
+Download and extract CakePHP 2.x to the web root directory:
cd /tmp/
-wget https://nodeload.github.com/cakephp/cakephp/tarball/1.3
+wget https://nodeload.github.com/cakephp/cakephp/tarball/2.1
tar zxvf cakephp-cakephp-.tar.gz
cd cakephp-cakephp-*
diff --git a/app/tests/cases/controllers/empty b/app/Test/Case/Controller/Component/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/cases/controllers/empty
rename to app/Test/Case/Controller/Component/empty
diff --git a/app/Test/Case/Controller/EventsControllerTest.php b/app/Test/Case/Controller/EventsControllerTest.php
new file mode 100644
index 000000000..1f326dbe1
--- /dev/null
+++ b/app/Test/Case/Controller/EventsControllerTest.php
@@ -0,0 +1,142 @@
+redirectUrl = $url;
+ }
+}
+
+/**
+ * EventsController Test Case
+ *
+ */
+class EventsControllerTestCase extends CakeTestCase {
+/**
+ * Fixtures
+ *
+ * @var array
+ */
+ public $fixtures = array('app.event', 'app.user', 'app.group', 'app.signature');
+
+/**
+ * setUp method
+ *
+ * @return void
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->Events = new TestEventsController();
+ $this->Events->constructClasses();
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Events);
+
+ parent::tearDown();
+ }
+
+/**
+ * testIndex method
+ *
+ * @return void
+ */
+ public function testIndex() {
+
+ }
+/**
+ * testView method
+ *
+ * @return void
+ */
+ public function testView() {
+
+ }
+/**
+ * testAdd method
+ *
+ * @return void
+ */
+ public function testAdd() {
+
+ }
+/**
+ * testEdit method
+ *
+ * @return void
+ */
+ public function testEdit() {
+
+ }
+/**
+ * testDelete method
+ *
+ * @return void
+ */
+ public function testDelete() {
+
+ }
+/**
+ * testAdminIndex method
+ *
+ * @return void
+ */
+ public function testAdminIndex() {
+
+ }
+/**
+ * testAdminView method
+ *
+ * @return void
+ */
+ public function testAdminView() {
+
+ }
+/**
+ * testAdminAdd method
+ *
+ * @return void
+ */
+ public function testAdminAdd() {
+
+ }
+/**
+ * testAdminEdit method
+ *
+ * @return void
+ */
+ public function testAdminEdit() {
+
+ }
+/**
+ * testAdminDelete method
+ *
+ * @return void
+ */
+ public function testAdminDelete() {
+
+ }
+}
diff --git a/app/Test/Case/Controller/SignaturesControllerTest.php b/app/Test/Case/Controller/SignaturesControllerTest.php
new file mode 100644
index 000000000..98aa584b0
--- /dev/null
+++ b/app/Test/Case/Controller/SignaturesControllerTest.php
@@ -0,0 +1,142 @@
+redirectUrl = $url;
+ }
+}
+
+/**
+ * SignaturesController Test Case
+ *
+ */
+class SignaturesControllerTestCase extends CakeTestCase {
+/**
+ * Fixtures
+ *
+ * @var array
+ */
+ public $fixtures = array('app.signature', 'app.event', 'app.user', 'app.group');
+
+/**
+ * setUp method
+ *
+ * @return void
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->Signatures = new TestSignaturesController();
+ $this->Signatures->constructClasses();
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Signatures);
+
+ parent::tearDown();
+ }
+
+/**
+ * testIndex method
+ *
+ * @return void
+ */
+ public function testIndex() {
+
+ }
+/**
+ * testView method
+ *
+ * @return void
+ */
+ public function testView() {
+
+ }
+/**
+ * testAdd method
+ *
+ * @return void
+ */
+ public function testAdd() {
+
+ }
+/**
+ * testEdit method
+ *
+ * @return void
+ */
+ public function testEdit() {
+
+ }
+/**
+ * testDelete method
+ *
+ * @return void
+ */
+ public function testDelete() {
+
+ }
+/**
+ * testAdminIndex method
+ *
+ * @return void
+ */
+ public function testAdminIndex() {
+
+ }
+/**
+ * testAdminView method
+ *
+ * @return void
+ */
+ public function testAdminView() {
+
+ }
+/**
+ * testAdminAdd method
+ *
+ * @return void
+ */
+ public function testAdminAdd() {
+
+ }
+/**
+ * testAdminEdit method
+ *
+ * @return void
+ */
+ public function testAdminEdit() {
+
+ }
+/**
+ * testAdminDelete method
+ *
+ * @return void
+ */
+ public function testAdminDelete() {
+
+ }
+}
diff --git a/app/Test/Case/Controller/UsersControllerTest.php b/app/Test/Case/Controller/UsersControllerTest.php
new file mode 100644
index 000000000..25bf8c463
--- /dev/null
+++ b/app/Test/Case/Controller/UsersControllerTest.php
@@ -0,0 +1,142 @@
+redirectUrl = $url;
+ }
+}
+
+/**
+ * UsersController Test Case
+ *
+ */
+class UsersControllerTestCase extends CakeTestCase {
+/**
+ * Fixtures
+ *
+ * @var array
+ */
+ public $fixtures = array('app.user', 'app.group', 'app.event', 'app.signature');
+
+/**
+ * setUp method
+ *
+ * @return void
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->Users = new TestUsersController();
+ $this->Users->constructClasses();
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Users);
+
+ parent::tearDown();
+ }
+
+/**
+ * testIndex method
+ *
+ * @return void
+ */
+ public function testIndex() {
+
+ }
+/**
+ * testView method
+ *
+ * @return void
+ */
+ public function testView() {
+
+ }
+/**
+ * testAdd method
+ *
+ * @return void
+ */
+ public function testAdd() {
+
+ }
+/**
+ * testEdit method
+ *
+ * @return void
+ */
+ public function testEdit() {
+
+ }
+/**
+ * testDelete method
+ *
+ * @return void
+ */
+ public function testDelete() {
+
+ }
+/**
+ * testAdminIndex method
+ *
+ * @return void
+ */
+ public function testAdminIndex() {
+
+ }
+/**
+ * testAdminView method
+ *
+ * @return void
+ */
+ public function testAdminView() {
+
+ }
+/**
+ * testAdminAdd method
+ *
+ * @return void
+ */
+ public function testAdminAdd() {
+
+ }
+/**
+ * testAdminEdit method
+ *
+ * @return void
+ */
+ public function testAdminEdit() {
+
+ }
+/**
+ * testAdminDelete method
+ *
+ * @return void
+ */
+ public function testAdminDelete() {
+
+ }
+}
diff --git a/app/tests/cases/helpers/empty b/app/Test/Case/Model/Behavior/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/cases/helpers/empty
rename to app/Test/Case/Model/Behavior/empty
diff --git a/app/Test/Case/Model/EventTest.php b/app/Test/Case/Model/EventTest.php
new file mode 100644
index 000000000..8e1d82e08
--- /dev/null
+++ b/app/Test/Case/Model/EventTest.php
@@ -0,0 +1,37 @@
+Event = ClassRegistry::init('Event');
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Event);
+
+ parent::tearDown();
+ }
+
+}
diff --git a/app/Test/Case/Model/GroupTest.php b/app/Test/Case/Model/GroupTest.php
new file mode 100644
index 000000000..8d4fdba38
--- /dev/null
+++ b/app/Test/Case/Model/GroupTest.php
@@ -0,0 +1,37 @@
+Group = ClassRegistry::init('Group');
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Group);
+
+ parent::tearDown();
+ }
+
+}
diff --git a/app/Test/Case/Model/SignatureTest.php b/app/Test/Case/Model/SignatureTest.php
new file mode 100644
index 000000000..49d9e88df
--- /dev/null
+++ b/app/Test/Case/Model/SignatureTest.php
@@ -0,0 +1,37 @@
+Signature = ClassRegistry::init('Signature');
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->Signature);
+
+ parent::tearDown();
+ }
+
+}
diff --git a/app/Test/Case/Model/UserTest.php b/app/Test/Case/Model/UserTest.php
new file mode 100644
index 000000000..e78f24043
--- /dev/null
+++ b/app/Test/Case/Model/UserTest.php
@@ -0,0 +1,37 @@
+User = ClassRegistry::init('User');
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ unset($this->User);
+
+ parent::tearDown();
+ }
+
+}
diff --git a/app/tests/cases/models/empty b/app/Test/Case/View/Helper/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/cases/models/empty
rename to app/Test/Case/View/Helper/empty
diff --git a/app/Test/Fixture/EventFixture.php b/app/Test/Fixture/EventFixture.php
new file mode 100644
index 000000000..b4afefa68
--- /dev/null
+++ b/app/Test/Fixture/EventFixture.php
@@ -0,0 +1,41 @@
+ 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, 'collate' => 'utf8_bin', 'charset' => 'utf8'),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'info' => array('column' => 'info', 'unique' => 0)),
+ 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM')
+ );
+
+/**
+ * Records
+ *
+ * @var array
+ */
+ public $records = array(
+ array(
+ 'id' => 1,
+ 'org' => 'Lorem ipsum dolor sit amet',
+ 'date' => '2012-03-13',
+ 'info' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
+ 'user_id' => 1,
+ 'alerted' => 1,
+ 'uuid' => 'Lorem ipsum dolor sit amet'
+ ),
+ );
+}
diff --git a/app/Test/Fixture/GroupFixture.php b/app/Test/Fixture/GroupFixture.php
new file mode 100644
index 000000000..e126eddae
--- /dev/null
+++ b/app/Test/Fixture/GroupFixture.php
@@ -0,0 +1,31 @@
+ array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_bin', 'charset' => 'utf8'),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
+ 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM')
+ );
+
+/**
+ * Records
+ *
+ * @var array
+ */
+ public $records = array(
+ array(
+ 'id' => 1,
+ 'name' => 'Lorem ipsum dolor sit amet'
+ ),
+ );
+}
diff --git a/app/Test/Fixture/SignatureFixture.php b/app/Test/Fixture/SignatureFixture.php
new file mode 100644
index 000000000..b2dfe261c
--- /dev/null
+++ b/app/Test/Fixture/SignatureFixture.php
@@ -0,0 +1,39 @@
+ 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'),
+ 'value' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'),
+ 'to_ids' => array('type' => 'boolean', 'null' => false, 'default' => '1'),
+ 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_bin', 'charset' => 'utf8'),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'event_id' => array('column' => 'event_id', 'unique' => 0)),
+ 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_bin', 'engine' => 'MyISAM')
+ );
+
+/**
+ * Records
+ *
+ * @var array
+ */
+ public $records = array(
+ array(
+ 'id' => 1,
+ 'event_id' => 1,
+ 'type' => 'Lorem ipsum dolor sit amet',
+ 'value' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
+ 'to_ids' => 1,
+ 'uuid' => 'Lorem ipsum dolor sit amet'
+ ),
+ );
+}
diff --git a/app/Test/Fixture/UserFixture.php b/app/Test/Fixture/UserFixture.php
new file mode 100644
index 000000000..3e05763da
--- /dev/null
+++ b/app/Test/Fixture/UserFixture.php
@@ -0,0 +1,51 @@
+ array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'group_id' => array('type' => 'integer', 'null' => false, 'default' => NULL),
+ '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')
+ );
+
+/**
+ * Records
+ *
+ * @var array
+ */
+ public $records = array(
+ array(
+ 'id' => 1,
+ 'group_id' => 1,
+ 'password' => 'Lorem ipsum dolor sit amet',
+ 'org' => 'Lorem ipsum dolor sit amet',
+ 'email' => 'Lorem ipsum dolor sit amet',
+ 'autoalert' => 1,
+ 'authkey' => 'Lorem ipsum dolor sit amet',
+ 'invited_by' => 1,
+ 'gpgkey' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
+ 'nids_sid' => 1,
+ 'termsaccepted' => 1,
+ 'newsread' => '2012-03-13'
+ ),
+ );
+}
diff --git a/app/tests/fixtures/empty b/app/Test/Fixture/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/fixtures/empty
rename to app/Test/Fixture/empty
diff --git a/app/tests/groups/empty b/app/Vendor/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/tests/groups/empty
rename to app/Vendor/empty
diff --git a/app/views/elements/actions_menu.ctp b/app/View/Elements/actions_menu.ctp
similarity index 100%
rename from app/views/elements/actions_menu.ctp
rename to app/View/Elements/actions_menu.ctp
diff --git a/app/vendors/shells/tasks/empty b/app/View/Elements/empty
old mode 100755
new mode 100644
similarity index 100%
rename from app/vendors/shells/tasks/empty
rename to app/View/Elements/empty
diff --git a/app/View/Emails/html/default.ctp b/app/View/Emails/html/default.ctp
new file mode 100644
index 000000000..9d8734706
--- /dev/null
+++ b/app/View/Emails/html/default.ctp
@@ -0,0 +1,25 @@
+
+ ' . $line . "
\n";
+endforeach;
+?>
\ No newline at end of file
diff --git a/app/views/elements/email/text/body.ctp b/app/View/Emails/text/body.ctp
similarity index 100%
rename from app/views/elements/email/text/body.ctp
rename to app/View/Emails/text/body.ctp
diff --git a/app/View/Emails/text/default.ctp b/app/View/Emails/text/default.ctp
new file mode 100644
index 000000000..29873b198
--- /dev/null
+++ b/app/View/Emails/text/default.ctp
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/app/views/elements/email/text/new_event.ctp b/app/View/Emails/text/new_event.ctp
similarity index 100%
rename from app/views/elements/email/text/new_event.ctp
rename to app/View/Emails/text/new_event.ctp
diff --git a/app/View/Errors/error400.ctp b/app/View/Errors/error400.ctp
new file mode 100644
index 000000000..09d590170
--- /dev/null
+++ b/app/View/Errors/error400.ctp
@@ -0,0 +1,31 @@
+
+
+
+ :
+ '{$url}'"
+ ); ?>
+
+ 0 ):
+ echo $this->element('exception_stack_trace');
+endif;
+?>
diff --git a/app/views/errors/error403.ctp b/app/View/Errors/error403.ctp
similarity index 100%
rename from app/views/errors/error403.ctp
rename to app/View/Errors/error403.ctp
diff --git a/app/View/Errors/error500.ctp b/app/View/Errors/error500.ctp
new file mode 100644
index 000000000..6c500aa73
--- /dev/null
+++ b/app/View/Errors/error500.ctp
@@ -0,0 +1,28 @@
+
+
+
+ :
+
+
+ 0 ):
+ echo $this->element('exception_stack_trace');
+endif;
+?>
diff --git a/app/views/events/add.ctp b/app/View/Events/add.ctp
similarity index 84%
rename from app/views/events/add.ctp
rename to app/View/Events/add.ctp
index c87fe634d..578be237b 100755
--- a/app/views/events/add.ctp
+++ b/app/View/Events/add.ctp
@@ -3,8 +3,6 @@