diff --git a/api/classes/model.php b/api/classes/model.php index c85a23c..1e52299 100644 --- a/api/classes/model.php +++ b/api/classes/model.php @@ -3,19 +3,28 @@ class Model { const ENCRYPTED_PROPERTIES = []; const PLAIN_PROPERTIES = []; + const PROOF_KEY_KNOWLEDGE = 'validate'; const SERVER_PROPERTIES = []; protected $data; + protected $proofKeyKnowledge; public function __construct() { - if(!defined('DATA_FOLDER')) { + if (!defined('DATA_FOLDER')) { throw new Exception('DATA_FOLDER is not defined'); } - if(!is_writable(DATA_FOLDER)) { + if (!is_writable(DATA_FOLDER)) { throw new Exception('DATA_FOLDER (' . DATA_FOLDER . ') is not writeable'); } + if ( + static::PROOF_KEY_KNOWLEDGE !== 'save' && + static::PROOF_KEY_KNOWLEDGE !== 'validate' + ) { + throw new Exception('PROOF_KEY_KNOWLEDGE must be "save" or "validate" but is ' . static::PROOF_KEY_KNOWLEDGE); + } + $this->data = new stdClass(); } @@ -102,6 +111,10 @@ class Model { throw new Exception ('getPath must be implemented by model'); } + private function getPathToKeyKnowledgeFile() { + return $this->getPollDir() . 'key_knowledge'; + } + /* * Checks if a json string is a proper SJCL encrypted message. * False if format is incorrect. @@ -168,7 +181,7 @@ class Model { catch (Exception $e) { // no poll with this id return false; - } + } $data = self::convertFromStorage($storageObject); @@ -181,6 +194,10 @@ class Model { $model->set($property, $data->$property); } + if (static::PROOF_KEY_KNOWLEDGE === 'save') { + $model->restoreKeyKnowledge($data); + } + if (method_exists($model, 'restoreHook')) { if ($model->restoreHook() === false) { return false; @@ -190,11 +207,34 @@ class Model { return $model; } + private function restoreKeyKnowledge() { + try { + $data = file_get_contents( + $this->getPathToKeyKnowledgeFile() + ); + + if ($data) { + return $data; + } + else { + throw new Exception('key knowledge file could not be read'); + } + } + catch (Exception $e) { + return false; + } + } + /* * save object to storage * gives back new id */ public function save() { + // proof key knowledge before save + if (static::PROOF_KEY_KNOWLEDGE === 'validate') { + $this->validateKeyKnowledge(); + } + // create dir for data if it does not exists $counter = 0; while (true) { @@ -238,9 +278,47 @@ class Model { // successfully run break; } + + // save key knowledge after poll is saved + if (static::PROOF_KEY_KNOWLEDGE === 'save') { + $this->saveKeyKnowledge(); + } + } + + private function saveKeyKnowledge() { + if ( + file_put_contents( + $this->getPathToKeyKnowledgeFile(), + $this->proofKeyKnowledge, + LOCK_EX + ) === false + ) { + throw new Exception('failed to save key knowledge'); + } } private function set($key, $value) { $this->data->$key = $value; } + + public function setProofKeyKnowledge($value) { + $this->proofKeyKnowledge = $value; + } + + private function validateKeyKnowledge() { + if (empty($this->proofKeyKnowledge)) { + throw new Exception('proof key knowledge is not set'); + } + + $keyKnowledge = $this->restoreKeyKnowledge(); + + if ( + $keyKnowledge !== false && + $keyKnowledge !== $this->proofKeyKnowledge + ) { + throw new Exception( + 'key knowledge not proofed: ' . $this->proofKeyKnowledge . ' does not equal ' . var_export($keyKnowledge, true) + ); + } + } } diff --git a/api/classes/poll.php b/api/classes/poll.php index aea1867..c53190b 100644 --- a/api/classes/poll.php +++ b/api/classes/poll.php @@ -26,6 +26,8 @@ class Poll extends model { 'version' ]; + const PROOF_KEY_KNOWLEDGE = 'save'; + const SERVER_PROPERTIES = [ 'serverExpirationDate' ]; @@ -72,8 +74,15 @@ class Poll extends model { } protected function getDir() { + if (($this->get('id') === null)) { + throw new Exception('id must be set before calling getDir'); + } return DATA_FOLDER . $this->get('id') . '/'; } + + protected function getPollDir() { + return $this->getDir(); + } protected function getPath() { return $this->getDir() . 'poll_data'; @@ -110,7 +119,8 @@ class Poll extends model { public static function isValidId($id) { $idCharacters = str_split($id); - return count(array_diff($idCharacters, str_split(self::ID_CHARACTERS))) === 0; + return strlen($id) === 10 && + count(array_diff($idCharacters, str_split(self::ID_CHARACTERS))) === 0; } protected function restoreHook() { diff --git a/api/classes/user.php b/api/classes/user.php index 60d8d43..25036e9 100644 --- a/api/classes/user.php +++ b/api/classes/user.php @@ -38,16 +38,28 @@ class User extends Model { } protected function getDir() { + return $this->getPollDir() . 'users/'; + } + + protected function getPollDir() { if ($this->get('poll') !== null) { $pollId = $this->get('poll'); } else { $pollId = explode('_', $this->get('id'))[0]; } - return DATA_FOLDER . $pollId . '/users/'; + + if (!Poll::isValidId($pollId)) { + throw new Exception('cound not get a valid id when getPollDir was called'); + } + + return DATA_FOLDER . $pollId . '/'; } - protected function getPath() { + protected function getPath() { + if (!self::isValidId($this->get('id'))) { + throw new Exception('no valid user id when getPath was called'); + } return $this->getDir() . explode('_', $this->get('id'))[1]; } diff --git a/api/index.php b/api/index.php index d2a1ae9..93b3361 100644 --- a/api/index.php +++ b/api/index.php @@ -55,6 +55,9 @@ $app->post('/polls', function() use ($app) { $app->request->getBody() )->poll ); + $poll->setProofKeyKnowledge( + $app->request->headers->get('X-Croodle-Proof-Key-Knowledge') + ); $poll->save(); $app->response->setBody( @@ -72,6 +75,9 @@ $app->post('/users', function() use ($app) { $app->request->getBody() )->user ); + $user->setProofKeyKnowledge( + $app->request->headers->get('X-Croodle-Proof-Key-Knowledge') + ); $user->save(); $app->response->setBody( diff --git a/api/tests/api/CreateAnotherUserCept.php b/api/tests/api/CreateAnotherUserCept.php index 584d6ab..2880915 100644 --- a/api/tests/api/CreateAnotherUserCept.php +++ b/api/tests/api/CreateAnotherUserCept.php @@ -1,19 +1,22 @@ wantTo('create a user'); +$I->haveHTTPHeader('X-Croodle-Proof-Key-Knowledge', $proofKeyKnowledge); $I->sendPOST('/users', $userJson); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); diff --git a/api/tests/api/CreatePollCept.php b/api/tests/api/CreatePollCept.php index ef8c90e..736ec2f 100644 --- a/api/tests/api/CreatePollCept.php +++ b/api/tests/api/CreatePollCept.php @@ -1,9 +1,11 @@ wantTo('create a poll'); +$I->haveHTTPHeader('X-Croodle-Proof-Key-Knowledge', $proofKeyKnowledge); $I->sendPOST('/polls', $pollJson); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); @@ -38,5 +40,15 @@ $I->seeResponseContainsJson( ); $I->dontSeeResponseJsonMatchesJsonPath( 'poll.serverExpirationDate', - 'serverExpirationDate is not in response.' + 'serverExpirationDate is not in response payload.' +); +$I->dontSeeResponseJsonMatchesJsonPath( + 'poll.proofKeyKnowledge', + 'proofKeyKnowledge is not in response payload.' +); + +\PHPUnit_Framework_Assert::assertEquals( + file_get_contents(TEST_DATA_DIR . $pollId . '/key_knowledge'), + $proofKeyKnowledge, + 'user array should be empty' ); diff --git a/api/tests/api/CreateUserCept.php b/api/tests/api/CreateUserCept.php index ae91ed5..0aff8d0 100644 --- a/api/tests/api/CreateUserCept.php +++ b/api/tests/api/CreateUserCept.php @@ -1,14 +1,18 @@ wantTo('create a user'); +$I->haveHTTPHeader('X-Croodle-Proof-Key-Knowledge', $proofKeyKnowledge); $I->sendPOST('/users', $userJson); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); diff --git a/api/tests/api/CreateUserFailsIfKeyKnowledgeHeaderIsNotSetCept.php b/api/tests/api/CreateUserFailsIfKeyKnowledgeHeaderIsNotSetCept.php new file mode 100644 index 0000000..838ccec --- /dev/null +++ b/api/tests/api/CreateUserFailsIfKeyKnowledgeHeaderIsNotSetCept.php @@ -0,0 +1,29 @@ +wantTo('see that create a new user fails if key knowledge header is not set'); +$I->sendPOST('/users', $userJson); +$I->seeResponseCodeIs(500); +$I->seeResponseIsJson(); + +try { + $result = file_get_contents($usersDir . '0'); +} +catch (Exception $e) { + $result = false; +} +\PHPUnit_Framework_Assert::assertFalse( + $result, + 'no user is saved to disc' +); diff --git a/api/tests/api/CreateUserFailsIfKeyKnowledgeIsNotProvedCept.php b/api/tests/api/CreateUserFailsIfKeyKnowledgeIsNotProvedCept.php new file mode 100644 index 0000000..51e4af7 --- /dev/null +++ b/api/tests/api/CreateUserFailsIfKeyKnowledgeIsNotProvedCept.php @@ -0,0 +1,30 @@ +wantTo('see that create a new user fails if key knowledge is wrong'); +$I->haveHTTPHeader('X-Croodle-Proof-Key-Knowledge', $wrongKeyKnowledge); +$I->sendPOST('/users', $userJson); +$I->seeResponseCodeIs(500); +$I->seeResponseIsJson(); + +try { + $result = file_get_contents($usersDir . '0'); +} +catch (Exception $e) { + $result = false; +} +\PHPUnit_Framework_Assert::assertFalse( + $result, + 'no user is saved to disc' +); diff --git a/api/tests/api/ExpiredPollGetsDeletedCept.php b/api/tests/api/ExpiredPollGetsDeletedCept.php index 6ad7ad2..16d45fb 100644 --- a/api/tests/api/ExpiredPollGetsDeletedCept.php +++ b/api/tests/api/ExpiredPollGetsDeletedCept.php @@ -1,6 +1,6 @@ wantTo('get an not existing poll'); -$I->sendGet('/polls/abcdEFGH12'); +$I->sendGet('/polls/notExistin'); $I->seeResponseCodeIs(404); $I->seeResponseEquals(''); diff --git a/api/tests/api/GetPollCept.php b/api/tests/api/GetPollCept.php index 0372038..0bb6094 100644 --- a/api/tests/api/GetPollCept.php +++ b/api/tests/api/GetPollCept.php @@ -1,5 +1,6 @@