From d2c1930b0e083166a9a19d080b3881e801354f17 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 15:22:40 -0400 Subject: [PATCH 01/29] Initial implementation of oath login. Post to login now generates an oath access token which will be used to identify the user in subsequent requests. --- app/Plugin/Api/Config/routes.php | 3 +- app/Plugin/Api/Controller/UsersController.php | 17 +- app/Plugin/Api/Model/ApiUserApplication.php | 87 +++ .../Case/Model/ApiUserApplicationTest.php | 66 ++ .../Api/Test/Case/models/api_user.test.php | 18 - .../Fixture/ApiUserApplicationFixture.php | 34 + .../Api/Test/Fixture/ApiUserFixture.php | 47 ++ .../Api/Test/Fixture/ContainerFixture.php | 51 ++ .../Api/Test/Fixture/ContainerItemFixture.php | 56 ++ .../Api/Test/Fixture/LocationFixture.php | 61 ++ app/Plugin/Api/Test/Fixture/UserFixture.php | 51 ++ .../Api/Test/Fixture/api_user_fixture.php | 26 - .../ConfigurationsControllerTest.php | 47 -- .../ContainerItemsControllerTest.php | 27 - .../controllers/ContainersControllerTest.php | 27 - .../Case/controllers/UsersControllerTest.php | 67 -- app/Test/Case/models/ContainerItemTest.php | 18 - app/Test/Case/models/UserTest.php | 18 - app/Test/Fixture/container_fixture.php | 32 - app/Test/Fixture/container_item_fixture.php | 30 - app/Test/Fixture/tag_fixture.php | 26 - app/Test/Fixture/user_fixture.php | 28 - app/webroot/css/cake.generic.css | 739 ++++++++++++++++++ app/webroot/img/cake.icon.png | Bin 0 -> 943 bytes app/webroot/img/cake.power.gif | Bin 0 -> 201 bytes app/webroot/img/test-error-icon.png | Bin 0 -> 3358 bytes app/webroot/img/test-fail-icon.png | Bin 0 -> 496 bytes app/webroot/img/test-pass-icon.png | Bin 0 -> 783 bytes app/webroot/img/test-skip-icon.png | Bin 0 -> 1207 bytes app/webroot/test.php | 92 ++- bin/gen-security.php | 32 + 31 files changed, 1285 insertions(+), 415 deletions(-) create mode 100644 app/Plugin/Api/Model/ApiUserApplication.php create mode 100644 app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php delete mode 100755 app/Plugin/Api/Test/Case/models/api_user.test.php create mode 100644 app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php create mode 100644 app/Plugin/Api/Test/Fixture/ApiUserFixture.php create mode 100644 app/Plugin/Api/Test/Fixture/ContainerFixture.php create mode 100644 app/Plugin/Api/Test/Fixture/ContainerItemFixture.php create mode 100644 app/Plugin/Api/Test/Fixture/LocationFixture.php create mode 100644 app/Plugin/Api/Test/Fixture/UserFixture.php delete mode 100755 app/Plugin/Api/Test/Fixture/api_user_fixture.php delete mode 100755 app/Test/Case/controllers/ConfigurationsControllerTest.php delete mode 100755 app/Test/Case/controllers/ContainerItemsControllerTest.php delete mode 100755 app/Test/Case/controllers/ContainersControllerTest.php delete mode 100755 app/Test/Case/controllers/UsersControllerTest.php delete mode 100755 app/Test/Case/models/ContainerItemTest.php delete mode 100755 app/Test/Case/models/UserTest.php delete mode 100755 app/Test/Fixture/container_fixture.php delete mode 100755 app/Test/Fixture/container_item_fixture.php delete mode 100755 app/Test/Fixture/tag_fixture.php delete mode 100755 app/Test/Fixture/user_fixture.php create mode 100644 app/webroot/css/cake.generic.css create mode 100644 app/webroot/img/cake.icon.png create mode 100644 app/webroot/img/cake.power.gif create mode 100644 app/webroot/img/test-error-icon.png create mode 100644 app/webroot/img/test-fail-icon.png create mode 100644 app/webroot/img/test-pass-icon.png create mode 100644 app/webroot/img/test-skip-icon.png create mode 100644 bin/gen-security.php diff --git a/app/Plugin/Api/Config/routes.php b/app/Plugin/Api/Config/routes.php index a4435bf..cc00ac3 100644 --- a/app/Plugin/Api/Config/routes.php +++ b/app/Plugin/Api/Config/routes.php @@ -2,7 +2,8 @@ Router::mapResources(array( 'Api.containers', - 'Api.conainer_items' + 'Api.conainer_items', + 'Api.users' ), array( 'id' => '[a-z0-9-]+' )); diff --git a/app/Plugin/Api/Controller/UsersController.php b/app/Plugin/Api/Controller/UsersController.php index 3cd4480..5e0f031 100644 --- a/app/Plugin/Api/Controller/UsersController.php +++ b/app/Plugin/Api/Controller/UsersController.php @@ -4,7 +4,7 @@ class UsersController extends ApiAppController { public $name = 'Users'; - public $uses = array('User'); + public $uses = array('User', 'Api.ApiUserApplication'); public function beforeFilter() { $this->ApiAuth->allow('login'); @@ -12,7 +12,7 @@ public function beforeFilter() { } public function login() { - $required = array('email' => true, 'password' => true); + $required = array('email' => true, 'password' => true, 'application' => true); if(!$this->RequestHandler->isPost()) { throw new MethodNotAllowedException(); } @@ -23,10 +23,13 @@ public function login() { throw new ForbiddenException(); } $user = $this->User->getUserByEmail($this->request->data['email']); - $user = array_intersect_key($user['User'], array('id' => true, 'email' => true, 'uuid' => true)); - $user['api_key'] = $this->ApiUser->getApiKey($user['id']); - $user['secret_key'] = $this->ApiUser->getSecretKey($user['api_key']); - $this->jsonOutput($user); + try { + $token = $this->ApiUserApplication->getTokenByUserId($user['User']['id'], $this->request->data['application']); + } catch (NotFoundException $e) { + // Token doesn't exist, create one + $token = $this->ApiUserApplication->createApplication($this->request->data['application'], $user['User']['id']); + } + $this->jsonOutput(compact('token')); } -} \ No newline at end of file +} diff --git a/app/Plugin/Api/Model/ApiUserApplication.php b/app/Plugin/Api/Model/ApiUserApplication.php new file mode 100644 index 0000000..efd0dd6 --- /dev/null +++ b/app/Plugin/Api/Model/ApiUserApplication.php @@ -0,0 +1,87 @@ + array( + 'numeric' => array( + 'rule' => array('numeric') + ), + 'notempty' => array( + 'rule' => array('notempty') + ) + ), + 'token' => array( + 'notempty' => array( + 'rule' => array('notempty') + ) + ) + ); + + public $belongsTo = array( + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'user_id', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) + ); + + /** + * Creates an access token entry for a user with a given application name. + * + * @param string $name + * @param integer $userId + * @return string + * @throws InternalErrorException + */ + public function createApplication($name, $userId) { + $token = sha1($userId . time()); + $result = $this->save(array( + 'ApiUserApplication' => array( + 'name' => $name, + 'user_id' => $userId, + 'token' => $token + ) + )); + if (!$result) { + throw new InternalErrorException('Unable to create oauth token'); + } + return $token; + } + + /** + * Retrieve the user's auth token by userId and application name. + * + * @param integer $userId + * @param string $name Application name + * @return string + * @throws NotFoundException + */ + public function getTokenByUserId($userId, $name) { + $key = $userId . $name . '_auth_token'; + if (!$token = Cache::read($key)) { + $result = $this->find('first', array( + 'conditions' => array( + 'ApiUserApplication.user_id' => $userId, + 'ApiUserApplication.name' => $name + ), + 'fields' => array('ApiUserApplication.token') + )); + if (!empty($result['ApiUserApplication']['token'])) { + $token = $result['ApiUserApplication']['token']; + Cache::write($key, $token); + } + } + if (empty($token)) { + throw new NotFoundException('Token does not exist for this user.'); + } + return $token; + } + +} diff --git a/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php b/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php new file mode 100644 index 0000000..44acdd7 --- /dev/null +++ b/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php @@ -0,0 +1,66 @@ +ApiUserApplication = ClassRegistry::init('Api.ApiUserApplication'); + $this->testAppToken = $this->ApiUserApplication->createApplication('Test App', '3'); + } + +/** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->ApiUserApplication); + unset($this->testAppToken); + Cache::clear(); + parent::tearDown(); + } + +/** + * testCreateApplication method + * + * @return void + */ + public function testCreateApplication() { + $result = $this->ApiUserApplication->createApplication('Test New App 1', '3'); + $this->assertNotEmpty($result); + $this->assertEquals($result, $this->ApiUserApplication->createApplication('Test New App 1', '3')); + } + +/** + * testGetTokenByUserId method + * + * @return void + */ + public function testGetTokenByUserId() { + $this->assertEquals($this->testAppToken, $this->ApiUserApplication->getTokenByUserId('3', 'Test App')); + } + +} diff --git a/app/Plugin/Api/Test/Case/models/api_user.test.php b/app/Plugin/Api/Test/Case/models/api_user.test.php deleted file mode 100755 index eafb884..0000000 --- a/app/Plugin/Api/Test/Case/models/api_user.test.php +++ /dev/null @@ -1,18 +0,0 @@ -ApiUser =& ClassRegistry::init('ApiUser'); - } - - function endTest() { - unset($this->ApiUser); - ClassRegistry::flush(); - } - -} -?> \ No newline at end of file diff --git a/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php b/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php new file mode 100644 index 0000000..4c3cb27 --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php @@ -0,0 +1,34 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'index'), + 'name' => array('type' => 'string', 'null' => false, 'length' => 100, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'token' => array('type' => 'string', 'null' => false, 'length' => 40, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'user_id' => array('column' => 'user_id', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/ApiUserFixture.php b/app/Plugin/Api/Test/Fixture/ApiUserFixture.php new file mode 100644 index 0000000..8132522 --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/ApiUserFixture.php @@ -0,0 +1,47 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'index'), + 'api_key' => array('type' => 'string', 'null' => false, 'default' => null, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'secret_key' => array('type' => 'string', 'null' => true, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), + 'is_active' => array('type' => 'boolean', 'null' => false, 'default' => '1'), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'user_id' => array('column' => 'user_id', 'unique' => 0), + 'api_key' => array('column' => 'api_key', 'unique' => 0), + 'secret_key' => array('column' => 'secret_key', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_unicode_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + array( + 'id' => '3', + 'user_id' => '3', + 'api_key' => 'd7504563a2e26a1970384974b44848576ca27b80', + 'secret_key' => 'c6d3085094354055d4a23f20c48a7bcfc2399c9b', + 'is_active' => 1, + 'created' => '2013-08-10 18:45:01', + 'modified' => '2013-08-10 18:45:01' + ), + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/ContainerFixture.php b/app/Plugin/Api/Test/Fixture/ContainerFixture.php new file mode 100644 index 0000000..8a24f28 --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/ContainerFixture.php @@ -0,0 +1,51 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), + 'location_id' => array('type' => 'integer', 'null' => true, 'default' => '0', 'key' => 'index'), + 'uuid' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 36, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 36, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'slug' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 40, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'container_item_count' => array('type' => 'integer', 'null' => true, 'default' => '0', 'length' => 10), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'user' => array('column' => 'user_id', 'unique' => 0), + 'fk_containers_users' => array('column' => 'user_id', 'unique' => 0), + 'location_id' => array('column' => 'location_id', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + array( + 'id' => '40', + 'user_id' => '3', + 'location_id' => '0', + 'uuid' => '52068a36-5f88-485a-b7f3-085521210046', + 'name' => 'Test 1', + 'slug' => 'test-1', + 'container_item_count' => '2', + 'created' => '2013-08-10 18:45:10', + 'modified' => '2013-08-10 18:45:10' + ), + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/ContainerItemFixture.php b/app/Plugin/Api/Test/Fixture/ContainerItemFixture.php new file mode 100644 index 0000000..d2382ea --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/ContainerItemFixture.php @@ -0,0 +1,56 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'container_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), + 'uuid' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 36, 'key' => 'index', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'body' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'quantity' => array('type' => 'integer', 'null' => false, 'default' => '1'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'container' => array('column' => 'container_id', 'unique' => 0), + 'fk_container_items_containers1' => array('column' => 'container_id', 'unique' => 0), + 'uuid' => array('column' => 'uuid', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + array( + 'id' => '119', + 'container_id' => '40', + 'uuid' => '52068a3c-0d50-40c4-8dca-085621210046', + 'body' => 'Test 1a', + 'quantity' => '1', + 'created' => '2013-08-10 18:45:16', + 'modified' => '2013-08-10 18:45:16' + ), + array( + 'id' => '120', + 'container_id' => '40', + 'uuid' => '52068a40-6dd4-47ae-bbf9-085821210046', + 'body' => 'Test 1b', + 'quantity' => '1', + 'created' => '2013-08-10 18:45:20', + 'modified' => '2013-08-10 18:45:20' + ), + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/LocationFixture.php b/app/Plugin/Api/Test/Fixture/LocationFixture.php new file mode 100644 index 0000000..5fffece --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/LocationFixture.php @@ -0,0 +1,61 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'index'), + 'uuid' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 36, 'key' => 'index', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 40, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'is_mappable' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'address' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 250, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'container_count' => array('type' => 'integer', 'null' => false, 'default' => '0'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'user_id' => array('column' => 'user_id', 'unique' => 0), + 'uuid' => array('column' => 'uuid', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + array( + 'id' => '4', + 'user_id' => '3', + 'uuid' => '52068a48-5cbc-4750-959c-085d21210046', + 'name' => 'Test Loc 1', + 'is_mappable' => 0, + 'address' => '', + 'container_count' => '0', + 'created' => '2013-08-10 18:45:28', + 'modified' => '2013-08-10 18:45:28' + ), + array( + 'id' => '5', + 'user_id' => '3', + 'uuid' => '52068a56-7968-40af-a06c-085e21210046', + 'name' => 'Test Loc 2', + 'is_mappable' => 1, + 'address' => '123 Easy St', + 'container_count' => '0', + 'created' => '2013-08-10 18:45:42', + 'modified' => '2013-08-10 18:45:42' + ), + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/UserFixture.php b/app/Plugin/Api/Test/Fixture/UserFixture.php new file mode 100644 index 0000000..f196ad1 --- /dev/null +++ b/app/Plugin/Api/Test/Fixture/UserFixture.php @@ -0,0 +1,51 @@ + array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), + 'email' => array('type' => 'string', 'null' => false, 'length' => 60, 'key' => 'unique', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'password' => array('type' => 'string', 'null' => false, 'length' => 60, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'uuid' => array('type' => 'string', 'null' => false, 'length' => 36, 'key' => 'index', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), + 'is_active' => array('type' => 'boolean', 'null' => false, 'default' => '1', 'key' => 'index'), + 'is_admin' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'reset_password' => array('type' => 'boolean', 'null' => false, 'default' => '0'), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'email' => array('column' => 'email', 'unique' => 1), + 'uuid' => array('column' => 'uuid', 'unique' => 0), + 'is_active' => array('column' => 'is_active', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_unicode_ci', 'engine' => 'InnoDB') + ); + +/** + * Records + * + * @var array + */ + public $records = array( + array( + 'id' => '3', + 'email' => 'test@test.com', + 'password' => 'c65470601d71b53402c41d709d0803477dd7c3cb', + 'uuid' => '52068a2c-00c4-40ba-baad-085321210046', + 'is_active' => 1, + 'is_admin' => 0, + 'reset_password' => 0, + 'created' => '2013-08-10 18:45:00', + 'modified' => '2013-08-10 18:45:00' + ), + ); + +} diff --git a/app/Plugin/Api/Test/Fixture/api_user_fixture.php b/app/Plugin/Api/Test/Fixture/api_user_fixture.php deleted file mode 100755 index a45af11..0000000 --- a/app/Plugin/Api/Test/Fixture/api_user_fixture.php +++ /dev/null @@ -1,26 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'), - 'api_key' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), - 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'user_id' => array('column' => 'user_id', 'unique' => 0), 'api_key' => array('column' => 'api_key', 'unique' => 0)), - 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_unicode_ci', 'engine' => 'InnoDB') - ); - - var $records = array( - array( - 'id' => 1, - 'user_id' => 1, - 'api_key' => 'Lorem ipsum dolor sit amet', - 'created' => '2011-05-17 22:52:07', - 'modified' => '2011-05-17 22:52:07' - ), - ); -} -?> \ No newline at end of file diff --git a/app/Test/Case/controllers/ConfigurationsControllerTest.php b/app/Test/Case/controllers/ConfigurationsControllerTest.php deleted file mode 100755 index a78eb23..0000000 --- a/app/Test/Case/controllers/ConfigurationsControllerTest.php +++ /dev/null @@ -1,47 +0,0 @@ -redirectUrl = $url; - } -} - -class ConfigurationsControllerTest extends CakeTestCase { - var $fixtures = array('app.configuration'); - - function startTest() { - $this->Configurations =& new TestConfigurationsController(); - $this->Configurations->constructClasses(); - } - - function endTest() { - unset($this->Configurations); - ClassRegistry::flush(); - } - - function testAdminIndex() { - - } - - function testAdminView() { - - } - - function testAdminAdd() { - - } - - function testAdminEdit() { - - } - - function testAdminDelete() { - - } - -} -?> \ No newline at end of file diff --git a/app/Test/Case/controllers/ContainerItemsControllerTest.php b/app/Test/Case/controllers/ContainerItemsControllerTest.php deleted file mode 100755 index b5c34e4..0000000 --- a/app/Test/Case/controllers/ContainerItemsControllerTest.php +++ /dev/null @@ -1,27 +0,0 @@ -redirectUrl = $url; - } -} - -class ContainerItemsControllerTest extends CakeTestCase { - var $fixtures = array('app.container_item', 'app.container', 'app.tag', 'app.user'); - - function startTest() { - $this->ContainerItems =& new TestContainerItemsController(); - $this->ContainerItems->constructClasses(); - } - - function endTest() { - unset($this->ContainerItems); - ClassRegistry::flush(); - } - -} -?> \ No newline at end of file diff --git a/app/Test/Case/controllers/ContainersControllerTest.php b/app/Test/Case/controllers/ContainersControllerTest.php deleted file mode 100755 index 57b252b..0000000 --- a/app/Test/Case/controllers/ContainersControllerTest.php +++ /dev/null @@ -1,27 +0,0 @@ -redirectUrl = $url; - } -} - -class ContainersControllerTest extends CakeTestCase { - var $fixtures = array('app.container', 'app.category', 'app.container_item', 'app.user'); - - function startTest() { - $this->Containers =& new TestContainersController(); - $this->Containers->constructClasses(); - } - - function endTest() { - unset($this->Containers); - ClassRegistry::flush(); - } - -} -?> \ No newline at end of file diff --git a/app/Test/Case/controllers/UsersControllerTest.php b/app/Test/Case/controllers/UsersControllerTest.php deleted file mode 100755 index 28defcb..0000000 --- a/app/Test/Case/controllers/UsersControllerTest.php +++ /dev/null @@ -1,67 +0,0 @@ -redirectUrl = $url; - } -} - -class UsersControllerTest extends CakeTestCase { - var $fixtures = array('app.user'); - - function startTest() { - $this->Users =& new TestUsersController(); - $this->Users->constructClasses(); - } - - function endTest() { - unset($this->Users); - ClassRegistry::flush(); - } - - function testIndex() { - - } - - function testView() { - - } - - function testAdd() { - - } - - function testEdit() { - - } - - function testDelete() { - - } - - function testAdminIndex() { - - } - - function testAdminView() { - - } - - function testAdminAdd() { - - } - - function testAdminEdit() { - - } - - function testAdminDelete() { - - } - -} -?> \ No newline at end of file diff --git a/app/Test/Case/models/ContainerItemTest.php b/app/Test/Case/models/ContainerItemTest.php deleted file mode 100755 index ba43fb1..0000000 --- a/app/Test/Case/models/ContainerItemTest.php +++ /dev/null @@ -1,18 +0,0 @@ -ContainerItem =& ClassRegistry::init('ContainerItem'); - } - - function endTest() { - unset($this->ContainerItem); - ClassRegistry::flush(); - } - -} -?> \ No newline at end of file diff --git a/app/Test/Case/models/UserTest.php b/app/Test/Case/models/UserTest.php deleted file mode 100755 index b1f24e4..0000000 --- a/app/Test/Case/models/UserTest.php +++ /dev/null @@ -1,18 +0,0 @@ -User =& ClassRegistry::init('User'); - } - - function endTest() { - unset($this->User); - ClassRegistry::flush(); - } - -} -?> \ No newline at end of file diff --git a/app/Test/Fixture/container_fixture.php b/app/Test/Fixture/container_fixture.php deleted file mode 100755 index 2995eac..0000000 --- a/app/Test/Fixture/container_fixture.php +++ /dev/null @@ -1,32 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'category_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), - 'user_Id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), - 'name' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 36, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'slug' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 40, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'container_item_count' => array('type' => 'integer', 'null' => true, 'default' => '0', 'length' => 10), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'category' => array('column' => 'category_id', 'unique' => 0), 'user' => array('column' => 'user_Id', 'unique' => 0)), - 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') - ); - - var $records = array( - array( - 'id' => 1, - 'category_id' => 1, - 'user_Id' => 1, - 'name' => 'Lorem ipsum dolor sit amet', - 'slug' => 'Lorem ipsum dolor sit amet', - 'container_item_count' => 1, - 'created' => '2011-03-02 23:11:31', - 'modified' => '2011-03-02 23:11:31' - ), - ); -} -?> \ No newline at end of file diff --git a/app/Test/Fixture/container_item_fixture.php b/app/Test/Fixture/container_item_fixture.php deleted file mode 100755 index a99b4bd..0000000 --- a/app/Test/Fixture/container_item_fixture.php +++ /dev/null @@ -1,30 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'container_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), - 'category_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'key' => 'index'), - 'title' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 30, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'body' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'container' => array('column' => 'container_id', 'unique' => 0), 'category' => array('column' => 'category_id', 'unique' => 0)), - 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') - ); - - var $records = array( - array( - 'id' => 1, - 'container_id' => 1, - 'category_id' => 1, - 'title' => 'Lorem ipsum dolor sit amet', - 'body' => '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.', - 'created' => '2011-03-02 23:15:01', - 'modified' => '2011-03-02 23:15:01' - ), - ); -} -?> \ No newline at end of file diff --git a/app/Test/Fixture/tag_fixture.php b/app/Test/Fixture/tag_fixture.php deleted file mode 100755 index d3eea09..0000000 --- a/app/Test/Fixture/tag_fixture.php +++ /dev/null @@ -1,26 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'name' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 30, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'slug' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 30, 'key' => 'index', 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'slug' => array('column' => 'slug', 'unique' => 0)), - 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') - ); - - var $records = array( - array( - 'id' => 1, - 'name' => 'Lorem ipsum dolor sit amet', - 'slug' => 'Lorem ipsum dolor sit amet', - 'created' => '2011-03-14 21:08:57', - 'modified' => '2011-03-14 21:08:57' - ), - ); -} -?> \ No newline at end of file diff --git a/app/Test/Fixture/user_fixture.php b/app/Test/Fixture/user_fixture.php deleted file mode 100755 index 4148988..0000000 --- a/app/Test/Fixture/user_fixture.php +++ /dev/null @@ -1,28 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'email' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 60, 'key' => 'unique', 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 60, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'uuid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 36, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), - 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'email' => array('column' => 'email', 'unique' => 1)), - 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'InnoDB') - ); - - var $records = array( - array( - 'id' => 1, - 'email' => 'Lorem ipsum dolor sit amet', - 'password' => 'Lorem ipsum dolor sit amet', - 'uuid' => 'Lorem ipsum dolor sit amet', - 'created' => '2011-02-19 18:01:45', - 'modified' => '2011-02-19 18:01:45' - ), - ); -} -?> \ No newline at end of file diff --git a/app/webroot/css/cake.generic.css b/app/webroot/css/cake.generic.css new file mode 100644 index 0000000..16ab345 --- /dev/null +++ b/app/webroot/css/cake.generic.css @@ -0,0 +1,739 @@ +@charset "utf-8"; +/** + * + * Generic CSS for CakePHP + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package app.webroot.css + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +* { + margin:0; + padding:0; +} + +/** General Style Info **/ +body { + background: #003d4c; + color: #fff; + font-family:'lucida grande',verdana,helvetica,arial,sans-serif; + font-size:90%; + margin: 0; +} +a { + color: #003d4c; + text-decoration: underline; + font-weight: bold; +} +a:hover { + color: #367889; + text-decoration:none; +} +a img { + border:none; +} +h1, h2, h3, h4 { + font-weight: normal; + margin-bottom:0.5em; +} +h1 { + background:#fff; + color: #003d4c; + font-size: 100%; +} +h2 { + background:#fff; + color: #e32; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 190%; +} +h3 { + color: #2c6877; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 165%; +} +h4 { + color: #993; + font-weight: normal; +} +ul, li { + margin: 0 12px; +} +p { + margin: 0 0 1em 0; +} + +/** Layout **/ +#container { + text-align: left; +} + +#header{ + padding: 10px 20px; +} +#header h1 { + line-height:20px; + background: #003d4c url('../img/cake.icon.png') no-repeat left; + color: #fff; + padding: 0px 30px; +} +#header h1 a { + color: #fff; + background: #003d4c; + font-weight: normal; + text-decoration: none; +} +#header h1 a:hover { + color: #fff; + background: #003d4c; + text-decoration: underline; +} +#content{ + background: #fff; + clear: both; + color: #333; + padding: 10px 20px 40px 20px; + overflow: auto; +} +#footer { + clear: both; + padding: 6px 10px; + text-align: right; +} + +/** containers **/ +div.form, +div.index, +div.view { + float:right; + width:76%; + border-left:1px solid #666; + padding:10px 2%; +} +div.actions { + float:left; + width:16%; + padding:10px 1.5%; +} +div.actions h3 { + padding-top:0; + color:#777; +} + + +/** Tables **/ +table { + border-right:0; + clear: both; + color: #333; + margin-bottom: 10px; + width: 100%; +} +th { + border:0; + border-bottom:2px solid #555; + text-align: left; + padding:4px; +} +th a { + display: block; + padding: 2px 4px; + text-decoration: none; +} +th a.asc:after { + content: ' ⇣'; +} +th a.desc:after { + content: ' ⇡'; +} +table tr td { + padding: 6px; + text-align: left; + vertical-align: top; + border-bottom:1px solid #ddd; +} +table tr:nth-child(even) { + background: #f9f9f9; +} +td.actions { + text-align: center; + white-space: nowrap; +} +table td.actions a { + margin: 0px 6px; + padding:2px 5px; +} + +/* SQL log */ +.cake-sql-log { + background: #fff; +} +.cake-sql-log td { + padding: 4px 8px; + text-align: left; + font-family: Monaco, Consolas, "Courier New", monospaced; +} +.cake-sql-log caption { + color:#fff; +} + +/** Paging **/ +.paging { + background:#fff; + color: #ccc; + margin-top: 1em; + clear:both; +} +.paging .current, +.paging .disabled, +.paging a { + text-decoration: none; + padding: 5px 8px; + display: inline-block +} +.paging > span { + display: inline-block; + border: 1px solid #ccc; + border-left: 0; +} +.paging > span:hover { + background: #efefef; +} +.paging .prev { + border-left: 1px solid #ccc; + -moz-border-radius: 4px 0 0 4px; + -webkit-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} +.paging .next { + -moz-border-radius: 0 4px 4px 0; + -webkit-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} +.paging .disabled { + color: #ddd; +} +.paging .disabled:hover { + background: transparent; +} +.paging .current { + background: #efefef; + color: #c73e14; +} + +/** Scaffold View **/ +dl { + line-height: 2em; + margin: 0em 0em; + width: 60%; +} +dl dd:nth-child(4n+2), +dl dt:nth-child(4n+1) { + background: #f4f4f4; +} + +dt { + font-weight: bold; + padding-left: 4px; + vertical-align: top; + width: 10em; +} +dd { + margin-left: 10em; + margin-top: -2em; + vertical-align: top; +} + +/** Forms **/ +form { + clear: both; + margin-right: 20px; + padding: 0; + width: 95%; +} +fieldset { + border: none; + margin-bottom: 1em; + padding: 16px 10px; +} +fieldset legend { + color: #e32; + font-size: 160%; + font-weight: bold; +} +fieldset fieldset { + margin-top: 0; + padding: 10px 0 0; +} +fieldset fieldset legend { + font-size: 120%; + font-weight: normal; +} +fieldset fieldset div { + clear: left; + margin: 0 20px; +} +form div { + clear: both; + margin-bottom: 1em; + padding: .5em; + vertical-align: text-top; +} +form .input { + color: #444; +} +form .required { + font-weight: bold; +} +form .required label:after { + color: #e32; + content: '*'; + display:inline; +} +form div.submit { + border: 0; + clear: both; + margin-top: 10px; +} +label { + display: block; + font-size: 110%; + margin-bottom:3px; +} +input, textarea { + clear: both; + font-size: 140%; + font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif; + padding: 1%; + width:98%; +} +select { + clear: both; + font-size: 120%; + vertical-align: text-bottom; +} +select[multiple=multiple] { + width: 100%; +} +option { + font-size: 120%; + padding: 0 3px; +} +input[type=checkbox] { + clear: left; + float: left; + margin: 0px 6px 7px 2px; + width: auto; +} +div.checkbox label { + display: inline; +} +input[type=radio] { + float:left; + width:auto; + margin: 6px 0; + padding: 0; + line-height: 26px; +} +.radio label { + margin: 0 0 6px 20px; + line-height: 26px; +} +input[type=submit] { + display: inline; + font-size: 110%; + width: auto; +} +form .submit input[type=submit] { + background:#62af56; + background-image: -webkit-gradient(linear, left top, left bottom, from(#76BF6B), to(#3B8230)); + background-image: -webkit-linear-gradient(top, #76BF6B, #3B8230); + background-image: -moz-linear-gradient(top, #76BF6B, #3B8230); + border-color: #2d6324; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 0px -1px 0px; + padding: 8px 10px; +} +form .submit input[type=submit]:hover { + background: #5BA150; +} +/* Form errors */ +form .error { + background: #FFDACC; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + font-weight: normal; +} +form .error-message { + -moz-border-radius: none; + -webkit-border-radius: none; + border-radius: none; + border: none; + background: none; + margin: 0; + padding-left: 4px; + padding-right: 0; +} +form .error, +form .error-message { + color: #9E2424; + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + text-shadow: none; +} + +/** Notices and Errors **/ +.message { + clear: both; + color: #fff; + font-size: 140%; + font-weight: bold; + margin: 0 0 1em 0; + padding: 5px; +} + +.success, +.message, +.cake-error, +.cake-debug, +.notice, +p.error, +.error-message { + background: #ffcc00; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(top, #ffcc00, #E6B800); + background-image: -ms-linear-gradient(top, #ffcc00, #E6B800); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ffcc00), to(#E6B800)); + background-image: -webkit-linear-gradient(top, #ffcc00, #E6B800); + background-image: -o-linear-gradient(top, #ffcc00, #E6B800); + background-image: linear-gradient(top, #ffcc00, #E6B800); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + border: 1px solid rgba(0, 0, 0, 0.2); + margin-bottom: 18px; + padding: 7px 14px; + color: #404040; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); +} +.success, +.message, +.cake-error, +p.error, +.error-message { + clear: both; + color: #fff; + background: #c43c35; + border: 1px solid rgba(0, 0, 0, 0.5); + background-repeat: repeat-x; + background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); + background-image: linear-gradient(top, #ee5f5b, #c43c35); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3); +} +.success { + clear: both; + color: #fff; + border: 1px solid rgba(0, 0, 0, 0.5); + background: #3B8230; + background-repeat: repeat-x; + background-image: -webkit-gradient(linear, left top, left bottom, from(#76BF6B), to(#3B8230)); + background-image: -webkit-linear-gradient(top, #76BF6B, #3B8230); + background-image: -moz-linear-gradient(top, #76BF6B, #3B8230); + background-image: -ms-linear-gradient(top, #76BF6B, #3B8230); + background-image: -o-linear-gradient(top, #76BF6B, #3B8230); + background-image: linear-gradient(top, #76BF6B, #3B8230); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3); +} +p.error { + font-family: Monaco, Consolas, Courier, monospace; + font-size: 120%; + padding: 0.8em; + margin: 1em 0; +} +p.error em { + font-weight: normal; + line-height: 140%; +} +.notice { + color: #000; + display: block; + font-size: 120%; + padding: 0.8em; + margin: 1em 0; +} +.success { + color: #fff; +} + +/** Actions **/ +.actions ul { + margin: 0; + padding: 0; +} +.actions li { + margin:0 0 0.5em 0; + list-style-type: none; + white-space: nowrap; + padding: 0; +} +.actions ul li a { + font-weight: normal; + display: block; + clear: both; +} + +/* Buttons and button links */ +input[type=submit], +.actions ul li a, +.actions a { + font-weight:normal; + padding: 4px 8px; + background: #dcdcdc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#dcdcdc)); + background-image: -webkit-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -moz-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -ms-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -o-linear-gradient(top, #fefefe, #dcdcdc); + background-image: linear-gradient(top, #fefefe, #dcdcdc); + color:#333; + border:1px solid #bbb; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + text-decoration: none; + text-shadow: #fff 0px 1px 0px; + min-width: 0; + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0px 1px 1px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0px 1px 1px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0px 1px 1px rgba(0, 0, 0, 0.2); + -webkit-user-select: none; + user-select: none; +} +.actions ul li a:hover, +.actions a:hover { + background: #ededed; + border-color: #acacac; + text-decoration: none; +} +input[type=submit]:active, +.actions ul li a:active, +.actions a:active { + background: #eee; + background-image: -webkit-gradient(linear, left top, left bottom, from(#dfdfdf), to(#eee)); + background-image: -webkit-linear-gradient(top, #dfdfdf, #eee); + background-image: -moz-linear-gradient(top, #dfdfdf, #eee); + background-image: -ms-linear-gradient(top, #dfdfdf, #eee); + background-image: -o-linear-gradient(top, #dfdfdf, #eee); + background-image: linear-gradient(top, #dfdfdf, #eee); + text-shadow: #eee 0px 1px 0px; + -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + border-color: #aaa; + text-decoration: none; +} + +/** Related **/ +.related { + clear: both; + display: block; +} + +/** Debugging **/ +pre { + color: #000; + background: #f0f0f0; + padding: 15px; + -moz-box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); +} +.cake-debug-output { + padding: 0; + position: relative; +} +.cake-debug-output > span { + position: absolute; + top: 5px; + right: 5px; + background: rgba(255, 255, 255, 0.3); + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + padding: 5px 6px; + color: #000; + display: block; + float: left; + -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.25), 0 1px 0 rgba(255, 255, 255, 0.5); + -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.25), 0 1px 0 rgba(255, 255, 255, 0.5); + box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.25), 0 1px 0 rgba(255, 255, 255, 0.5); + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8); +} +.cake-debug, +.cake-error { + font-size: 16px; + line-height: 20px; + clear: both; +} +.cake-error > a { + text-shadow: none; +} +.cake-error { + white-space: normal; +} +.cake-stack-trace { + background: rgba(255, 255, 255, 0.7); + color: #333; + margin: 10px 0 5px 0; + padding: 10px 10px 0 10px; + font-size: 120%; + line-height: 140%; + overflow: auto; + position: relative; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +.cake-stack-trace a { + text-shadow: none; + background: rgba(255, 255, 255, 0.7); + padding: 5px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; + margin: 0px 4px 10px 2px; + font-family: sans-serif; + font-size: 14px; + line-height: 14px; + display: inline-block; + text-decoration: none; + -moz-box-shadow: inset 0px 1px 0 rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0px 1px 0 rgba(0, 0, 0, 0.3); + box-shadow: inset 0px 1px 0 rgba(0, 0, 0, 0.3); +} +.cake-code-dump pre { + position: relative; + overflow: auto; +} +.cake-context { + margin-bottom: 10px; +} +.cake-stack-trace pre { + color: #000; + background-color: #F0F0F0; + margin: 0px 0 10px 0; + padding: 1em; + overflow: auto; + text-shadow: none; +} +.cake-stack-trace li { + padding: 10px 5px 0px; + margin: 0 0 4px 0; + font-family: monospace; + border: 1px solid #bbb; + -moz-border-radius: 4px; + -wekbkit-border-radius: 4px; + border-radius: 4px; + background: #dcdcdc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#dcdcdc)); + background-image: -webkit-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -moz-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -ms-linear-gradient(top, #fefefe, #dcdcdc); + background-image: -o-linear-gradient(top, #fefefe, #dcdcdc); + background-image: linear-gradient(top, #fefefe, #dcdcdc); +} +/* excerpt */ +.cake-code-dump pre, +.cake-code-dump pre code { + clear: both; + font-size: 12px; + line-height: 15px; + margin: 4px 2px; + padding: 4px; + overflow: auto; +} +.cake-code-dump .code-highlight { + display: block; + background-color: rgba(255, 255, 0, 0.5); +} +.code-coverage-results div.code-line { + padding-left:5px; + display:block; + margin-left:10px; +} +.code-coverage-results div.uncovered span.content { + background:#ecc; +} +.code-coverage-results div.covered span.content { + background:#cec; +} +.code-coverage-results div.ignored span.content { + color:#aaa; +} +.code-coverage-results span.line-num { + color:#666; + display:block; + float:left; + width:20px; + text-align:right; + margin-right:5px; +} +.code-coverage-results span.line-num strong { + color:#666; +} +.code-coverage-results div.start { + border:1px solid #aaa; + border-width:1px 1px 0px 1px; + margin-top:30px; + padding-top:5px; +} +.code-coverage-results div.end { + border:1px solid #aaa; + border-width:0px 1px 1px 1px; + margin-bottom:30px; + padding-bottom:5px; +} +.code-coverage-results div.realstart { + margin-top:0px; +} +.code-coverage-results p.note { + color:#bbb; + padding:5px; + margin:5px 0 10px; + font-size:10px; +} +.code-coverage-results span.result-bad { + color: #a00; +} +.code-coverage-results span.result-ok { + color: #fa0; +} +.code-coverage-results span.result-good { + color: #0a0; +} + +/** Elements **/ +#url-rewriting-warning { + display:none; +} diff --git a/app/webroot/img/cake.icon.png b/app/webroot/img/cake.icon.png new file mode 100644 index 0000000000000000000000000000000000000000..394fa42d5131cdc7b0b1246af93b92179f3c887e GIT binary patch literal 943 zcmV;g15o^lP)9q)cr7> zIGsQFGn3| zCzs2iP$-yfVPOGVTU&6sT(-5fwHb2tVsLP9#{Vr9Ct?R7q(rf?v2A5#W$OI=e1YUJ zQ1YRnA&iWSQ1XYAm__>aYb6XIhMiYVD+-z8_pYi6+CsH{*^m;vOjqvbr=H&DFkeqxHQBh$Scsoy0Glw(T zsaSG*ok62V;~yXYNgP*DUw;o98^+0@vGFb{HC+As}XJ=;xg=B7N_;-mKbHH{|lXs_o+aPcs5~J?s%^P2Odb)Uz z$GvY6^!N9(C2-h?28B$qx7%_yHnt2eU%nQ0qThbl6a_+b)EirjBgQ`g1_07Fr&6R? RzIgxu002ovPDHLkV1mdlwUYn< literal 0 HcmV?d00001 diff --git a/app/webroot/img/cake.power.gif b/app/webroot/img/cake.power.gif new file mode 100644 index 0000000000000000000000000000000000000000..8f8d570a2e24d86f0ad7730ee8f2435fd49f152c GIT binary patch literal 201 zcmV;)05<&ZTq0L2I(c1A@d@rg`ENj#vn zcl`yi#iKX*jb2F7vd0WQgUq5Tw}Jp}g+ZnCeBY3dYNI+m71%bHRfx4UCkD2th(Q*@ zmd5r+MJNYn7MP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0006>Nklb9w)H??3Zq{to|uwe}MC z?e+bSKTh?^`{wD^wWPb{@}CHTKzw?6`$N5PyyB2n?^xO1cT3;J75nFbCC72*cXxMX zxm>nuPu5Jmal8UN_lgUheaAk$l{jyl3&KrcBcIP#g%B8HC>Dz>KUn6j_Z4&V8GCzs z-A1EvJqQBvqUM>*;2yA1EEd0ylNIZ(i;!35`Jv=;2ffT?2Hm9ejw-*-|nVOoS zP$-bg<ZSNe~2d zIvrfsrBEnvbaVtD0RtdB&%Ev&{`8k5nAgpm!>J!T7(dIo(HhhYPE122Ukjo z)|xnuiK1xS*%(6!=07%mmpr3`|W=94{ zl7!J{gb;!>P0?DTlp>B}thHEcQAz=P&I}}wOVjjGmP3*xD5d^b*-V_DpQDs|1-u(S z2jBtm%*+h=eE!N=P)c!fasm(lgNzq|0@nKdKEvVguj1iwnEipLzl$$o0oV~jSl{>U o#KeU4ecuWpEU@#dehEJZ0P;*FSb&o|IsgCw07*qoM6N<$f;Xi=v;Y7A literal 0 HcmV?d00001 diff --git a/app/webroot/img/test-fail-icon.png b/app/webroot/img/test-fail-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d2f147ec4ef406186c967c17aa9d592a09336e GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8r2#%6u9q1Y_A@XXWnehT!0_XL%T5M{>kJI%85mYDFg#fm z@qmG069dC728KUx-uyQ(To^b!NZ6$XaAf}9Vfq#lclKVe|Fz`$^pf#C)( z?|%b>GYkxe7#Qw~i+_GseHzG+ka#R5b(MkPp}6=pPR{@T|KFMBG8O1x^O7LHU?7(a zz#tGgw;O20JWm(LkcwMA=Z`Zr83?dk2y7BE6=YUe|*dabSZF*mIx>v0bsK zhImj64Tcwn`A_+lH49lnMl*u*gGmJ>51|J|OF=yr*B8yJW~ec3o5lk`2g|wzshC-w ziSCMq2n+=S*%-Br(g+${U@~D`FhT&sxB*oN&;*cc$WcTT9wVtcsdKUucOmbP;||0x z#q3tLa6o9^vHKPIF?*Owqq5D^MpaVDQnH801Nwb#BL&(K7w$YlkeuS0|3?^KwXL|W zZgJvS!cr?^E>%Hd`MA%ASbAg$QJRhgw-~dTt8se0&H5u6>~Hz*7HSB4*!uq4vq;NjRN`wbRrY=k ztZO7Hf9`nx3i*;j@S2CmwzWRi?KpG$@ix}{S-*Q_F&XAfUVh5qiW8;W{?l*PWU~|& zXJF+?I7gWJwdv?FLs}6xsv$ajvf*#NUOnZcOK`I#;=J!d&L`LN;INb9hb~B}{sAr8 BX72z1 literal 0 HcmV?d00001 diff --git a/app/webroot/img/test-skip-icon.png b/app/webroot/img/test-skip-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..749771c9895a1abbb843f894540a43a5dc426b2c GIT binary patch literal 1207 zcmcgr`BM@I7-iGaX-3y=+tk$2=@1heYqQ2Fr&dQR+w#8F+EyJ=3j{$FBt!*70YwQk z&qPo`NyPiUZ$&My$ooDs54`YP_Mg~q=6i3xpWeLp=A+ozJW)A+`8)ssP_ce$=^$sd z|5jN+js`b>c9fGQ%E}Gp2=_suJ&|C5xi{PktY_`-=?iuMdwPc;I>BZDfC9koxs%l! zEKAYA?)3C@cTc8#*X_a+xXL52WNcR59{1Y|%sC@xAdC&dt3FBE?v&eJ(VET#C1aPfAbX9c(2(>>t!J=b9>Y}4{Vv&J?_v2@ z{Zeit+2+s~*}>tpx2alAc$vPpzT>hM@4;GBTU{^zhonf02G`{6!jDK79JiR5fMQk5Qsz~iA0Kwj3krEQBhIR(a{tNg-WH;XtbD^nAq4@I-MRD z7Z)EN&tNd#zkknUGFdEELPA1fV&aDnACi)il9Q8DQc_Y=Q`6GY($mvFe*Bn`k-=uO zGcz+e91fSu&C1Hk&d$!s$>H&Md_F%nH#aXYPaqI{`t&J3KVK*m78DeSM54mN!lI(0 z;^N|xl9JNW(z3F$^78VEii*n0%BrfW>gwv6nwr|$+Pb>B`uh5YhK9z*#-^sG=H}*> zmX_Am*0#2`_V)IUj*iaG&aSSm?(S}}SlrXo)7#tI*Vp&?^XD&LzV!F^4-52q~$`a_L84UdB=6VyZ=u9nnig?j|*#Wx)G#h*aePfzjUYo@M;4}Fb&*iuQMNlT)P zH1r_DmZyA5)S=yZ1P7iME0&D<)aAv^j SzbA!qhX89U8%v>u2jO4XOSDb^ literal 0 HcmV?d00001 diff --git a/app/webroot/test.php b/app/webroot/test.php index d1c1a9f..8c5cb98 100755 --- a/app/webroot/test.php +++ b/app/webroot/test.php @@ -2,29 +2,32 @@ /** * Web Access Frontend for TestSuite * - * PHP versions 4 and 5 + * PHP 5 * - * CakePHP(tm) Tests - * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * CakePHP(tm) Tests + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * - * Licensed under The Open Group Test Suite License - * Redistributions of files must retain the above copyright notice. + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice * - * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests - * @package cake - * @subpackage cake.cake.tests.libs + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html + * @package app.webroot * @since CakePHP(tm) v 1.2.0.4433 - * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + set_time_limit(0); ini_set('display_errors', 1); + /** * Use the DS to separate the directories in other defines */ - if (!defined('DS')) { - define('DS', DIRECTORY_SEPARATOR); - } +if (!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); +} + /** * These defines should only be edited if you have cake installed in * a directory layout other than the way it is distributed. @@ -35,23 +38,30 @@ * The full path to the directory which holds "app", WITHOUT a trailing DS. * */ - if (!defined('ROOT')) { - define('ROOT', dirname(dirname(dirname(__FILE__)))); - } +if (!defined('ROOT')) { + define('ROOT', dirname(dirname(dirname(__FILE__)))); +} + /** * The actual directory name for the "app". * */ - if (!defined('APP_DIR')) { - define('APP_DIR', basename(dirname(dirname(__FILE__)))); - } +if (!defined('APP_DIR')) { + define('APP_DIR', basename(dirname(dirname(__FILE__)))); +} + /** - * The absolute path to the "cake" directory, WITHOUT a trailing DS. + * The absolute path to the "Cake" directory, WITHOUT a trailing DS. * + * For ease of development CakePHP uses PHP's include_path. If you + * need to cannot modify your include_path, you can set this path. + * + * Leaving this constant undefined will result in it being defined in Cake/bootstrap.php + * + * The following line differs from its sibling + * /lib/Cake/Console/Templates/skel/webroot/test.php */ - if (!defined('CAKE_CORE_INCLUDE_PATH')) { - define('CAKE_CORE_INCLUDE_PATH', DS . 'home' . DS . 'cjsaylor' . DS . 'public_html' . DS . 'boxmeup' . DS . 'cakephp'); - } +//define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'lib'); /** * Editing below this line should not be necessary. @@ -64,31 +74,27 @@ if (!defined('WWW_ROOT')) { define('WWW_ROOT', dirname(__FILE__) . DS); } -if (!defined('CORE_PATH')) { - if (function_exists('ini_set') && ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get('include_path'))) { - define('APP_PATH', null); - define('CORE_PATH', null); - } else { - define('APP_PATH', ROOT . DS . APP_DIR . DS); - define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); - } -} -if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) { - trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR); -} -$corePath = App::core('cake'); -if (isset($corePath[0])) { - define('TEST_CAKE_CORE_INCLUDE_PATH', rtrim($corePath[0], DS) . DS); +if (!defined('CAKE_CORE_INCLUDE_PATH')) { + if (function_exists('ini_set')) { + ini_set('include_path', ROOT . DS . 'cakephp' . DS . 'lib' . PATH_SEPARATOR . ini_get('include_path')); + } + if (!include ('Cake' . DS . 'bootstrap.php')) { + $failed = true; + } } else { - define('TEST_CAKE_CORE_INCLUDE_PATH', CAKE_CORE_INCLUDE_PATH); + if (!include (CAKE_CORE_INCLUDE_PATH . DS . 'Cake' . DS . 'bootstrap.php')) { + $failed = true; + } +} +if (!empty($failed)) { + trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR); } if (Configure::read('debug') < 1) { - die(__('Debug setting does not allow access to this url.')); + throw new NotFoundException(__d('cake_dev', 'Debug setting does not allow access to this url.')); } -require_once CAKE . 'TestSuite' . DS . 'cake_test_suite_dispatcher.php'; +require_once CAKE . 'TestSuite' . DS . 'CakeTestSuiteDispatcher.php'; -$Dispatcher = new CakeTestSuiteDispatcher(); -$Dispatcher->dispatch(); +CakeTestSuiteDispatcher::run(); diff --git a/bin/gen-security.php b/bin/gen-security.php new file mode 100644 index 0000000..1a5788f --- /dev/null +++ b/bin/gen-security.php @@ -0,0 +1,32 @@ + Date: Sat, 10 Aug 2013 18:37:46 -0400 Subject: [PATCH 02/29] Created authenticated application view. Users can revoke tokens here. --- app/Controller/UsersController.php | 20 ++++++++++++++-- app/Plugin/Api/Config/routes.php | 1 + app/Plugin/Api/Model/ApiUserApplication.php | 26 +++++++++++++++++++++ app/View/Elements/app/account.ctp | 1 + app/View/Users/auth.ctp | 21 +++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 app/View/Users/auth.ctp diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index e899117..a0ea1cd 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -101,7 +101,6 @@ public function logout() { } public function account() { - Configure::write('Feature.adsense', false); $this->layout = 'app'; if(!empty($this->request->data)) { $this->request->data['User']['id'] = $this->Auth->user('id'); @@ -121,6 +120,23 @@ public function account() { $secret_key = ClassRegistry::init('Api.ApiUser')->getSecretKey($api_key); $this->set(compact('api_key', 'secret_key')); } + + public function auth() { + $this->layout = 'app'; + $userApplication = ClassRegistry::init('Api.ApiUserApplication'); + $this->set('applications', $userApplication->getAllAuthenticatedApps($this->Auth->user('id'))); + } + + public function revoke($id) { + $this->layout = 'app'; + $userApplication = ClassRegistry::init('Api.ApiUserApplication'); + if ($userApplication->revokeTokenById($id)) { + $this->Session->setFlash(__('Successfully revoked token.'), 'notification/success'); + } else { + $this->Session->setFlash(__('Error revoking token.'), 'notification/error'); + } + $this->redirect(array('action' => 'auth')); + } public function forgot_password() { if(!empty($this->request->data)) { @@ -185,4 +201,4 @@ public function change_language($language = null) { } $this->redirect($this->referer()); } -} \ No newline at end of file +} diff --git a/app/Plugin/Api/Config/routes.php b/app/Plugin/Api/Config/routes.php index cc00ac3..9dc4420 100644 --- a/app/Plugin/Api/Config/routes.php +++ b/app/Plugin/Api/Config/routes.php @@ -3,6 +3,7 @@ Router::mapResources(array( 'Api.containers', 'Api.conainer_items', + 'Api.locations', 'Api.users' ), array( 'id' => '[a-z0-9-]+' diff --git a/app/Plugin/Api/Model/ApiUserApplication.php b/app/Plugin/Api/Model/ApiUserApplication.php index efd0dd6..894de2a 100644 --- a/app/Plugin/Api/Model/ApiUserApplication.php +++ b/app/Plugin/Api/Model/ApiUserApplication.php @@ -84,4 +84,30 @@ public function getTokenByUserId($userId, $name) { return $token; } + /** + * Get all authenticated applications + * + * @param integer $userId + * @return array + */ + public function getAllAuthenticatedApps($userId) { + $results = $this->find('all', array( + 'conditions' => array( + 'ApiUserApplication.user_id' => $userId + ), + 'contain' => false + )); + return $results; + } + + /** + * Removes an auth token by ID. + * + * @param integer $id + * @return boolean + */ + public function revokeTokenById($id) { + return $this->delete($id); + } + } diff --git a/app/View/Elements/app/account.ctp b/app/View/Elements/app/account.ctp index d91deae..a275504 100755 --- a/app/View/Elements/app/account.ctp +++ b/app/View/Elements/app/account.ctp @@ -6,6 +6,7 @@ diff --git a/app/View/Users/auth.ctp b/app/View/Users/auth.ctp new file mode 100644 index 0000000..7e0c37b --- /dev/null +++ b/app/View/Users/auth.ctp @@ -0,0 +1,21 @@ +

+ +

+ + + + + + + + + + + + + + + + +
 
+ From d3f246ad8a6d9019939f916d0f7ce05e66253ff9 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 18:38:00 -0400 Subject: [PATCH 03/29] Removed outdated api config schema. --- app/Plugin/Api/Config/Schema/schema.php | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100755 app/Plugin/Api/Config/Schema/schema.php diff --git a/app/Plugin/Api/Config/Schema/schema.php b/app/Plugin/Api/Config/Schema/schema.php deleted file mode 100755 index 7aeaef6..0000000 --- a/app/Plugin/Api/Config/Schema/schema.php +++ /dev/null @@ -1,24 +0,0 @@ - array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), - 'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'), - 'api_key' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'), - 'is_active' => array('type' => 'boolean', 'null' => false, 'default' => '1'), - 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'user_id' => array('column' => 'user_id', 'unique' => 0), 'api_key' => array('column' => 'api_key', 'unique' => 0)), - 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_unicode_ci', 'engine' => 'InnoDB') - ); -} -?> \ No newline at end of file From d08bbdd6f2f98fc8e135b941f9163b04e69255bd Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 20:39:18 -0400 Subject: [PATCH 04/29] Updated auth component to support the new oauth token. --- .../Api/Controller/ApiAppController.php | 7 +- .../Controller/Component/ApiAuthComponent.php | 52 ++---------- app/Plugin/Api/Model/ApiUserApplication.php | 29 +++++++ .../Case/Model/ApiUserApplicationTest.php | 84 +++++++++++++------ .../Fixture/ApiUserApplicationFixture.php | 6 ++ 5 files changed, 105 insertions(+), 73 deletions(-) diff --git a/app/Plugin/Api/Controller/ApiAppController.php b/app/Plugin/Api/Controller/ApiAppController.php index 6ce4cf1..00164b4 100644 --- a/app/Plugin/Api/Controller/ApiAppController.php +++ b/app/Plugin/Api/Controller/ApiAppController.php @@ -2,7 +2,7 @@ class ApiAppController extends AppController { - public $uses = array('Api.ApiUser'); + public $uses = array('Api.ApiUser', 'Api.ApiUserApplication'); public $viewClass = 'Json'; @@ -24,10 +24,7 @@ public function jsonOutput($data) { } public function getUserId() { - if (empty($this->userId)) { - $this->userId = $this->ApiUser->getUserId($this->request->data['ApiUser']['api_key']); - } - return $this->userId; + return $this->request->data['User']['id']; } } diff --git a/app/Plugin/Api/Controller/Component/ApiAuthComponent.php b/app/Plugin/Api/Controller/Component/ApiAuthComponent.php index 027b0f3..3e236a4 100644 --- a/app/Plugin/Api/Controller/Component/ApiAuthComponent.php +++ b/app/Plugin/Api/Controller/Component/ApiAuthComponent.php @@ -2,7 +2,6 @@ class ApiAuthComponent extends Component { - const TIMESTAMP_THRESHOLD = 60; const AUTHENTICATION_HEADER = 'Authentication'; const AUTHENTICATION_TYPE = 'BoxmeupAPI'; @@ -62,7 +61,7 @@ protected function isAllowed() { * @return void */ protected function authenticate() { - $requiredHeaderParams = array('api_key', 'now', 'hash'); + $requiredHeaderParams = array('token'); // Parse authentication headers $authHeader = CakeRequest::header(static::AUTHENTICATION_HEADER); @@ -75,51 +74,18 @@ protected function authenticate() { } // Check API key - $userSecretKey = $this->controller->ApiUser->getSecretKey($parsedAuthHeader['api_key']); - if (empty($userSecretKey)) { - throw new NotAuthorizedException('Invalid api key.'); + try { + $user = $this->controller->ApiUserApplication->getUserByToken($parsedAuthHeader['token']); + } catch (NotFoundException $e) { + throw new NotAuthorizedException($e->getMessage()); } - // Validate time - if (!$this->isValidTime($parsedAuthHeader['now'])) { - throw new NotAuthorizedException('[now] parameter is not within threshold.'); - } - - $params = $this->controller->request->isGet() ? - $this->controller->request->query : - $this->controller->data; - ksort($params); - - // Validate hash code - $encodedParams = version_compare(PHP_VERSION, '5.4.0', '>=') ? - http_build_query($params, null, null, PHP_QUERY_RFC3986) : - str_replace('+', '%20', http_build_query($params)); // 5.3 hack - $code = sha1( - '/' . $this->controller->request->url . '?' . - $encodedParams . - $parsedAuthHeader['now'] . - $userSecretKey - ); - if ($code !== $parsedAuthHeader['hash']) { - throw new NotAuthorizedException('HMAC code signature does not match expected signature.'); - } - - // Store the parner into the request to be reused by the app - $this->controller->request->data('ApiUser.api_key', $parsedAuthHeader['api_key']); + // Store the user information in the request + $this->controller->request->data('User', $user); } /** - * Determines if passed timestamp is within threshold. - * - * @param integer $timestamp - * @return boolean - */ - protected function isValidTime($timestamp) { - return abs(time() - $timestamp) <= static::TIMESTAMP_THRESHOLD; - } - - /** - * Parse the ZumbaAPI authentication header. + * Parse the BoxmeupAPI authentication header. * * @param string $header * @return array @@ -152,4 +118,4 @@ public function __construct($message, $code = 401) { parent::__construct($message, $code); } -} \ No newline at end of file +} diff --git a/app/Plugin/Api/Model/ApiUserApplication.php b/app/Plugin/Api/Model/ApiUserApplication.php index 894de2a..53e6424 100644 --- a/app/Plugin/Api/Model/ApiUserApplication.php +++ b/app/Plugin/Api/Model/ApiUserApplication.php @@ -110,4 +110,33 @@ public function revokeTokenById($id) { return $this->delete($id); } + /** + * Get user information based on auth token. + * + * @param string $token + * @return array + */ + public function getUserByToken($token) { + $key = $token . '_auth_token'; + if (!$user = Cache::read($key)) { + $result = $this->find('first', array( + 'conditions' => array( + 'ApiUserApplication.token' => $token + ), + 'contain' => array( + 'User' => array( + 'id', 'email', 'uuid' + ) + ) + )); + if (empty($result['User'])) { + throw new NotFoundException('Invalid token.'); + } + $user = $result['User']; + Cache::write($key, $user); + } + + return $user; + } + } diff --git a/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php b/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php index 44acdd7..42a2abc 100644 --- a/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php +++ b/app/Plugin/Api/Test/Case/Model/ApiUserApplicationTest.php @@ -7,11 +7,11 @@ */ class ApiUserApplicationTest extends CakeTestCase { -/** - * Fixtures - * - * @var array - */ + /** + * Fixtures + * + * @var array + */ public $fixtures = array( 'plugin.api.api_user_application', 'plugin.api.user', @@ -20,22 +20,22 @@ class ApiUserApplicationTest extends CakeTestCase { 'plugin.api.container_item' ); -/** - * setUp method - * - * @return void - */ + /** + * setUp method + * + * @return void + */ public function setUp() { parent::setUp(); $this->ApiUserApplication = ClassRegistry::init('Api.ApiUserApplication'); $this->testAppToken = $this->ApiUserApplication->createApplication('Test App', '3'); } -/** - * tearDown method - * - * @return void - */ + /** + * tearDown method + * + * @return void + */ public function tearDown() { unset($this->ApiUserApplication); unset($this->testAppToken); @@ -43,24 +43,58 @@ public function tearDown() { parent::tearDown(); } -/** - * testCreateApplication method - * - * @return void - */ + /** + * testCreateApplication method + * + * @return void + */ public function testCreateApplication() { $result = $this->ApiUserApplication->createApplication('Test New App 1', '3'); $this->assertNotEmpty($result); $this->assertEquals($result, $this->ApiUserApplication->createApplication('Test New App 1', '3')); } -/** - * testGetTokenByUserId method - * - * @return void - */ + /** + * testGetTokenByUserId method + * + * @return void + */ public function testGetTokenByUserId() { $this->assertEquals($this->testAppToken, $this->ApiUserApplication->getTokenByUserId('3', 'Test App')); } + public function testGetAllAuthenticatedApps() { + $result = $this->ApiUserApplication->getAllAuthenticatedApps('3'); + $this->assertCount(2, $result); + $expected = array( + 'ApiUserApplication' => array( + 'id' => '1', + 'user_id' => '3', + 'name' => 'Test Fixture App', + 'token' => 'testtoken', + 'created' => null + ) + ); + $this->assertEquals($expected, $result[0]); + } + + public function testGetUserByToken() { + $result = $this->ApiUserApplication->getUserByToken('testtoken'); + $expected = array( + 'id' => '3', + 'email' => 'test@test.com', + 'uuid' => '52068a2c-00c4-40ba-baad-085321210046' + ); + $this->assertEquals($expected, $result); + } + + /** + * @expectedException NotFoundException + * @return void + */ + public function testRevokeTokenById() { + $this->assertTrue($this->ApiUserApplication->revokeTokenById(1)); + $this->ApiUserApplication->getUserByToken('testtoken'); + } + } diff --git a/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php b/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php index 4c3cb27..90a4972 100644 --- a/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php +++ b/app/Plugin/Api/Test/Fixture/ApiUserApplicationFixture.php @@ -29,6 +29,12 @@ class ApiUserApplicationFixture extends CakeTestFixture { * @var array */ public $records = array( + array( + 'id' => 1, + 'user_id' => 3, + 'name' => 'Test Fixture App', + 'token' => 'testtoken' + ) ); } From 32392c00d864a7ff823f1ff7c548eb92c9211459 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:10:13 -0400 Subject: [PATCH 05/29] Changed how the API is routed slightly. --- app/Config/routes.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Config/routes.php b/app/Config/routes.php index d4ae947..36f99cd 100755 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -1,6 +1,13 @@ '[a-z0-9-]+' +)); Router::parseExtensions(); + /** * Static pages: * /slug => template name @@ -23,6 +30,9 @@ // Application Router::connect('/dashboard', array('controller' => 'containers', 'action' => 'dashboard')); +// API +Router::connect('/api/:controller/:action/*', array('plugin' => 'api')); + // Fallback Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); From 1ad7221662e2e120be0684459995ca27fc284292 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:11:21 -0400 Subject: [PATCH 06/29] Removed api plugin routing. --- app/Plugin/Api/Config/routes.php | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 app/Plugin/Api/Config/routes.php diff --git a/app/Plugin/Api/Config/routes.php b/app/Plugin/Api/Config/routes.php deleted file mode 100644 index 9dc4420..0000000 --- a/app/Plugin/Api/Config/routes.php +++ /dev/null @@ -1,10 +0,0 @@ - '[a-z0-9-]+' -)); From 54541f151c4de734f66bb89f436eabd0a15aed5a Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:21:07 -0400 Subject: [PATCH 07/29] Fixed additional api routing. --- app/Config/routes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Config/routes.php b/app/Config/routes.php index 36f99cd..44d7ae3 100755 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -31,6 +31,7 @@ Router::connect('/dashboard', array('controller' => 'containers', 'action' => 'dashboard')); // API +Router::connect('/api/:controller', array('plugin' => 'api', 'action' => 'index')); Router::connect('/api/:controller/:action/*', array('plugin' => 'api')); // Fallback From bb4c6ceee6a571060be4620a6c33a8e31ed1394b Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:21:40 -0400 Subject: [PATCH 08/29] Better api plugin detection in api exception renderer. --- app/Plugin/Api/Lib/Error/ApiExceptionRenderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Plugin/Api/Lib/Error/ApiExceptionRenderer.php b/app/Plugin/Api/Lib/Error/ApiExceptionRenderer.php index 86e16ae..796d97a 100644 --- a/app/Plugin/Api/Lib/Error/ApiExceptionRenderer.php +++ b/app/Plugin/Api/Lib/Error/ApiExceptionRenderer.php @@ -5,14 +5,14 @@ class ApiExceptionRenderer extends ExceptionRenderer { public function error400($error) { - if (Router::currentRoute()->options['plugin']) { + if (Router::currentRoute()->defaults['plugin'] == 'api') { $this->controller->viewClass = 'Json'; } parent::error400($error); } public function error500($error) { - if (Router::currentRoute()->options['plugin']) { + if (Router::currentRoute()->defaults['plugin'] == 'api') { $this->controller->viewClass = 'Json'; } parent::error500($error); From 02ad7181a89de222ad70c4b00eea0425da224de5 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:22:16 -0400 Subject: [PATCH 09/29] Updated api container edit response for consistency. --- app/Plugin/Api/Controller/ContainersController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Plugin/Api/Controller/ContainersController.php b/app/Plugin/Api/Controller/ContainersController.php index 15f37af..fd47ac4 100644 --- a/app/Plugin/Api/Controller/ContainersController.php +++ b/app/Plugin/Api/Controller/ContainersController.php @@ -53,7 +53,8 @@ public function edit($slug = null) { if(!$result = $this->Container->save($data)) { throw new BadRequestException(); } - unset($result['Container']['id'], $result['Container']['user_id'], $result['Container']['location_id'], $result['Location']); + unset($result['Container']['id'], $result['Container']['user_id'], $result['Container']['location_id']); + $result['Location'] = array_intersect_key($result['Location'], array('uuid' => true, 'name' => true)); $result['Container']['container_item_count'] = (int)$result['Container']['container_item_count']; $this->jsonOutput($result); } @@ -103,4 +104,4 @@ public function search() { 'total' => $this->request->params['paging']['ContainerItem']['count'] ); } -} \ No newline at end of file +} From a5a86bb258671dc069ac03e7ecb17c4b0b8aca9c Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 22:54:16 -0400 Subject: [PATCH 10/29] Revoking tokens now remove related caches. --- app/Plugin/Api/Model/ApiUserApplication.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/Plugin/Api/Model/ApiUserApplication.php b/app/Plugin/Api/Model/ApiUserApplication.php index 53e6424..675972a 100644 --- a/app/Plugin/Api/Model/ApiUserApplication.php +++ b/app/Plugin/Api/Model/ApiUserApplication.php @@ -107,6 +107,19 @@ public function getAllAuthenticatedApps($userId) { * @return boolean */ public function revokeTokenById($id) { + $application = $this->find('first', array( + 'conditions' => array( + 'ApiUserApplication.id' => $id + ), + 'contain' => array( + 'ApiUserApplication' + ) + )); + if (empty($application)) { + return false; + } + Cache::delete($application['ApiUserApplication']['token'] . '_auth_token'); + Cache::delete($application['ApiUserApplication']['user_id'] . $application['ApiUserApplication']['name'] . '_auth_token'); return $this->delete($id); } From d0f47ef6e49b4fdab2a82f6140ae21f699ed74ca Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 23:41:50 -0400 Subject: [PATCH 11/29] Removed reliance on table for feedback. --- app/Plugin/Feedback/Model/Feedback.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Plugin/Feedback/Model/Feedback.php b/app/Plugin/Feedback/Model/Feedback.php index 506d0b9..e18452c 100755 --- a/app/Plugin/Feedback/Model/Feedback.php +++ b/app/Plugin/Feedback/Model/Feedback.php @@ -8,7 +8,7 @@ class Feedback extends FeedbackAppModel { public $name = 'Feedback'; - public $useTable = 'feedback'; + public $useTable = false; public $validate = array( 'message' => array( @@ -56,4 +56,4 @@ public function githubIssue($type, $title, $body) { $body = json_decode($result->body, true); return $body['html_url']; } -} \ No newline at end of file +} From 405cce13e7e217583c0070bdcb0446c383a538bf Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Sat, 10 Aug 2013 23:42:49 -0400 Subject: [PATCH 12/29] Started API documentation. --- app/Config/routes.php | 3 ++- app/View/Elements/app/footer.ctp | 3 +++ app/View/Elements/footer.ctp | 2 +- app/View/Layouts/default.ctp | 2 +- app/View/Pages/developer.ctp | 25 +++++++++++++++++++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 app/View/Pages/developer.ctp diff --git a/app/Config/routes.php b/app/Config/routes.php index 44d7ae3..24e58d7 100755 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -15,7 +15,8 @@ $static_pages = array( '' => 'home', 'terms' => 'terms', - 'privacy' => 'privacy' + 'privacy' => 'privacy', + 'developer' => 'developer' ); foreach($static_pages as $slug => $page) { Router::connect('/'.$slug, array('controller' => 'pages', 'action' => 'display', $page)); diff --git a/app/View/Elements/app/footer.ctp b/app/View/Elements/app/footer.ctp index c5893ac..6710d7f 100755 --- a/app/View/Elements/app/footer.ctp +++ b/app/View/Elements/app/footer.ctp @@ -4,6 +4,9 @@ __('Privacy Policy') => '/privacy', __('Status') => 'http://status.boxmeupapp.com' ); + if (Configure::read('Feature.api')) { + $footer_links[__('API')] = '/developer'; + } ?>