diff --git a/backend/src/controllers/Credentials.php b/backend/src/controllers/Credentials.php index 73b99bf..c820f26 100644 --- a/backend/src/controllers/Credentials.php +++ b/backend/src/controllers/Credentials.php @@ -129,4 +129,46 @@ class Credentials return $res->withJson(['error' => 'No matching credential found.'], 404); } } + + public function put(Request $req, Response $res, array $args) + { + $body = $req->getParsedBody(); + + if ((array_key_exists('type', $body) && $body['type'] === 'key' && !array_key_exists('key', $body)) + || (array_key_exists('type', $body) && $body['type'] === 'password' && !array_key_exists('password', $body))) { + $this->logger->debug('One of the required fields is missing'); + return $res->withJson(['error' => 'One of the required fields is missing'], 422); + } + + $userId = $req->getAttribute('userId'); + $recordId = intval($args['recordId']); + $credentialId = intval($args['credentialId']); + + $ac = new \Operations\AccessControl($this->c); + if (!$ac->canAccessRecord($userId, $recordId)) { + $this->logger->info('User tries to update credential for record without permission.'); + return $res->withJson(['error' => 'You have no permissions for the given record.'], 403); + } + + $credentials = new \Operations\Credentials($this->c); + + $key = array_key_exists('key', $body) ? $body['key'] : null; + $password = array_key_exists('password', $body) ? $body['password'] : null; + $description = array_key_exists('description', $body) ? $body['description'] : null; + $type = array_key_exists('type', $body) ? $body['type'] : null; + + try { + $credentials->updateCredential($recordId, $credentialId, $description, $type, $key, $password); + return $res->withStatus(204); + } catch (\Exceptions\SemanticException $e) { + $this->logger->debug('User tries to update credential with wrong type.'); + return $res->withJson(['error' => 'The type is invalid.'], 400); + } catch (\Exceptions\InvalidKeyException $e) { + $this->logger->debug('User tries to update invalid credential key.'); + return $res->withJson(['error' => 'The provided key is invalid.'], 400); + } catch (\Exceptions\NotFoundException $e) { + $this->logger->debug('User tries to update not existent credential.'); + return $res->withJson(['error' => 'The provided credential does not exist.'], 404); + } + } } diff --git a/backend/src/operations/Credentials.php b/backend/src/operations/Credentials.php index a306a02..03a466e 100644 --- a/backend/src/operations/Credentials.php +++ b/backend/src/operations/Credentials.php @@ -2,6 +2,8 @@ namespace Operations; +use function Monolog\Handler\error_log; + require '../vendor/autoload.php'; /** @@ -181,4 +183,60 @@ class Credentials return $record; } + + /** + * Add a new credential + * + * @param $record Record for which this credential should be valid + * @param $credential Credential to update + * @param $description Description for this credential + * @param $type Type of the credential, can bei key or password + * @param $key Key if type is key, null otherwise + * @param $password Password if type was password, null otherwise + * + * @return array The new credential entry. + */ + public function updateCredential(int $record, int $credential, ? string $description, ? string $type, ? string $key, ? string $password) : array + { + if ($type === 'key') { + if (openssl_pkey_get_public($key) === false) { + throw new \Exceptions\InvalidKeyException(); + } + $secret = $key; + } elseif ($type === 'password') { + $secret = password_hash($password, PASSWORD_DEFAULT); + } elseif ($type === null) { + $secret = null; + } else { + throw new \Exceptions\SemanticException(); + } + + $this->db->beginTransaction(); + + $query = $this->db->prepare('SELECT id,record,description,type,security FROM remote WHERE id=:id AND record=:record'); + $query->bindValue(':id', $credential, \PDO::PARAM_INT); + $query->bindValue(':record', $record, \PDO::PARAM_INT); + $query->execute(); + + $record = $query->fetch(); + + if ($record === false) { + $this->db->rollBack(); + throw new \Exceptions\NotFoundException(); + } + + $description = $description !== null ? $description : $record['description']; + $type = $type !== null ? $type : $record['type']; + $secret = $secret !== null ? $secret : $record['security']; + + $query = $this->db->prepare('UPDATE remote SET description=:description,type=:type,security=:security'); + $query->bindValue(':description', $description); + $query->bindValue(':type', $type); + $query->bindValue(':security', $secret); + $query->execute(); + + $this->db->commit(); + + return $record; + } } diff --git a/backend/src/public/index.php b/backend/src/public/index.php index b561a47..feadcd1 100644 --- a/backend/src/public/index.php +++ b/backend/src/public/index.php @@ -46,6 +46,7 @@ $app->group('/v1', function () { $this->post('/records/{recordId}/credentials', '\Controllers\Credentials:postNew'); $this->delete('/records/{recordId}/credentials/{credentialId}', '\Controllers\Credentials:delete'); $this->get('/records/{recordId}/credentials/{credentialId}', '\Controllers\Credentials:getSingle'); + $this->put('/records/{recordId}/credentials/{credentialId}', '\Controllers\Credentials:put'); })->add('\Middlewares\Authentication'); }); diff --git a/backend/test/tests/credentials-crud.js b/backend/test/tests/credentials-crud.js index 7b4585f..138bd5b 100644 --- a/backend/test/tests/credentials-crud.js +++ b/backend/test/tests/credentials-crud.js @@ -124,6 +124,104 @@ test.run(async function () { type: 'password', }, 'Added password does not match.'); + //Update credential + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + type: 'key', + key: '-----BEGIN PUBLIC KEY-----\nMDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMTyWha8C93l2NAPMkLPZ2WnbkqWXOnH\no3RenmVJHn1tAgMBAAE=\n-----END PUBLIC KEY-----' + } + }); + + assert.equal(res.status, 204, 'Updating record should succeed.'); + + var res = await req({ + url: '/records/1/credentials/4', + method: 'get' + }); + + assert.equal(res.status, 200, 'Updated credential should be found.'); + assert.equal(res.data, { + id: 4, + description: 'Test Key', + type: 'key', + key: '-----BEGIN PUBLIC KEY-----\nMDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMTyWha8C93l2NAPMkLPZ2WnbkqWXOnH\no3RenmVJHn1tAgMBAAE=\n-----END PUBLIC KEY-----' + }, 'Updated key does not match.'); + + // Change type to password + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + description: 'Foo Bar', + type: 'password', + password: 'foo' + } + }); + + assert.equal(res.status, 204, 'Updating record should succeed.'); + + var res = await req({ + url: '/records/1/credentials/4', + method: 'get' + }); + + assert.equal(res.status, 200, 'Updated credential should be found.'); + assert.equal(res.data, { + id: 4, + description: 'Foo Bar', + type: 'password' + }, 'Added key does not match.'); + + //Test update fails + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + type: 'foo' + } + }); + assert.equal(res.status, 400, 'Invalid type should trigger error.'); + + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + type: 'key', + key: 'foo' + } + }); + assert.equal(res.status, 400, 'Invalid key should trigger error.'); + + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + type: 'key' + } + }); + assert.equal(res.status, 422, 'Missing key should trigger error.'); + + var res = await req({ + url: '/records/1/credentials/4', + method: 'put', + data: { + type: 'password' + } + }); + assert.equal(res.status, 422, 'Missing password should trigger error.'); + + var res = await req({ + url: '/records/1/credentials/100', + method: 'put', + data: { + description: 'foo' + } + }); + assert.equal(res.status, 404, 'Invalid credential should trigger error.'); + + //Delete entry var res = await req({ url: '/records/1/credentials/4',