From 5928203a0a280e3e582343057c6f37f0d4c6207e Mon Sep 17 00:00:00 2001 From: Lukas Metzger Date: Sat, 24 Mar 2018 14:48:49 +0100 Subject: [PATCH] Added POST /domains api --- backend/src/controllers/Domains.php | 44 +++++++- .../exceptions/AlreadyExistentException.php | 9 ++ backend/src/operations/Domains.php | 51 +++++++++ backend/src/public/index.php | 1 + backend/test/db.sql | 5 +- backend/test/testlib.js | 12 +-- backend/test/tests/domains-crud.js | 100 ++++++++++++++++++ backend/test/tests/domains-get.js | 2 +- backend/test/tests/session.js | 2 +- 9 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 backend/src/exceptions/AlreadyExistentException.php create mode 100644 backend/test/tests/domains-crud.js diff --git a/backend/src/controllers/Domains.php b/backend/src/controllers/Domains.php index 7cab32a..ccbe2cc 100644 --- a/backend/src/controllers/Domains.php +++ b/backend/src/controllers/Domains.php @@ -13,17 +13,17 @@ class Domains private $logger; /** @var \Slim\Container */ - private $container; + private $c; public function __construct(\Slim\Container $c) { $this->logger = $c->logger; - $this->container = $c; + $this->c = $c; } public function getList(Request $req, Response $res, array $args) { - $domains = new \Operations\Domains($this->container); + $domains = new \Operations\Domains($this->c); $paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize')); $query = $req->getQueryParam('query'); @@ -39,4 +39,42 @@ class Domains 'results' => $results ], 200); } + + public function postNew(Request $req, Response $res, array $args) + { + $ac = new \Operations\AccessControl($this->c); + if (!$ac->isAdmin($req->getAttribute('userId'))) { + $this->logger->info('Non admin user tries to add domain'); + return $res->withJson(['error' => 'You must be admin to use this feature'], 403); + } + + $body = $req->getParsedBody(); + + if (!array_key_exists('name', $body) || + !array_key_exists('type', $body) || ($body['type'] === 'SLAVE' && !array_key_exists('master', $body))) { + $this->logger->debug('One of the required fields is missing'); + return $res->withJson(['error' => 'One of the required fields is missing'], 422); + } + + $name = $body['name']; + $type = $body['type']; + $master = isset($body['master']) ? $body['master'] : null; + + if (!in_array($type, ['MASTER', 'NATIVE', 'SLAVE'])) { + $this->logger->info('Invalid type for new domain', ['type' => $type]); + return $res->withJson(['error' => 'Invalid type allowed are MASTER, NATIVE and SLAVE'], 422); + } + + $domains = new \Operations\Domains($this->c); + + try { + $result = $domains->addDomain($name, $type, $master); + + $this->logger->debug('Domain was added', $result); + return $res->withJson($result, 201); + } catch (\Exceptions\AlreadyExistentException $e) { + $this->logger->debug('Zone with name ' . $name . ' already exists.'); + return $res->withJson(['error' => 'Zone with name ' . $name . ' already exists.'], 409); + } + } } diff --git a/backend/src/exceptions/AlreadyExistentException.php b/backend/src/exceptions/AlreadyExistentException.php new file mode 100644 index 0000000..c3b1020 --- /dev/null +++ b/backend/src/exceptions/AlreadyExistentException.php @@ -0,0 +1,9 @@ +db->beginTransaction(); + + $query = $this->db->prepare('SELECT id FROM domains WHERE name=:name'); + $query->bindValue(':name', $name, \PDO::PARAM_STR); + $query->execute(); + + $record = $query->fetch(); + + if ($record !== false) { // Domain already exists + $this->db->rollBack(); + throw new \Exceptions\AlreadyExistentException(); + } + + if ($type === 'SLAVE') { + $query = $this->db->prepare('INSERT INTO domains (name, type, master) VALUES(:name, :type, :master)'); + $query->bindValue(':master', $master, \PDO::PARAM_STR); + } else { + $query = $this->db->prepare('INSERT INTO domains (name, type) VALUES(:name, :type)'); + } + $query->bindValue(':name', $name, \PDO::PARAM_STR); + $query->bindValue(':type', $type, \PDO::PARAM_STR); + $query->execute(); + + + $query = $this->db->prepare('SELECT id,name,type,master FROM domains WHERE name=:name'); + $query->bindValue(':name', $name, \PDO::PARAM_STR); + $query->execute(); + + $record = $query->fetch(); + if ($type !== 'SLAVE') { + unset($record['master']); + } + + $this->db->commit(); + + return $record; + } } diff --git a/backend/src/public/index.php b/backend/src/public/index.php index 1330e65..6dbcec8 100644 --- a/backend/src/public/index.php +++ b/backend/src/public/index.php @@ -28,6 +28,7 @@ $app->group('/v1', function () { $this->delete('/sessions/{sessionId}', '\Controllers\Sessions:delete'); $this->get('/domains', '\Controllers\Domains:getList'); + $this->post('/domains', '\Controllers\Domains:postNew'); })->add('\Middlewares\Authentication'); }); diff --git a/backend/test/db.sql b/backend/test/db.sql index 91f67a2..0bc9be3 100644 --- a/backend/test/db.sql +++ b/backend/test/db.sql @@ -175,10 +175,11 @@ CREATE TABLE `users` ( -- -- Dumping data for table `users` --- +--$2y$10$MktCI4XcfD0FpIFSkxex6OVifnIw3Nqw6QJueWmjVte99wx6XGBoq INSERT INTO `users` (`id`, `name`, `backend`, `type`, `password`) VALUES -(1, 'admin', 'native', 'admin', '$2y$10$9iIDHWgjY0pEsz8pZLXPx.gkMNDxTMzb7U0Um5hUGjKmUUHWQNXcW'); +(1, 'admin', 'native', 'admin', '$2y$10$9iIDHWgjY0pEsz8pZLXPx.gkMNDxTMzb7U0Um5hUGjKmUUHWQNXcW'), +(2, 'user', 'native', 'user', '$2y$10$MktCI4XcfD0FpIFSkxex6OVifnIw3Nqw6QJueWmjVte99wx6XGBoq'); -- -- Indexes for dumped tables diff --git a/backend/test/testlib.js b/backend/test/testlib.js index 04cf669..8bb7a74 100644 --- a/backend/test/testlib.js +++ b/backend/test/testlib.js @@ -1,7 +1,7 @@ const assert = require('assert'); const axios = require('axios'); -async function runTest(f) { +async function runTest(user, f) { const assertObj = { equal: assert.deepStrictEqual, true: assert.ok @@ -13,7 +13,7 @@ async function runTest(f) { }); try { - const token = await logIn(assertObj, requestObj); + const token = await logIn(assertObj, requestObj, user); requestObj = axios.create({ baseURL: process.argv[2], @@ -41,19 +41,19 @@ async function runTest(f) { process.exit(0); } -async function logIn(assert, req) { +async function logIn(assert, req, username) { //Try to login with valid username and password var res = await req({ url: '/sessions', method: 'post', data: { - username: 'admin', - password: 'admin' + username: username, + password: username } }); assert.equal(res.status, 201, 'LOGIN: Status not valid'); - assert.equal(res.data.username, 'admin', 'LOGIN: Username should be admin'); + assert.equal(res.data.username, username, 'LOGIN: Username should be ' + username); assert.equal(res.data.token.length, 86, 'LOGIN: Token length fail'); return res.data.token; diff --git a/backend/test/tests/domains-crud.js b/backend/test/tests/domains-crud.js new file mode 100644 index 0000000..3c14e9c --- /dev/null +++ b/backend/test/tests/domains-crud.js @@ -0,0 +1,100 @@ +const cartesianProduct = require('cartesian-product'); + +require('../testlib')('admin', async function (assert, req) { + //Test missing fields + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'abc.de' + } + }); + + assert.equal(res.status, 422, 'Missing type filed should trigger error.'); + + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'abc.de', + type: 'SLAVE' + } + }); + + assert.equal(res.status, 422, 'Missing master field for SLAVE domain should trigger error.'); + + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'foo.de', + type: 'MASTER' + } + }); + + assert.equal(res.status, 409, 'Existing domain should trigger error.'); + + //Test creation of master zone + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'master.de', + type: 'MASTER' + } + }); + + assert.equal(res.status, 201, 'Creation should be successfull'); + assert.equal(res.data, { + id: '6', + name: 'master.de', + type: 'MASTER' + }, 'Creation result fail.') + + //Test creation of native zone + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'native.de', + type: 'NATIVE' + } + }); + + assert.equal(res.status, 201, 'Creation should be successfull'); + assert.equal(res.data, { + id: '7', + name: 'native.de', + type: 'NATIVE' + }, 'Creation result fail.') + + //Test creation of slave zone + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'slave.de', + type: 'SLAVE' + } + }); + + assert.equal(res.status, 201, 'Creation should be successfull'); + assert.equal(res.data, { + id: '8', + name: 'slave.de', + type: 'SLAVE' + }, 'Creation result fail.') +}); + +require('../testlib')('user', async function (assert, req) { + //Test insufficient privileges + var res = await req({ + url: '/domains', + method: 'post', + data: { + name: 'foo.de' + } + }); + + assert.equal(res.status, 403, 'Domain creation should be forbidden for users.') +}); \ No newline at end of file diff --git a/backend/test/tests/domains-get.js b/backend/test/tests/domains-get.js index 5dd87c7..bf2ca4f 100644 --- a/backend/test/tests/domains-get.js +++ b/backend/test/tests/domains-get.js @@ -1,6 +1,6 @@ const cartesianProduct = require('cartesian-product'); -require('../testlib')(async function (assert, req) { +require('../testlib')('admin', async function (assert, req) { //GET /domains?page=5&pagesize=10&query=foo&sort=id-asc,name-desc,type-asc,records-asc&type=MASTER //Test sorting in all combinations diff --git a/backend/test/tests/session.js b/backend/test/tests/session.js index d65e624..890a8c0 100644 --- a/backend/test/tests/session.js +++ b/backend/test/tests/session.js @@ -1,5 +1,5 @@ -require('../testlib')(async function (assert, req) { +require('../testlib')('admin', async function (assert, req) { //Try to login with invalid username and password var res = await req({ url: '/sessions',