Compare commits
194 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3030ff4a15 | ||
![]() |
d85999ba13 | ||
![]() |
28abbfa356 | ||
![]() |
52050a4298 | ||
![]() |
72a60a0703 | ||
![]() |
0b2f36dbbd | ||
![]() |
6feed021d2 | ||
![]() |
4eed13e475 | ||
![]() |
0e53529442 | ||
![]() |
aa2a5906f9 | ||
![]() |
36b6bf5b88 | ||
![]() |
972d03b005 | ||
![]() |
2bae2042a2 | ||
![]() |
600c7acdc1 | ||
![]() |
2628d7f938 | ||
![]() |
091f27e9e5 | ||
![]() |
328d645838 | ||
![]() |
67bff0d6bf | ||
![]() |
99ad1ef4a4 | ||
![]() |
78dc31bec8 | ||
![]() |
df99f5e237 | ||
![]() |
3aedfbac4b | ||
![]() |
cfbe93ba38 | ||
![]() |
160156dcb5 | ||
![]() |
d7430923f4 | ||
![]() |
d6285f92bb | ||
![]() |
b38b1e9122 | ||
![]() |
93d8521b86 | ||
![]() |
7cb02511ae | ||
![]() |
bf9f68a450 | ||
![]() |
8ffda92071 | ||
![]() |
de80e3797b | ||
![]() |
67d3981694 | ||
![]() |
2df4ce991a | ||
![]() |
8db7040211 | ||
![]() |
b77c8232f9 | ||
![]() |
aed50e530f | ||
![]() |
5838f61db6 | ||
![]() |
799ce1b371 | ||
![]() |
d5e86f3583 | ||
![]() |
b2cf655a88 | ||
![]() |
16a56184b8 | ||
![]() |
e6ca551641 | ||
![]() |
2e4c49445f | ||
![]() |
25346304f5 | ||
![]() |
14039932fe | ||
![]() |
29f97e781e | ||
![]() |
f8da1e68a3 | ||
![]() |
b083024ab2 | ||
![]() |
67c2f2d2a2 | ||
![]() |
5208c36d8e | ||
![]() |
5d2ef81610 | ||
![]() |
a0548c0e01 | ||
![]() |
6342e48742 | ||
![]() |
1fa2cec0a6 | ||
![]() |
d2e4082dbc | ||
![]() |
08289e6745 | ||
![]() |
10d3d8c50f | ||
![]() |
ee72dd2620 | ||
![]() |
5d18531ec4 | ||
![]() |
193b17ac19 | ||
![]() |
0acd117e87 | ||
![]() |
11416c682f | ||
![]() |
d5be125982 | ||
![]() |
e4611dffd9 | ||
![]() |
58bff34e4a | ||
![]() |
6b78678c45 | ||
![]() |
2bd996fe10 | ||
![]() |
b4f6922c96 | ||
![]() |
0701388c7e | ||
![]() |
dd35643915 | ||
![]() |
43b911eb60 | ||
![]() |
d1a32ec860 | ||
![]() |
a2572a8a99 | ||
![]() |
7d063fde98 | ||
![]() |
9c001d9d88 | ||
![]() |
266a583a4d | ||
![]() |
983ee5171a | ||
![]() |
2f19aafa80 | ||
![]() |
76e9f7327a | ||
![]() |
f48b0e8a11 | ||
![]() |
8262d53280 | ||
![]() |
0e6a90fa8f | ||
![]() |
ee1b081447 | ||
![]() |
15ff44a86e | ||
![]() |
dcc0989d6d | ||
![]() |
25382d0de3 | ||
![]() |
ff41604aa2 | ||
![]() |
706011edd6 | ||
![]() |
c00263e072 | ||
![]() |
02f8e48f11 | ||
![]() |
22334174da | ||
![]() |
877e7c9e02 | ||
![]() |
b9a6e5d7f9 | ||
![]() |
2447d10dd2 | ||
![]() |
61430dd1d4 | ||
![]() |
3c6debf882 | ||
![]() |
3f78bc8ea6 | ||
![]() |
2f41db98e9 | ||
![]() |
4a7f884fb6 | ||
![]() |
51e294acc2 | ||
![]() |
73a881a408 | ||
![]() |
2ef6cdfa4b | ||
![]() |
852d0d6007 | ||
![]() |
bac3fd1dfb | ||
![]() |
28c0b0d08d | ||
![]() |
01cd32e27c | ||
![]() |
827d4d8280 | ||
![]() |
f4b06ae910 | ||
![]() |
29aa6e87f8 | ||
![]() |
8d24d6e6e1 | ||
![]() |
6ec9c81c32 | ||
![]() |
5928203a0a | ||
![]() |
b38cf25a75 | ||
![]() |
f76a0ed8a8 | ||
![]() |
7b7b739589 | ||
![]() |
725d0e1591 | ||
![]() |
49b45a0cf1 | ||
![]() |
cd6f019dd0 | ||
![]() |
e95f520afa | ||
![]() |
a573a7813e | ||
![]() |
e39bb6d13c | ||
![]() |
8c7fbf40ef | ||
![]() |
c002e98156 | ||
![]() |
3f4203fdf0 | ||
![]() |
e543e9ceea | ||
![]() |
90381aec54 | ||
![]() |
878bfc3f6c | ||
![]() |
dcb33c5c69 | ||
![]() |
5f93a13412 | ||
![]() |
54bf2a1099 | ||
![]() |
154232236a | ||
![]() |
ac7f6f5b56 | ||
![]() |
1aad0d7219 | ||
![]() |
c3250e58fc | ||
![]() |
e0c12809e9 | ||
![]() |
fdaa16ea1b | ||
![]() |
00c3a32de8 | ||
![]() |
058cafb6d7 | ||
![]() |
ae8b16c825 | ||
![]() |
03ae894776 | ||
![]() |
2511608c88 | ||
![]() |
bff62a725d | ||
![]() |
800a832975 | ||
![]() |
fae6ac469b | ||
![]() |
016183f244 | ||
![]() |
4c4269c1fa | ||
![]() |
31d7ecfdf3 | ||
![]() |
921b30e47d | ||
![]() |
f238b7edaa | ||
![]() |
b1a06a8684 | ||
![]() |
39dd3f86c6 | ||
![]() |
6aa5ea61df | ||
![]() |
ccc423291c | ||
![]() |
2bb00ea0ba | ||
![]() |
f922f19fd3 | ||
![]() |
1d7086dc6c | ||
![]() |
5c9f0da3aa | ||
![]() |
77fa9a926d | ||
![]() |
a105edc4ab | ||
![]() |
107bfc7c67 | ||
![]() |
8f382ac72f | ||
![]() |
47aebceac4 | ||
![]() |
61d7ea9a51 | ||
![]() |
d9f4b20448 | ||
![]() |
b9efd906e5 | ||
![]() |
47f3f9939e | ||
![]() |
5d5f8c4af7 | ||
![]() |
7972a197ee | ||
![]() |
1aaa24280f | ||
![]() |
3bf4e2874a | ||
![]() |
25569890ac | ||
![]() |
d9f113c95d | ||
![]() |
788c0d1d19 | ||
![]() |
dc8fab4d42 | ||
![]() |
0d1f5503ae | ||
![]() |
c72806708b | ||
![]() |
3a3855e939 | ||
![]() |
d14c64043c | ||
![]() |
b0e53d92a0 | ||
![]() |
01ca1ca62c | ||
![]() |
ab84439c1a | ||
![]() |
c7ff6326ee | ||
![]() |
e52aa1428e | ||
![]() |
ec47c6b175 | ||
![]() |
bda3384ad7 | ||
![]() |
0c4bb7e127 | ||
![]() |
be316f163f | ||
![]() |
6231e06b91 | ||
![]() |
869ba131ef | ||
![]() |
135f88015f | ||
![]() |
e894493cd1 | ||
![]() |
d14868618f | ||
![]() |
4fb70354ce |
280 changed files with 25493 additions and 4599 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1,3 +1,10 @@
|
|||
nbproject/private/
|
||||
config/config-user.php
|
||||
releases/
|
||||
*/node_modules/*
|
||||
|
||||
backend/src/vendor/*
|
||||
backend/src/config/ConfigUser.php
|
||||
backend/src/config/ConfigOverride.php
|
||||
backend/test/config.sh
|
||||
backend/test/node_modules/
|
||||
backend/test/logfile.log
|
||||
|
||||
.vscode/
|
||||
|
|
36
.travis.yml
Normal file
36
.travis.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
sudo: required
|
||||
dist: trusty
|
||||
language: php
|
||||
services:
|
||||
- mysql
|
||||
php:
|
||||
- "7.1"
|
||||
before_install:
|
||||
- sudo rm -rf ~/.nvm
|
||||
- curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
- sudo apt-get install -y nodejs build-essential
|
||||
install:
|
||||
- .travis/frontend-install.sh
|
||||
- .travis/backend-install.sh
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
- phpenv config-add .travis/data/phpconfig.ini
|
||||
- mysql -e 'CREATE DATABASE pdnstest;'
|
||||
- .travis/backend-start-server.sh > /dev/null 2>&1 &
|
||||
script:
|
||||
- .travis/frontend-lint.sh
|
||||
- .travis/frontend-build.sh
|
||||
- .travis/backend-lint.sh
|
||||
- .travis/backend-test.sh
|
||||
- .travis/build-package.sh
|
||||
deploy:
|
||||
- provider: script
|
||||
script: .travis/deploy-snapshot.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
- provider: script
|
||||
script: .travis/deploy-release.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
19
.travis/backend-install.sh
Executable file
19
.travis/backend-install.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd backend/src
|
||||
if ! composer install
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
cd ../..
|
||||
|
||||
cd backend/test
|
||||
if ! npm install
|
||||
then
|
||||
exit 2
|
||||
fi
|
||||
cp ../../.travis/data/config-backend-test.sh config.sh
|
||||
cd ../..
|
||||
|
||||
exit 0
|
||||
|
5
.travis/backend-lint.sh
Executable file
5
.travis/backend-lint.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd backend/src
|
||||
|
||||
composer run-script lint
|
5
.travis/backend-start-server.sh
Executable file
5
.travis/backend-start-server.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd backend/src/public
|
||||
|
||||
php -S localhost:8000
|
5
.travis/backend-test.sh
Executable file
5
.travis/backend-test.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd backend/test
|
||||
|
||||
./test.sh all
|
13
.travis/build-package.sh
Executable file
13
.travis/build-package.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
if test $TRAVIS_TAG
|
||||
then
|
||||
utils/make-package.sh pdnsmanager-${TRAVIS_TAG:1} ${TRAVIS_TAG:1}
|
||||
utils/make-package.sh pdnsmanager-$TRAVIS_COMMIT $TRAVIS_COMMIT
|
||||
else
|
||||
utils/make-package.sh pdnsmanager-$TRAVIS_COMMIT $TRAVIS_COMMIT
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
7
.travis/data/config-backend-test.sh
Normal file
7
.travis/data/config-backend-test.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
DBHOST="localhost"
|
||||
DBUSER="root"
|
||||
DBPASSWORD=""
|
||||
DBNAME="pdnstest"
|
||||
|
||||
TESTURL="http://localhost:8000/v1"
|
1
.travis/data/phpconfig.ini
Normal file
1
.travis/data/phpconfig.ini
Normal file
|
@ -0,0 +1 @@
|
|||
extension="apcu.so"
|
3
.travis/deploy-release.sh
Executable file
3
.travis/deploy-release.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl -F "file=@pdnsmanager-${TRAVIS_TAG:1}.tar.gz" -u "travis:$UPLOAD_PASS" "https://upload.pdnsmanager.org/?action=release&version=${TRAVIS_TAG:1}"
|
3
.travis/deploy-snapshot.sh
Executable file
3
.travis/deploy-snapshot.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl -F "file=@pdnsmanager-$TRAVIS_COMMIT.tar.gz" -u "travis:$UPLOAD_PASS" "https://upload.pdnsmanager.org/?action=snapshot&version=$TRAVIS_COMMIT"
|
5
.travis/frontend-build.sh
Executable file
5
.travis/frontend-build.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd frontend
|
||||
|
||||
npm run build
|
5
.travis/frontend-install.sh
Executable file
5
.travis/frontend-install.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd frontend
|
||||
|
||||
npm install
|
5
.travis/frontend-lint.sh
Executable file
5
.travis/frontend-lint.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd frontend
|
||||
|
||||
npm run lint
|
2
LICENSE
2
LICENSE
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2016-2018 Lukas Metzger
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# PDNS Manager
|
||||
|
||||
[PDNS Manager](https://pdnsmanager.org) is a simple yet powerful free administration tool for the Powerdns authoritative nameserver. It supports master, native and slave zones.
|
||||
|
||||
PDNS Manager was developed from scratch to achieve a user-friendly and pretty looking interface.
|
||||
|
||||
PDNS Manager also features a powerful API to set records programatically.
|
||||
This can be used e.g. for a dynamic DNS service, but also to obtain certificates from [Let's Encrypt](https://letsencrypt.org/) via the dns-01 challenge.
|
||||
|
||||
PDNS Managers Backend is written in PHP using [Slim Framework](https://www.slimframework.com/). The backend uses a MySQL/Maria DB database. The database is also used by Powerdns using the pdns-backend-mysql backend. The Frontend is based on [Angular](https://angular.io/) and [Bootstrap](https://getbootstrap.com/).
|
||||
|
||||
PDNS Manager also features a plugin API to support different session caches or authentication strategies. If you want to contribute a new plugin here feel free to contact me.
|
||||
|
||||
## More information
|
||||
You can find more information and documentation as well as contact information on [pdnsmanager.org](https://pdnsmanager.org). There are also some tutorials to get you quickly up and running.
|
||||
|
||||
## Contribute
|
||||
If you are looking for a new feature or you found a bug, feel free to create a pull request or open a issue.
|
103
add-domain.php
103
add-domain.php
|
@ -1,103 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<?php
|
||||
require_once 'lib/headers.php';
|
||||
require_once 'lib/session.php';
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>PDNS Manager - Domains</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="include/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="include/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">
|
||||
<link href="include/custom.css" rel="stylesheet">
|
||||
|
||||
<script src="include/jquery.js"></script>
|
||||
<script src="include/bootstrap/js/bootstrap.min.js"></script>
|
||||
|
||||
<script src="js/add-domain.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
PDNS Manager
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="domains.php">Domains</a></li>
|
||||
<?php if($_SESSION['type'] == "admin") echo '<li><a href="users.php">Users</a></li>'; ?>
|
||||
<li><a href="password.php">Password</a></li>
|
||||
<li><a href="logout.php">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<row>
|
||||
<h2 id="domain-name">Add Domain</h2>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<form>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="zone-name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="zone-name" placeholder="Name" autocomplete="off" data-regex="^([^.]+\.)*[^.]+$" tabindex="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="zone-primary" class="control-label">Primary</label>
|
||||
<input type="text" class="form-control" id="zone-primary" placeholder="Primary" autocomplete="off" data-regex="^([^.]+\.)*[^.]+$" tabindex="2">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="zone-mail" class="control-label">Email</label>
|
||||
<input type="text" class="form-control" id="zone-mail" placeholder="Email" autocomplete="off" data-regex="^.+\@.+\.[^.]+$" tabindex="3">
|
||||
</div>
|
||||
<button id="zone-button-add" class="btn btn-primary" tabindex="8">Add</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 col-md-offset-1">
|
||||
<div class="form-group">
|
||||
<label for="zone-refresh" class="control-label">Refresh</label>
|
||||
<input type="text" class="form-control" id="zone-refresh" placeholder="Refresh" autocomplete="off" data-regex="^[0-9]+$" tabindex="4" value="3600">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="zone-retry" class="control-label">Retry</label>
|
||||
<input type="text" class="form-control" id="zone-retry" placeholder="Retry" autocomplete="off" data-regex="^[0-9]+$" tabindex="5" value="900">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 col-md-offset-1">
|
||||
<div class="form-group">
|
||||
<label for="zone-expire" class="control-label">Expire</label>
|
||||
<input type="text" class="form-control" id="zone-expire" placeholder="Expire" autocomplete="off" data-regex="^[0-9]+$" tabindex="6" value="604800">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="zone-ttl" class="control-label">TTL</label>
|
||||
<input type="text" class="form-control" id="zone-ttl" placeholder="TTL" autocomplete="off" data-regex="^[0-9]+$" tabindex="7" value="86400">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</row>
|
||||
|
||||
</div>
|
||||
<?php echo '<span class="hidden" id="csrfToken">' . $_SESSION['csrfToken'] . '</span>'; ?>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
require_once '../lib/soa-mail.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(!isset($_SESSION['type']) || $_SESSION['type'] != "admin") {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "addDomain") {
|
||||
$soaData = Array();
|
||||
$soaData[] = $input->primary;
|
||||
$soaData[] = mail_to_soa($input->mail);
|
||||
$soaData[] = date("Ymd") . "00";
|
||||
$soaData[] = $input->refresh;
|
||||
$soaData[] = $input->retry;
|
||||
$soaData[] = $input->expire;
|
||||
$soaData[] = $input->ttl;
|
||||
|
||||
$soaContent = implode(" ", $soaData);
|
||||
|
||||
$db->autocommit(false);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO domains(name,type) VALUES (?,'MASTER')");
|
||||
$stmt->bind_param("s", $input->name);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT LAST_INSERT_ID()");
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($newDomainId);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO records(domain_id,name,type,content,ttl) VALUES (?,?,'SOA',?,?)");
|
||||
$stmt->bind_param("issi", $newDomainId, $input->name, $soaContent, $input->ttl);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$db->commit();
|
||||
|
||||
$retval = Array();
|
||||
$retval['newId'] = $newDomainId;
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
133
api/domains.php
133
api/domains.php
|
@ -1,133 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "getDomains") {
|
||||
|
||||
$sql = "
|
||||
SELECT D.id,D.name,D.type,count(R.domain_id) AS records
|
||||
FROM domains D
|
||||
LEFT OUTER JOIN records R ON D.id = R.domain_id
|
||||
LEFT OUTER JOIN permissions P ON D.id = P.domain
|
||||
WHERE (P.user=? OR ?)
|
||||
GROUP BY D.id
|
||||
HAVING
|
||||
(D.name LIKE ? OR ?) AND
|
||||
(D.type=? OR ?)
|
||||
";
|
||||
|
||||
if(isset($input->sort->field) && $input->sort->field != "") {
|
||||
if($input->sort->field == "id") {
|
||||
$sql .= "ORDER BY id";
|
||||
} else if($input->sort->field == "name") {
|
||||
$sql .= "ORDER BY name";
|
||||
} else if($input->sort->field == "type") {
|
||||
$sql .= "ORDER BY type";
|
||||
} else if($input->sort->field == "records") {
|
||||
$sql .= "ORDER BY records";
|
||||
}
|
||||
|
||||
if(isset($input->sort->order)) {
|
||||
if($input->sort->order == 0) {
|
||||
$sql .= " DESC";
|
||||
} else if($input->sort->order == 1) {
|
||||
$sql .= " ASC";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
if(isset($input->name)) {
|
||||
$name_filter = "%" . $input->name . "%";
|
||||
$name_filter_used = 0;
|
||||
} else {
|
||||
$name_filter = "";
|
||||
$name_filter_used = 1;
|
||||
}
|
||||
|
||||
$id_filter = $_SESSION['id'];
|
||||
$id_filter_used = (int)($_SESSION['type'] == "admin" ? 1 : 0);
|
||||
|
||||
if(isset($input->type)) {
|
||||
$type_filter = $input->type;
|
||||
$type_filter_used = 0;
|
||||
} else {
|
||||
$type_filter = "";
|
||||
$type_filter_used = 1;
|
||||
}
|
||||
|
||||
$stmt->bind_param("sisiii",
|
||||
$id_filter, $id_filter_used,
|
||||
$name_filter, $name_filter_used,
|
||||
$type_filter, $type_filter_used
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "deleteDomain") {
|
||||
$domainId = $input->id;
|
||||
|
||||
$db->autocommit(false);
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM permissions WHERE domain=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM remote WHERE record IN (SELECT id FROM records WHERE domain_id=?)");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM records WHERE domain_id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM domains WHERE id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$db->commit();
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
|
@ -1,286 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
require_once '../lib/soa-mail.php';
|
||||
require_once '../lib/update-serial.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
//Permission check
|
||||
if(isset($input->domain)) {
|
||||
$permquery = $db->prepare("SELECT * FROM permissions WHERE user=? AND domain=?");
|
||||
|
||||
$permquery->bind_param("ii", $_SESSION['id'], $input->domain);
|
||||
$permquery->execute();
|
||||
$permquery->store_result();
|
||||
if($permquery->num_rows() < 1 && $_SESSION['type'] != "admin") {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
//Action for getting Records
|
||||
if(isset($input->action) && $input->action == "getRecords") {
|
||||
|
||||
$sql = "
|
||||
SELECT id,name,type,content,ttl,prio AS priority
|
||||
FROM records
|
||||
WHERE
|
||||
(name LIKE ? OR ?) AND
|
||||
(content LIKE ? OR ?) AND
|
||||
(domain_id = ?) AND
|
||||
(type != 'SOA')
|
||||
";
|
||||
|
||||
if(isset($input->type)) {
|
||||
$sql .= " AND type IN(";
|
||||
|
||||
foreach($input->type as $filtertype) {
|
||||
$filtertype = $db->escape_string($filtertype);
|
||||
$sql .= "'" . $filtertype . "'" . ",";
|
||||
}
|
||||
$sql = rtrim($sql, ",");
|
||||
$sql .= ")";
|
||||
}
|
||||
|
||||
if(isset($input->sort->field) && $input->sort->field != "") {
|
||||
if($input->sort->field == "id") {
|
||||
$sql .= " ORDER BY id";
|
||||
} else if($input->sort->field == "name") {
|
||||
$sql .= " ORDER BY name";
|
||||
} else if($input->sort->field == "type") {
|
||||
$sql .= " ORDER BY type";
|
||||
} else if($input->sort->field == "content") {
|
||||
$sql .= " ORDER BY content";
|
||||
} else if($input->sort->field == "ttl") {
|
||||
$sql .= " ORDER BY ttl";
|
||||
} else if($input->sort->field == "priority") {
|
||||
$sql .= " ORDER BY prio";
|
||||
}
|
||||
|
||||
if(isset($input->sort->order)) {
|
||||
if($input->sort->order == 0) {
|
||||
$sql .= " DESC";
|
||||
} else if($input->sort->order == 1) {
|
||||
$sql .= " ASC";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
if(isset($input->name)) {
|
||||
$name_filter = "%" . $input->name . "%";
|
||||
$name_filter_used = 0;
|
||||
} else {
|
||||
$name_filter = "";
|
||||
$name_filter_used = 1;
|
||||
}
|
||||
|
||||
if(isset($input->content)) {
|
||||
$content_filter = "%" . $input->content . "%";
|
||||
$content_filter_used = 0;
|
||||
} else {
|
||||
$content_filter = "";
|
||||
$content_filter_used = 1;
|
||||
}
|
||||
|
||||
$domainId = (int)$input->domain;
|
||||
|
||||
$stmt->bind_param("sisii",
|
||||
$name_filter, $name_filter_used,
|
||||
$content_filter, $content_filter_used,
|
||||
$domainId
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Action for getting SOA
|
||||
if(isset($input->action) && $input->action == "getSoa") {
|
||||
$domainId = (int)$input->domain;
|
||||
|
||||
$stmt = $db->prepare("SELECT content FROM records WHERE type='SOA' AND domain_id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->bind_result($content);
|
||||
$stmt->fetch();
|
||||
|
||||
$content = explode(" ", $content);
|
||||
|
||||
$retval = Array();
|
||||
|
||||
$retval['primary'] = preg_replace('/\\.$/', "", $content[0]);
|
||||
$retval['email'] = soa_to_mail($content[1]);
|
||||
$retval['serial'] = $content[2];
|
||||
$retval['refresh'] = $content[3];
|
||||
$retval['retry'] = $content[4];
|
||||
$retval['expire'] = $content[5];
|
||||
$retval['ttl'] = $content[6];
|
||||
|
||||
|
||||
}
|
||||
|
||||
//Action for getting SOA
|
||||
if(isset($input->action) && $input->action == "getSerial") {
|
||||
$domainId = (int)$input->domain;
|
||||
|
||||
$stmt = $db->prepare("SELECT content FROM records WHERE type='SOA' AND domain_id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->bind_result($content);
|
||||
$stmt->fetch();
|
||||
|
||||
$content = explode(" ", $content);
|
||||
|
||||
$retval = Array();
|
||||
|
||||
$retval['serial'] = $content[2];
|
||||
}
|
||||
|
||||
//Action for saving SOA
|
||||
if(isset($input->action) && $input->action == "saveSoa") {
|
||||
$domainId = (int)$input->domain;
|
||||
|
||||
$db->autocommit(false);
|
||||
$db->begin_transaction();
|
||||
|
||||
$stmt = $db->prepare("SELECT content FROM records WHERE type='SOA' AND domain_id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($content);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$content = explode(" ", $content);
|
||||
$serial = $content[2];
|
||||
|
||||
$newsoa = $input->primary . " ";
|
||||
$newsoa .= mail_to_soa($input->email) . " ";
|
||||
$newsoa .= $serial . " ";
|
||||
$newsoa .= $input->refresh . " ";
|
||||
$newsoa .= $input->retry . " ";
|
||||
$newsoa .= $input->expire . " ";
|
||||
$newsoa .= $input->ttl;
|
||||
|
||||
$stmt = $db->prepare("UPDATE records SET content=?,ttl=? WHERE type='SOA' AND domain_id=?");
|
||||
$stmt->bind_param("sii", $newsoa, $input->ttl, $domainId);
|
||||
$stmt->execute();
|
||||
|
||||
$db->commit();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
update_serial($db, $domainId);
|
||||
}
|
||||
|
||||
//Action for saving Record
|
||||
if(isset($input->action) && $input->action == "saveRecord") {
|
||||
$domainId = $input->domain;
|
||||
|
||||
$stmt = $db->prepare("UPDATE records SET name=?,type=?,content=?,ttl=?,prio=? WHERE id=? AND domain_id=?");
|
||||
$stmt->bind_param("sssiiii",
|
||||
$input->name, $input->type,
|
||||
$input->content, $input->ttl,
|
||||
$input->prio,
|
||||
$input->id, $domainId
|
||||
);
|
||||
$stmt->execute();
|
||||
update_serial($db, $domainId);
|
||||
}
|
||||
|
||||
//Action for adding Record
|
||||
if(isset($input->action) && $input->action == "addRecord") {
|
||||
$domainId = $input->domain;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO records (domain_id, name, type, content, prio, ttl) VALUES (?,?,?,?,?,?)");
|
||||
$stmt->bind_param("isssii",
|
||||
$domainId, $input->name,
|
||||
$input->type, $input->content,
|
||||
$input->prio, $input->ttl
|
||||
);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT LAST_INSERT_ID()");
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($newId);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$retval = Array();
|
||||
$retval['newId'] = $newId;
|
||||
|
||||
update_serial($db, $domainId);
|
||||
}
|
||||
|
||||
//Action for removing Record
|
||||
if(isset($input->action) && $input->action == "removeRecord") {
|
||||
$domainId = $input->domain;
|
||||
$recordId = $input->id;
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM records WHERE id=? AND domain_id=?");
|
||||
$stmt->bind_param("ii", $recordId, $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
update_serial($db, $domainId);
|
||||
}
|
||||
|
||||
//Action for getting domain name
|
||||
if(isset($input->action) && $input->action == "getDomainName") {
|
||||
$domainId = $input->domain;
|
||||
|
||||
$stmt = $db->prepare("SELECT name FROM domains WHERE id=?");
|
||||
$stmt->bind_param("i", $domainId);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($domainName);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$retval = Array();
|
||||
$retval['name'] = $domainName;
|
||||
}
|
||||
|
||||
if (isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
//Permission check
|
||||
if(isset($input->record)) {
|
||||
$permquery = $db->prepare("SELECT * FROM records JOIN permissions ON records.domain_id=permissions.domain WHERE user=? AND records.id=?");
|
||||
|
||||
$permquery->bind_param("ii", $_SESSION['id'], $input->record);
|
||||
$permquery->execute();
|
||||
$permquery->store_result();
|
||||
if($permquery->num_rows() < 1 && $_SESSION['type'] != "admin") {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
//Action for getting permission
|
||||
if(isset($input->action) && $input->action == "getPermissions") {
|
||||
|
||||
$sql = "SELECT id, description, type FROM remote WHERE record=?";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("i",$input->record);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Action for adding password
|
||||
if(isset($input->action) && $input->action == "addPassword") {
|
||||
$passwordHash = password_hash($input->password, PASSWORD_DEFAULT);
|
||||
|
||||
$sql = "INSERT INTO remote(record,description,type,security) VALUES (?,?,'password',?)";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("iss",$input->record, $input->description, $passwordHash);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
//Action for adding key
|
||||
if(isset($input->action) && $input->action == "addKey") {
|
||||
$sql = "INSERT INTO remote(record,description,type,security) VALUES (?,?,'key',?)";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("iss",$input->record, $input->description, $input->key);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
//Action for updating password
|
||||
if(isset($input->action) && $input->action == "changePassword") {
|
||||
if(isset($input->password)) {
|
||||
$passwordHash = password_hash($input->password, PASSWORD_DEFAULT);
|
||||
$sql = "UPDATE remote SET description=?,security=? WHERE id=?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bind_param("ssi",$input->description, $passwordHash, $input->permission);
|
||||
$stmt->execute();
|
||||
} else {
|
||||
$sql = "UPDATE remote SET description=? WHERE id=?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bind_param("ssi",$input->description, $input->permission);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
//Action for updating key
|
||||
if(isset($input->action) && $input->action == "changeKey") {
|
||||
$sql = "UPDATE remote SET description=?,security=? WHERE id=?";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("ssi",$input->description, $input->key, $input->permission);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
//Action for getting key
|
||||
if(isset($input->action) && $input->action == "getKey") {
|
||||
$sql = "SELECT security FROM remote WHERE id=? AND type='key'";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("i",$input->permission);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($key);
|
||||
$stmt->fetch();
|
||||
|
||||
$retval = Array();
|
||||
$retval['key'] = $key;
|
||||
}
|
||||
|
||||
//Action for deleting permission
|
||||
if(isset($input->action) && $input->action == "deletePermission") {
|
||||
$sql = "DELETE FROM remote WHERE id=?";
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
$stmt->bind_param("i",$input->permission);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(!isset($_SESSION['type']) || $_SESSION['type'] != "admin") {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "addUser") {
|
||||
$passwordHash = password_hash($input->password, PASSWORD_DEFAULT);
|
||||
|
||||
$db->autocommit(false);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO user(name,password,type) VALUES (?,?,?)");
|
||||
$stmt->bind_param("sss", $input->name, $passwordHash, $input->type);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT LAST_INSERT_ID()");
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($newUserId);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$db->commit();
|
||||
|
||||
$retval = Array();
|
||||
$retval['newId'] = $newUserId;
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "getUserData") {
|
||||
$stmt = $db->prepare("SELECT name,type FROM user WHERE id=?");
|
||||
$stmt->bind_param("i", $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($userName, $userType);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$retval = Array();
|
||||
$retval['name'] = $userName;
|
||||
$retval['type'] = $userType;
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "saveUserChanges") {
|
||||
if(isset($input->password)) {
|
||||
$passwordHash = password_hash($input->password, PASSWORD_DEFAULT);
|
||||
$stmt = $db->prepare("UPDATE user SET name=?,password=?,type=? WHERE id=?");
|
||||
$stmt->bind_param("sssi", $input->name, $passwordHash, $input->type, $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
} else {
|
||||
$stmt = $db->prepare("UPDATE user SET name=?,type=? WHERE id=?");
|
||||
$stmt->bind_param("ssi", $input->name, $input->type, $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "getPermissions") {
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT D.id,D.name
|
||||
FROM permissions P
|
||||
JOIN domains D ON P.domain=D.id
|
||||
WHERE P.user=?
|
||||
");
|
||||
|
||||
$stmt->bind_param("i", $input->id);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "removePermission") {
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM permissions WHERE user=? AND domain=?");
|
||||
|
||||
$stmt->bind_param("ii", $input->userId, $input->domainId);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "searchDomains" && isset($input->term)) {
|
||||
$stmt = $db->prepare("SELECT id,name AS text FROM domains WHERE name LIKE ? AND id NOT IN(SELECT domain FROM permissions WHERE user=?)");
|
||||
|
||||
$searchTerm = "%" . $input->term . "%";
|
||||
|
||||
$stmt->bind_param("si", $searchTerm, $input->userId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "addPermissions") {
|
||||
$stmt = $db->prepare("INSERT INTO permissions(user,domain) VALUES (?,?)");
|
||||
|
||||
foreach($input->domains as $domain) {
|
||||
$stmt->bind_param("ii", $input->userId, $domain);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
$sql = $db->prepare("SELECT id,password,type FROM user WHERE name=?");
|
||||
$sql->bind_param("s", $input->user);
|
||||
$sql->execute();
|
||||
|
||||
$sql->bind_result($id, $password, $type);
|
||||
$sql->fetch();
|
||||
|
||||
if (password_verify($input->password, $password)) {
|
||||
$retval['status'] = "success";
|
||||
|
||||
session_start();
|
||||
|
||||
$_SESSION['id'] = $id;
|
||||
$_SESSION['type'] = $type;
|
||||
|
||||
$randomSecret = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
$_SESSION['secret'] = $randomSecret;
|
||||
setcookie("authSecret", $randomSecret, 0, "/", "", false, true);
|
||||
|
||||
$csrfToken = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
$_SESSION['csrfToken'] = $csrfToken;
|
||||
} else {
|
||||
$retval['status'] = "fail";
|
||||
}
|
||||
|
||||
echo json_encode($retval);
|
136
api/install.php
136
api/install.php
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if(file_exists("../config/config-user.php")) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
//Get input
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
//Database command
|
||||
$sql = "
|
||||
CREATE TABLE IF NOT EXISTS domains (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255) NOT NULL,
|
||||
master varchar(128) DEFAULT NULL,
|
||||
last_check int(11) DEFAULT NULL,
|
||||
type varchar(6) NOT NULL,
|
||||
notified_serial int(11) DEFAULT NULL,
|
||||
account varchar(40) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY name_index (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
user int(11) NOT NULL,
|
||||
domain int(11) NOT NULL,
|
||||
PRIMARY KEY (user,domain),
|
||||
KEY domain (domain)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS records (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
domain_id int(11) DEFAULT NULL,
|
||||
name varchar(255) DEFAULT NULL,
|
||||
type varchar(6) DEFAULT NULL,
|
||||
content varchar(255) DEFAULT NULL,
|
||||
ttl int(11) DEFAULT NULL,
|
||||
prio int(11) NOT NULL DEFAULT '0',
|
||||
change_date int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY rec_name_index (name),
|
||||
KEY nametype_index (name,type),
|
||||
KEY domain_id (domain_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
name varchar(50) NOT NULL,
|
||||
password varchar(200) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
ALTER TABLE permissions
|
||||
ADD CONSTRAINT permissions_ibfk_2 FOREIGN KEY (domain) REFERENCES domains (id),
|
||||
ADD CONSTRAINT permissions_ibfk_1 FOREIGN KEY (user) REFERENCES user (id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
record int(11) NOT NULL,
|
||||
description varchar(255) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
security varchar(2000) NOT NULL,
|
||||
nonce varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY record (record)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT `remote_ibfk_1` FOREIGN KEY (`record`) REFERENCES `records` (`id`);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS options (
|
||||
name varchar(255) NOT NULL,
|
||||
value varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
INSERT INTO options(name,value) VALUES ('schema_version', 1);
|
||||
|
||||
";
|
||||
|
||||
|
||||
$db = @new mysqli($input->host, $input->user, $input->password, $input->database, $input->port);
|
||||
|
||||
|
||||
if($db->connect_error) {
|
||||
$retval['status'] = "error";
|
||||
$retval['message'] = $db->connect_error;
|
||||
} else {
|
||||
$passwordHash = password_hash($input->userPassword, PASSWORD_DEFAULT);
|
||||
|
||||
$db->multi_query($sql);
|
||||
while ($db->next_result()) {;}
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO user(name,password,type) VALUES (?,?,'admin')");
|
||||
$stmt->bind_param("ss", $input->userName, $passwordHash);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$configFile = Array();
|
||||
|
||||
$configFile[] = '<?php';
|
||||
$configFile[] = '$config[\'db_host\'] = \'' . addslashes($input->host) . "';";
|
||||
$configFile[] = '$config[\'db_user\'] = \'' . addslashes($input->user) . "';";
|
||||
$configFile[] = '$config[\'db_password\'] = \'' . addslashes($input->password) . "';";
|
||||
$configFile[] = '$config[\'db_name\'] = \'' . addslashes($input->database) . "';";
|
||||
$configFile[] = '$config[\'db_port\'] = ' . addslashes($input->port) . ";";
|
||||
|
||||
file_put_contents("../config/config-user.php", implode("\n", $configFile));
|
||||
|
||||
$retval['status'] = "success";
|
||||
}
|
||||
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "changePassword") {
|
||||
$passwordHash = password_hash($input->password, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $db->prepare("UPDATE user SET password=? WHERE id=?");
|
||||
$stmt->bind_param("si", $passwordHash, $_SESSION['id']);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
148
api/remote.php
148
api/remote.php
|
@ -1,148 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/update-serial.php';
|
||||
|
||||
if(filter_input(INPUT_SERVER, "REQUEST_METHOD") == "GET") {
|
||||
$input_domain = filter_input(INPUT_GET, "domain");
|
||||
$input_id = filter_input(INPUT_GET, "id");
|
||||
$input_password = filter_input(INPUT_GET, "password");
|
||||
$input_content = filter_input(INPUT_GET, "content");
|
||||
|
||||
$stmt = $db->prepare("SELECT security,record FROM remote WHERE type='password' AND id=?");
|
||||
$stmt->bind_param("i", $input_id);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($passwordHash, $record);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
if(!password_verify($input_password, $passwordHash)) {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "Permission denied";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE records SET content=? WHERE name=? AND id=?");
|
||||
$stmt->bind_param("ssi", $input_content, $input_domain, $record);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT domain_id FROM records WHERE id=?");
|
||||
$stmt->bind_param("i",$record);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($domain_id);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
update_serial($db, $domain_id);
|
||||
|
||||
$return['status'] = "success";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
|
||||
} else if(filter_input(INPUT_SERVER, "REQUEST_METHOD") == "POST") {
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(isset($input->domain) && isset($input->id) && isset($input->content)) {
|
||||
$stmt = $db->prepare("SELECT E.name,E.id FROM remote R JOIN records E ON R.record = E.id WHERE R.id=?");
|
||||
$stmt->bind_param("i", $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($domainName, $record);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
if($domainName != $input->domain) {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "Id and domain do not match!";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($_GET['getNonce'])) {
|
||||
$newNonce = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
$dbNonce = $newNonce . ":" . time();
|
||||
|
||||
$stmt = $db->prepare("UPDATE remote SET nonce=? WHERE id=?");
|
||||
$stmt->bind_param("si", $dbNonce, $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$return['nonce'] = $newNonce;
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
} else if(isset($_GET['editRecord'])) {
|
||||
$stmt = $db->prepare("SELECT security,nonce FROM remote WHERE id=?");
|
||||
$stmt->bind_param("i", $input->id);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($pubkey, $dbNonce);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$nonce = explode(":", $dbNonce);
|
||||
|
||||
if($dbNonce == NULL || (time() - $nonce[1]) > $config['nonce_lifetime']) {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "No valid nonce available!";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
|
||||
$verifyString = $input->domain . $input->id . $input->content . $nonce[0];
|
||||
$signature = base64_decode($input->signature);
|
||||
|
||||
if(openssl_verify($verifyString, $signature, $pubkey, OPENSSL_ALGO_SHA512) != 1) {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "Bad signature!";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE records SET content=? WHERE name=? AND id=?");
|
||||
$stmt->bind_param("ssi", $input->content, $input->domain, $record);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT domain_id FROM records WHERE id=?");
|
||||
$stmt->bind_param("i",$record);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($domain_id);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
update_serial($db, $domain_id);
|
||||
|
||||
$return['status'] = "success";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
} else {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "Wrong action";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
|
||||
} else {
|
||||
$return['status'] = "error";
|
||||
$return['error'] = "Missing data";
|
||||
echo json_encode($return);
|
||||
exit();
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/checkversion.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(isset($input->action) && $input->action == "getVersions") {
|
||||
$retval['from'] = getVersion($db);
|
||||
$retval['to'] = getExpectedVersion();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "requestUpgrade") {
|
||||
$currentVersion = getVersion($db);
|
||||
|
||||
if($currentVersion < 1) {
|
||||
$sql = "
|
||||
CREATE TABLE IF NOT EXISTS remote (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
record int(11) NOT NULL,
|
||||
description varchar(255) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
security varchar(2000) NOT NULL,
|
||||
nonce varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY record (record)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT `remote_ibfk_1` FOREIGN KEY (`record`) REFERENCES `records` (`id`);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS options (
|
||||
name varchar(255) NOT NULL,
|
||||
value varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
INSERT INTO options(name,value) VALUES ('schema_version', 1);
|
||||
";
|
||||
|
||||
$db->multi_query($sql);
|
||||
while ($db->next_result()) {;}
|
||||
}
|
||||
|
||||
$retval['status'] = "success";
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
118
api/users.php
118
api/users.php
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require_once '../config/config-default.php';
|
||||
require_once '../lib/database.php';
|
||||
require_once '../lib/session.php';
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'));
|
||||
|
||||
if(!isset($input->csrfToken) || $input->csrfToken !== $_SESSION['csrfToken']) {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(!isset($_SESSION['type']) || $_SESSION['type'] != "admin") {
|
||||
echo "Permission denied!";
|
||||
exit();
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "getUsers") {
|
||||
|
||||
$sql = "
|
||||
SELECT id,name,type
|
||||
FROM user
|
||||
WHERE
|
||||
(name LIKE ? OR ?) AND
|
||||
(type=? OR ?)
|
||||
";
|
||||
|
||||
if(isset($input->sort->field) && $input->sort->field != "") {
|
||||
if($input->sort->field == "id") {
|
||||
$sql .= "ORDER BY id";
|
||||
} else if($input->sort->field == "name") {
|
||||
$sql .= "ORDER BY name";
|
||||
} else if($input->sort->field == "type") {
|
||||
$sql .= "ORDER BY type";
|
||||
}
|
||||
|
||||
if(isset($input->sort->order)) {
|
||||
if($input->sort->order == 0) {
|
||||
$sql .= " DESC";
|
||||
} else if($input->sort->order == 1) {
|
||||
$sql .= " ASC";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
if(isset($input->name)) {
|
||||
$name_filter = "%" . $input->name . "%";
|
||||
$name_filter_used = 0;
|
||||
} else {
|
||||
$name_filter = "";
|
||||
$name_filter_used = 1;
|
||||
}
|
||||
|
||||
if(isset($input->type)) {
|
||||
$type_filter = $input->type;
|
||||
$type_filter_used = 0;
|
||||
} else {
|
||||
$type_filter = "";
|
||||
$type_filter_used = 1;
|
||||
}
|
||||
|
||||
$stmt->bind_param("sisi",
|
||||
$name_filter, $name_filter_used,
|
||||
$type_filter, $type_filter_used
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$retval = Array();
|
||||
|
||||
while($obj = $result->fetch_object()) {
|
||||
$retval[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($input->action) && $input->action == "deleteUser") {
|
||||
$userId = $input->id;
|
||||
|
||||
$db->autocommit(false);
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM permissions WHERE user=?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM user WHERE id=?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$db->commit();
|
||||
}
|
||||
|
||||
if(isset($retval)) {
|
||||
echo json_encode($retval);
|
||||
} else {
|
||||
echo "{}";
|
||||
}
|
23
backend/src/composer.json
Normal file
23
backend/src/composer.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"require": {
|
||||
"slim/slim": "^3.9",
|
||||
"monolog/monolog": "^1.23"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Services\\": "services/",
|
||||
"Controllers\\": "controllers/",
|
||||
"Operations\\": "operations/",
|
||||
"Plugins\\": "plugins/",
|
||||
"Middlewares\\": "middlewares/",
|
||||
"Exceptions\\": "exceptions/",
|
||||
"Utils\\": "utils/"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.11"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "vendor/bin/php-cs-fixer fix --dry-run --rules @PSR2,-no_trailing_whitespace_in_comment --using-cache false --stop-on-violation --diff ./"
|
||||
}
|
||||
}
|
1640
backend/src/composer.lock
generated
Normal file
1640
backend/src/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
52
backend/src/config/ConfigDefault.php
Normal file
52
backend/src/config/ConfigDefault.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
$defaultConfig = [
|
||||
'db' => [
|
||||
'host' => 'localhost',
|
||||
'user' => 'user',
|
||||
'password' => 'password',
|
||||
'dbname' => 'pdnsmanager',
|
||||
'port' => 3306
|
||||
],
|
||||
'logging' => [
|
||||
'level' => 'info',
|
||||
'path' => ''
|
||||
],
|
||||
'sessionstorage' => [
|
||||
'plugin' => 'apcu',
|
||||
'timeout' => 3600,
|
||||
'config' => null
|
||||
],
|
||||
'authentication' => [
|
||||
'native' => [
|
||||
'plugin' => 'native',
|
||||
'prefix' => 'default',
|
||||
'config' => null
|
||||
]
|
||||
],
|
||||
'remote' => [
|
||||
'timestampWindow' => 15
|
||||
],
|
||||
'records' => [
|
||||
'allowedTypes' => [
|
||||
'A', 'A6', 'AAAA', 'AFSDB', 'ALIAS', 'CAA', 'CDNSKEY', 'CDS', 'CERT', 'CNAME', 'DHCID',
|
||||
'DLV', 'DNAME', 'DNSKEY', 'DS', 'EUI48', 'EUI64', 'HINFO',
|
||||
'IPSECKEY', 'KEY', 'KX', 'LOC', 'LUA', 'MAILA', 'MAILB', 'MINFO', 'MR',
|
||||
'MX', 'NAPTR', 'NS', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPENPGPKEY',
|
||||
'OPT', 'PTR', 'RKEY', 'RP', 'RRSIG', 'SIG', 'SPF',
|
||||
'SRV', 'TKEY', 'SSHFP', 'TLSA', 'TSIG', 'TXT', 'WKS', 'MBOXFW', 'URL'
|
||||
]
|
||||
],
|
||||
'proxys' => [],
|
||||
'dbVersion' => 7
|
||||
];
|
||||
|
||||
if (file_exists('../config/ConfigOverride.php')) {
|
||||
$userConfig = require('ConfigOverride.php');
|
||||
} elseif (file_exists('../config/ConfigUser.php')) {
|
||||
$userConfig = require('ConfigUser.php');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array('config' => array_replace_recursive($defaultConfig, $userConfig));
|
177
backend/src/controllers/Credentials.php
Normal file
177
backend/src/controllers/Credentials.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Credentials
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function getList(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$recordId = intval($args['recordId']);
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessRecord($userId, $recordId)) {
|
||||
$this->logger->info('Non admin user tries to get credentials for record without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this record.'], 403);
|
||||
}
|
||||
|
||||
$credentials = new \Operations\Credentials($this->c);
|
||||
|
||||
$paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize'));
|
||||
|
||||
$results = $credentials->getCredentials($paging, $recordId);
|
||||
|
||||
return $res->withJson([
|
||||
'paging' => $paging->toArray(),
|
||||
'results' => $results
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function postNew(Request $req, Response $res, array $args)
|
||||
{
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!array_key_exists('description', $body) ||
|
||||
!array_key_exists('type', $body) || ($body['type'] === 'key' &&
|
||||
!array_key_exists('key', $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']);
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessRecord($userId, $recordId)) {
|
||||
$this->logger->info('User tries to add 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;
|
||||
|
||||
try {
|
||||
$result = $credentials->addCredential($recordId, $body['description'], $body['type'], $key, $password);
|
||||
return $res->withJson($result, 201);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->debug('User tries to add credential with wrong type.');
|
||||
return $res->withJson(['error' => 'The type is invalid.'], 400);
|
||||
} catch (\Exceptions\InvalidKeyException $e) {
|
||||
$this->logger->debug('User tries to add invalid credential key.');
|
||||
return $res->withJson(['error' => 'The provided key is invalid.'], 400);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tries to add credential for not existing record.');
|
||||
return $res->withJson(['error' => 'The provided record does not exist.'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(Request $req, Response $res, array $args)
|
||||
{
|
||||
$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 delete credential without permissions.');
|
||||
return $res->withJson(['error' => 'You have no permission for this record'], 403);
|
||||
}
|
||||
|
||||
$credentials = new \Operations\Credentials($this->c);
|
||||
|
||||
try {
|
||||
$credentials->deleteCredential($recordId, $credentialId);
|
||||
|
||||
$this->logger->info('Deleted credential', ['id' => $credentialId]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No credential found for id ' . $credentialId], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSingle(Request $req, Response $res, array $args)
|
||||
{
|
||||
$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('Non admin user tries to get credential without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this record.'], 403);
|
||||
}
|
||||
|
||||
$credentials = new \Operations\Credentials($this->c);
|
||||
|
||||
try {
|
||||
$result = $credentials->getCredential($recordId, $credentialId);
|
||||
$this->logger->debug('Get credential info', ['id' => $credentialId]);
|
||||
return $res->withJson($result, 200);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('Credential info not found', ['id' => $credentialId, 'record' => $recordId]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
227
backend/src/controllers/Domains.php
Normal file
227
backend/src/controllers/Domains.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Domains
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function getList(Request $req, Response $res, array $args)
|
||||
{
|
||||
$domains = new \Operations\Domains($this->c);
|
||||
|
||||
$paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize'));
|
||||
$query = $req->getQueryParam('query');
|
||||
$sort = $req->getQueryParam('sort');
|
||||
$type = $req->getQueryParam('type');
|
||||
|
||||
$userId = $req->getAttribute('userId');
|
||||
|
||||
$results = $domains->getDomains($paging, $userId, $query, $sort, $type);
|
||||
|
||||
return $res->withJson([
|
||||
'paging' => $paging->toArray(),
|
||||
'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;
|
||||
|
||||
$domains = new \Operations\Domains($this->c);
|
||||
|
||||
try {
|
||||
$result = $domains->addDomain($name, $type, $master);
|
||||
|
||||
$this->logger->info('Created domain', $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);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->info('Invalid type for new domain', ['type' => $type]);
|
||||
return $res->withJson(['error' => 'Invalid type allowed are MASTER, NATIVE and SLAVE'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(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 delete domain');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$domains = new \Operations\Domains($this->c);
|
||||
|
||||
$domainId = intval($args['domainId']);
|
||||
|
||||
try {
|
||||
$domains->deleteDomain($domainId);
|
||||
|
||||
$this->logger->info('Deleted domain', ['id' => $domainId]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No domain found for id ' . $domainId], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSingle(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$domainId = intval($args['domainId']);
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessDomain($userId, $domainId)) {
|
||||
$this->logger->info('Non admin user tries to get domain without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this domain.'], 403);
|
||||
}
|
||||
|
||||
$domains = new \Operations\Domains($this->c);
|
||||
|
||||
try {
|
||||
$result = $domains->getDomain($domainId);
|
||||
|
||||
$this->logger->debug('Get domain info', ['id' => $domainId]);
|
||||
return $res->withJson($result, 200);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No domain found for id ' . $domainId], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function put(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$domainId = intval($args['domainId']);
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessDomain($userId, $domainId)) {
|
||||
$this->logger->info('User tries to update domain without permission');
|
||||
return $res->withJson(['error' => 'You have no permissions for this domain.'], 403);
|
||||
}
|
||||
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!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);
|
||||
}
|
||||
|
||||
$master = $body['master'];
|
||||
|
||||
$domains = new \Operations\Domains($this->c);
|
||||
|
||||
try {
|
||||
$result = $domains->updateSlave($domainId, $master);
|
||||
|
||||
$this->logger->debug('Update master', ['id' => $domainId]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('Trying to update non existing slave zone', ['id' => $domainId]);
|
||||
return $res->withJson(['error' => 'No domain found for id ' . $domainId], 404);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->debug('Trying to update non slave zone', ['id' => $domainId]);
|
||||
return $res->withJson(['error' => 'Domain is not a slave zone'], 405);
|
||||
}
|
||||
}
|
||||
|
||||
public function putSoa(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$domainId = $args['domainId'];
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessDomain($userId, $domainId)) {
|
||||
$this->logger->info('Non admin user tries to get domain without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this domain.'], 403);
|
||||
}
|
||||
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!array_key_exists('primary', $body) ||
|
||||
!array_key_exists('email', $body) ||
|
||||
!array_key_exists('refresh', $body) ||
|
||||
!array_key_exists('retry', $body) ||
|
||||
!array_key_exists('expire', $body) ||
|
||||
!array_key_exists('ttl', $body)) {
|
||||
$this->logger->debug('One of the required fields is missing');
|
||||
return $res->withJson(['error' => 'One of the required fields is missing'], 422);
|
||||
}
|
||||
|
||||
$soa = new \Operations\Soa($this->c);
|
||||
|
||||
try {
|
||||
$soa->setSoa(
|
||||
intval($domainId),
|
||||
$body['email'],
|
||||
$body['primary'],
|
||||
intval($body['refresh']),
|
||||
intval($body['retry']),
|
||||
intval($body['expire']),
|
||||
intval($body['ttl'])
|
||||
);
|
||||
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->warning('Trying to set soa for not existing domain.', ['domainId' => $domainId]);
|
||||
return $res->withJson(['error' => 'No domain found for id ' . $domainId], 404);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->warning('Trying to set soa for slave domain.', ['domainId' => $domainId]);
|
||||
return $res->withJson(['error' => 'SOA can not be set for slave domains'], 405);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSoa(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$domainId = $args['domainId'];
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessDomain($userId, $domainId)) {
|
||||
$this->logger->info('Non admin user tries to get domain without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this domain.'], 403);
|
||||
}
|
||||
|
||||
$soa = new \Operations\Soa($this->c);
|
||||
|
||||
try {
|
||||
$soaArray = $soa->getSoa($domainId);
|
||||
|
||||
return $res->withJson($soaArray, 200);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tried to get non existing soa.', ['domainId' => $domainId]);
|
||||
return $res->withJson(['error' => 'This domain has no soa record.'], 404);
|
||||
}
|
||||
}
|
||||
}
|
18
backend/src/controllers/NotAllowed.php
Normal file
18
backend/src/controllers/NotAllowed.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class NotAllowed
|
||||
{
|
||||
public function __invoke(\Slim\Container $c)
|
||||
{
|
||||
return function ($request, $response, $methods) use ($c) {
|
||||
$c->logger->warning('Method ' . $request->getMethod() . ' is not valid for ' . $request->getUri()->getPath());
|
||||
return $c['response']
|
||||
->withHeader('Allow', \implode(', ', $methods))
|
||||
->withJson(array('error' => 'Method ' . $request->getMethod() . ' is not valid use on of ' . implode(', ', $methods)), 405);
|
||||
};
|
||||
}
|
||||
}
|
16
backend/src/controllers/NotFound.php
Normal file
16
backend/src/controllers/NotFound.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class NotFound
|
||||
{
|
||||
public function __invoke(\Slim\Container $c)
|
||||
{
|
||||
return function ($request, $response) use ($c) {
|
||||
$c->logger->warning('No valid endpoint found for: ' . $request->getUri()->getPath());
|
||||
return $c['response']->withJson(array('error' => 'No valid endpoint found!'), 404);
|
||||
};
|
||||
}
|
||||
}
|
96
backend/src/controllers/Permissions.php
Normal file
96
backend/src/controllers/Permissions.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Permissions
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function getList(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 get permissions');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize'));
|
||||
$user = intval($args['user']);
|
||||
|
||||
$permissions = new \Operations\Permissions($this->c);
|
||||
|
||||
$results = $permissions->getPermissions($paging, $user);
|
||||
|
||||
return $res->withJson([
|
||||
'paging' => $paging->toArray(),
|
||||
'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 permissions');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!array_key_exists('domainId', $body)) {
|
||||
$this->logger->debug('One of the required fields is missing');
|
||||
return $res->withJson(['error' => 'One of the required fields is missing'], 422);
|
||||
}
|
||||
|
||||
$user = intval($args['user']);
|
||||
|
||||
$permissions = new \Operations\Permissions($this->c);
|
||||
|
||||
try {
|
||||
$permissions->addPermission($user, $body['domainId']);
|
||||
|
||||
$this->logger->info('Permission was added:', ['by' => $req->getAttribute('userId'), 'user' => $user, 'domain' => $body['domainId']]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'Either domain or user were not found'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(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 permissions');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$user = intval($args['user']);
|
||||
$domainId = intval($args['domainId']);
|
||||
|
||||
$permissions = new \Operations\Permissions($this->c);
|
||||
|
||||
try {
|
||||
$permissions->deletePermission($user, $domainId);
|
||||
|
||||
$this->logger->info('Permission was removed:', ['by' => $req->getAttribute('userId'), 'user' => $user, 'domain' => $domainId]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'Either domain or user were not found'], 404);
|
||||
}
|
||||
}
|
||||
}
|
157
backend/src/controllers/Records.php
Normal file
157
backend/src/controllers/Records.php
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Records
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function getList(Request $req, Response $res, array $args)
|
||||
{
|
||||
$records = new \Operations\Records($this->c);
|
||||
|
||||
$paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize'));
|
||||
$domain = $req->getQueryParam('domain');
|
||||
$queryName = $req->getQueryParam('queryName');
|
||||
$type = $req->getQueryParam('type');
|
||||
$queryContent = $req->getQueryParam('queryContent');
|
||||
$sort = $req->getQueryParam('sort');
|
||||
|
||||
$userId = $req->getAttribute('userId');
|
||||
|
||||
$results = $records->getRecords($paging, $userId, $domain, $queryName, $type, $queryContent, $sort);
|
||||
|
||||
return $res->withJson([
|
||||
'paging' => $paging->toArray(),
|
||||
'results' => $results
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function postNew(Request $req, Response $res, array $args)
|
||||
{
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!array_key_exists('name', $body) ||
|
||||
!array_key_exists('type', $body) ||
|
||||
!array_key_exists('content', $body) ||
|
||||
!array_key_exists('priority', $body) ||
|
||||
!array_key_exists('ttl', $body) ||
|
||||
!array_key_exists('domain', $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');
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessDomain($userId, $body['domain'])) {
|
||||
$this->logger->info('User tries to add record for domain without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for the given domain.'], 403);
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
|
||||
try {
|
||||
$result = $records->addRecord($body['name'], $body['type'], $body['content'], $body['priority'], $body['ttl'], $body['domain']);
|
||||
return $res->withJson($result, 201);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tries to add record for invalid domain.');
|
||||
return $res->withJson(['error' => 'The domain does not exist or is neighter MASTER nor NATIVE.'], 404);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->debug('User tries to add record with invalid type.', ['type' => $body['type']]);
|
||||
return $res->withJson(['error' => 'The provided type is invalid.'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$recordId = intval($args['recordId']);
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessRecord($userId, $recordId)) {
|
||||
$this->logger->info('User tries to delete record without permissions.');
|
||||
return $res->withJson(['error' => 'You have no permission to delete this record'], 403);
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
|
||||
try {
|
||||
$records->deleteRecord($recordId);
|
||||
|
||||
$this->logger->info('Deleted record', ['id' => $recordId]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No record found for id ' . $recordId], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSingle(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$recordId = intval($args['recordId']);
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessRecord($userId, $recordId)) {
|
||||
$this->logger->info('Non admin user tries to get record without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this record.'], 403);
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
|
||||
try {
|
||||
$result = $records->getRecord($recordId);
|
||||
|
||||
$this->logger->debug('Get record info', ['id' => $recordId]);
|
||||
return $res->withJson($result, 200);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No record found for id ' . $recordId], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function put(Request $req, Response $res, array $args)
|
||||
{
|
||||
$userId = $req->getAttribute('userId');
|
||||
$recordId = intval($args['recordId']);
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if (!$ac->canAccessRecord($userId, $recordId)) {
|
||||
$this->logger->info('Non admin user tries to update record without permission.');
|
||||
return $res->withJson(['error' => 'You have no permissions for this record.'], 403);
|
||||
}
|
||||
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
$name = array_key_exists('name', $body) ? $body['name'] : null;
|
||||
$type = array_key_exists('type', $body) ? $body['type'] : null;
|
||||
$content = array_key_exists('content', $body) ? $body['content'] : null;
|
||||
$priority = array_key_exists('priority', $body) ? $body['priority'] : null;
|
||||
$ttl = array_key_exists('ttl', $body) ? $body['ttl'] : null;
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
|
||||
try {
|
||||
$records->updateRecord($recordId, $name, $type, $content, $priority, $ttl);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tries to update not existing record.');
|
||||
return $res->withJson(['error' => 'The record does not exist.'], 404);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->debug('User tries to update record with invalid type.', ['type' => $type]);
|
||||
return $res->withJson(['error' => 'The provided type is invalid.'], 400);
|
||||
}
|
||||
}
|
||||
}
|
90
backend/src/controllers/Remote.php
Normal file
90
backend/src/controllers/Remote.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Remote
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function ip(Request $req, Response $res, array $args)
|
||||
{
|
||||
return $res->withJson([
|
||||
'ip' => $req->getAttribute('clientIp')
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function updatePassword(Request $req, Response $res, array $args)
|
||||
{
|
||||
$record = $req->getParam('record');
|
||||
$content = $req->getParam('content');
|
||||
$password = $req->getParam('password');
|
||||
|
||||
if ($record === null || $content === null || $password === null) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing.'], 422);
|
||||
}
|
||||
|
||||
$remote = new \Operations\Remote($this->c);
|
||||
|
||||
try {
|
||||
$remote->updatePassword(intval($record), $content, $password);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tried to update non existent record via changepw api.');
|
||||
return $res->withJson(['error' => 'The given record does not exist.'], 404);
|
||||
} catch (\Exceptions\ForbiddenException $e) {
|
||||
$this->logger->debug('User tried to update an record via changepw api with incorrect password.');
|
||||
return $res->withJson(['error' => 'The provided password was invalid.'], 403);
|
||||
}
|
||||
|
||||
$this->logger->info('Record ' . $record . ' was changed via the changepw api.');
|
||||
return $res->withStatus(204);
|
||||
}
|
||||
|
||||
public function updateKey(Request $req, Response $res, array $args)
|
||||
{
|
||||
$record = $req->getParsedBodyParam('record');
|
||||
$content = $req->getParsedBodyParam('content');
|
||||
$time = $req->getParsedBodyParam('time');
|
||||
$signature = $req->getParsedBodyParam('signature');
|
||||
|
||||
if ($record === null || $content === null || $time === null || $signature === null) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing.'], 422);
|
||||
}
|
||||
|
||||
$remote = new \Operations\Remote($this->c);
|
||||
|
||||
try {
|
||||
$remote->updateKey($record, $content, $time, $signature);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tried to update non existent record via changekey api.');
|
||||
return $res->withJson(['error' => 'The given record does not exist.'], 404);
|
||||
} catch (\Exceptions\ForbiddenException $e) {
|
||||
$this->logger->debug('User tried to update an record via changekey api with incorrect signature.');
|
||||
return $res->withJson(['error' => 'The provided signature was invalid.'], 403);
|
||||
}
|
||||
|
||||
$this->logger->info('Record ' . $record . ' was changed via the changekey api.');
|
||||
return $res->withStatus(204);
|
||||
}
|
||||
|
||||
public function servertime(Request $req, Response $res, array $args)
|
||||
{
|
||||
return $res->withJson([
|
||||
'time' => time()
|
||||
], 200);
|
||||
}
|
||||
}
|
76
backend/src/controllers/Sessions.php
Normal file
76
backend/src/controllers/Sessions.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Sessions
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $container;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->container = $c;
|
||||
}
|
||||
|
||||
public function post(Request $req, Response $res, array $args)
|
||||
{
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if (!array_key_exists('username', $body) ||
|
||||
!array_key_exists('password', $body)) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing'], 422);
|
||||
}
|
||||
|
||||
$userAuth = new \Operations\UserAuth($this->container);
|
||||
$sessionStorage = new \Operations\Sessionstorage($this->container);
|
||||
|
||||
$sessionTimeout = $this->container['config']['sessionstorage']['timeout'];
|
||||
|
||||
try {
|
||||
$userId = $userAuth->authenticate($body['username'], $body['password']);
|
||||
} catch (\Exceptions\PluginNotFoundException $e) {
|
||||
return $res->withJson(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
if ($userId >= 0) {
|
||||
$secret = openssl_random_pseudo_bytes(64);
|
||||
$secretString = base64_encode($secret);
|
||||
$secretString = rtrim(strtr($secretString, '+/', '-_'), '=');
|
||||
|
||||
$sessionStorage->set($secretString, $userId, $sessionTimeout);
|
||||
|
||||
$this->logger->info('User authenticated successfully', ['username' => $body['username']]);
|
||||
return $res->withJson([
|
||||
'username' => $body['username'],
|
||||
'token' => $secretString
|
||||
], 201);
|
||||
} else {
|
||||
$this->logger->info('User failed to authenticate', ['username' => $body['username'], 'ip' => $req->getAttribute('clientIp')]);
|
||||
return $res->withJson(['error' => 'Username or password is invalid'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(Request $req, Response $res, array $args)
|
||||
{
|
||||
$sessionStorage = new \Operations\Sessionstorage($this->container);
|
||||
|
||||
if ($sessionStorage->exists($args['sessionId'])) {
|
||||
$sessionStorage->delete($args['sessionId']);
|
||||
|
||||
$this->logger->info('Deleting session', ['token' => $args['sessionId']]);
|
||||
return $res->withStatus(204);
|
||||
} else {
|
||||
$this->logger->warning('Trying to delete non existing session', ['token' => $args['sessionId']]);
|
||||
return $res->withJson(['error' => 'Session not found'], 404);
|
||||
}
|
||||
}
|
||||
}
|
109
backend/src/controllers/Setup.php
Normal file
109
backend/src/controllers/Setup.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Setup
|
||||
{
|
||||
public function setup(Request $req, Response $res, array $args)
|
||||
{
|
||||
// Check if supplied data has all fields
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
if ($body === null) {
|
||||
return $res->withJson(['error' => 'The supplied body was empty'], 400);
|
||||
}
|
||||
|
||||
if (!array_key_exists('db', $body) || !array_key_exists('admin', $body)) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing.'], 422);
|
||||
}
|
||||
|
||||
$db = $body['db'];
|
||||
$admin = $body['admin'];
|
||||
|
||||
if (!array_key_exists('host', $db) || !array_key_exists('user', $db) ||
|
||||
!array_key_exists('password', $db) || !array_key_exists('database', $db) ||
|
||||
!array_key_exists('port', $db) || !array_key_exists('name', $admin) ||
|
||||
!array_key_exists('password', $admin)) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing.'], 422);
|
||||
}
|
||||
|
||||
// Check if pdo exists
|
||||
if (!extension_loaded('pdo')) {
|
||||
return $res->withJson(['error' => 'PDO extension is not enabled.'], 500);
|
||||
}
|
||||
if (!extension_loaded('pdo_mysql')) {
|
||||
return $res->withJson(['error' => 'PDO mysql extension is not enabled.'], 500);
|
||||
}
|
||||
|
||||
// Check if apcu exists
|
||||
if (!extension_loaded('apcu')) {
|
||||
return $res->withJson(['error' => 'APCU extension is not enabled.'], 500);
|
||||
}
|
||||
|
||||
try {
|
||||
// Test database connection
|
||||
$pdo = new \PDO(
|
||||
'mysql:host=' . $db['host'] . ';port=' . $db['port'] . ';dbname=' . $db['database'],
|
||||
$db['user'],
|
||||
$db['password']
|
||||
);
|
||||
|
||||
// Configure db connection
|
||||
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
|
||||
|
||||
// Check if database is empty
|
||||
$query = $pdo->prepare('SHOW TABLES');
|
||||
$query->execute();
|
||||
if ($query->fetch() !== false) {
|
||||
return $res->withJson(['error' => 'The database is not empty.'], 500);
|
||||
}
|
||||
|
||||
// Check if config can be written
|
||||
if (file_put_contents('../config/ConfigUser.php', 'test') === false) {
|
||||
return $res->withJson(['error' => 'Write of config file failed, check that the PHP user can write in the config directory.'], 500);
|
||||
} else {
|
||||
unlink('../config/ConfigUser.php');
|
||||
}
|
||||
|
||||
// Execute sql from setup file
|
||||
$sqlLines = explode(';', file_get_contents('../sql/setup.sql'));
|
||||
|
||||
foreach ($sqlLines as $sql) {
|
||||
if (strlen(preg_replace('/\s+/', '', $sql)) > 0) {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
// Create admin user
|
||||
$query = $pdo->prepare('INSERT INTO users (name, backend, type, password) VALUES (:name, :backend, :type, :password)');
|
||||
$query->bindValue(':name', $admin['name']);
|
||||
$query->bindValue(':backend', 'native');
|
||||
$query->bindValue(':type', 'admin');
|
||||
$query->bindValue(':password', password_hash($admin['password'], PASSWORD_DEFAULT));
|
||||
$query->execute();
|
||||
|
||||
// Save config file
|
||||
$config = [
|
||||
'db' => [
|
||||
'host' => $db['host'],
|
||||
'user' => $db['user'],
|
||||
'password' => $db['password'],
|
||||
'dbname' => $db['database'],
|
||||
'port' => intval($db['port'])
|
||||
]
|
||||
];
|
||||
$configFile = '<?php' . "\n\n" . 'return ' . var_export($config, true) . ';';
|
||||
file_put_contents('../config/ConfigUser.php', $configFile);
|
||||
} catch (\PDOException $e) {
|
||||
return $res->withJson(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
return $res->withStatus(204);
|
||||
}
|
||||
}
|
86
backend/src/controllers/Update.php
Normal file
86
backend/src/controllers/Update.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Update
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function get(Request $req, Response $res, array $args)
|
||||
{
|
||||
$currentVersion = $this->getCurrentVersion();
|
||||
|
||||
$targetVersion = $this->c['config']['dbVersion'];
|
||||
|
||||
if ($currentVersion < $targetVersion) {
|
||||
return $res->withJson([
|
||||
'updateRequired' => true,
|
||||
'currentVersion' => $currentVersion,
|
||||
'targetVersion' => $targetVersion
|
||||
], 200);
|
||||
} else {
|
||||
return $res->withJson(['updateRequired' => false], 200);
|
||||
}
|
||||
}
|
||||
|
||||
public function post(Request $req, Response $res, array $args)
|
||||
{
|
||||
$currentVersion = $this->getCurrentVersion();
|
||||
|
||||
$targetVersion = $this->c['config']['dbVersion'];
|
||||
|
||||
if ($currentVersion < $targetVersion) {
|
||||
try {
|
||||
for ($i = $currentVersion + 1; $i <= $targetVersion; $i++) {
|
||||
$sqlLines = explode(';', file_get_contents('../sql/Update' . $i . '.sql'));
|
||||
|
||||
foreach ($sqlLines as $sql) {
|
||||
if (strlen(preg_replace('/\s+/', '', $sql)) > 0) {
|
||||
$this->db->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info('Upgrade to version ' . $i . ' successfull!');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Upgrade failed with: ' . $e->getMessage());
|
||||
return $res->withJson(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
return $res->withStatus(204);
|
||||
}
|
||||
|
||||
private function getCurrentVersion() : int
|
||||
{
|
||||
$query = $this->db->prepare('SHOW TABLES LIKE \'options\';');
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT value FROM options WHERE name=\'schema_version\'');
|
||||
$query->execute();
|
||||
|
||||
return intval($query->fetch()['value']);
|
||||
}
|
||||
}
|
165
backend/src/controllers/Users.php
Normal file
165
backend/src/controllers/Users.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Controllers;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Users
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
public function getList(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 get users');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$users = new \Operations\Users($this->c);
|
||||
|
||||
$paging = new \Utils\PagingInfo($req->getQueryParam('page'), $req->getQueryParam('pagesize'));
|
||||
$query = $req->getQueryParam('query');
|
||||
$sort = $req->getQueryParam('sort');
|
||||
$type = $req->getQueryParam('type');
|
||||
|
||||
$results = $users->getUsers($paging, $query, $type, $sort);
|
||||
|
||||
return $res->withJson([
|
||||
'paging' => $paging->toArray(),
|
||||
'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 user');
|
||||
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) ||
|
||||
!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);
|
||||
}
|
||||
|
||||
$name = $body['name'];
|
||||
$type = $body['type'];
|
||||
$password = $body['password'];
|
||||
|
||||
$users = new \Operations\Users($this->c);
|
||||
|
||||
try {
|
||||
$result = $users->addUser($name, $type, $password);
|
||||
|
||||
$this->logger->info('Created user', $result);
|
||||
return $res->withJson($result, 201);
|
||||
} catch (\Exceptions\AlreadyExistentException $e) {
|
||||
$this->logger->debug('User with name ' . $name . ' already exists.');
|
||||
return $res->withJson(['error' => 'User with name ' . $name . ' already exists.'], 409);
|
||||
} catch (\Exceptions\SemanticException $e) {
|
||||
$this->logger->info('Invalid type for new user', ['type' => $type]);
|
||||
return $res->withJson(['error' => 'Invalid type allowed are admin and user'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(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 delete user');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$users = new \Operations\Users($this->c);
|
||||
|
||||
$user = intval($args['user']);
|
||||
|
||||
try {
|
||||
$users->deleteDomain($user);
|
||||
|
||||
$this->logger->info('Deleted user', ['id' => $user]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No user found for id ' . $user], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSingle(Request $req, Response $res, array $args)
|
||||
{
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if ($args['user'] === 'me') {
|
||||
$user = $req->getAttribute('userId');
|
||||
} elseif ($ac->isAdmin($req->getAttribute('userId'))) {
|
||||
$user = intval($args['user']);
|
||||
} else {
|
||||
$this->logger->info('Non admin user tries to get other user');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$users = new \Operations\Users($this->c);
|
||||
|
||||
try {
|
||||
$result = $users->getUser($user);
|
||||
|
||||
$this->logger->debug('Get user info', ['id' => $user]);
|
||||
return $res->withJson($result, 200);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
return $res->withJson(['error' => 'No user found for id ' . $user], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function put(Request $req, Response $res, array $args)
|
||||
{
|
||||
$body = $req->getParsedBody();
|
||||
|
||||
$name = array_key_exists('name', $body) ? $body['name'] : null;
|
||||
$type = array_key_exists('type', $body) ? $body['type'] : null;
|
||||
$password = array_key_exists('password', $body) ? $body['password'] : null;
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
if ($args['user'] === 'me') {
|
||||
$user = $req->getAttribute('userId');
|
||||
$name = null;
|
||||
$type = null;
|
||||
} elseif ($ac->isAdmin($req->getAttribute('userId'))) {
|
||||
$user = intval($args['user']);
|
||||
} else {
|
||||
$this->logger->info('Non admin user tries to get other user');
|
||||
return $res->withJson(['error' => 'You must be admin to use this feature'], 403);
|
||||
}
|
||||
|
||||
$users = new \Operations\Users($this->c);
|
||||
|
||||
try {
|
||||
$result = $users->updateUser($user, $name, $type, $password);
|
||||
|
||||
$this->logger->debug('Update user', ['id' => $user]);
|
||||
return $res->withStatus(204);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('Trying to update non existing user', ['id' => $user]);
|
||||
return $res->withJson(['error' => 'No user found for id ' . $user], 404);
|
||||
} catch (\Exceptions\AlreadyExistentException $e) {
|
||||
$this->logger->debug('Trying to rename user to conflicting name', ['id' => $user]);
|
||||
return $res->withJson(['error' => 'The new name already exists.'], 409);
|
||||
}
|
||||
}
|
||||
}
|
9
backend/src/exceptions/AlreadyExistentException.php
Normal file
9
backend/src/exceptions/AlreadyExistentException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class AlreadyExistentException extends \Exception
|
||||
{
|
||||
}
|
9
backend/src/exceptions/ForbiddenException.php
Normal file
9
backend/src/exceptions/ForbiddenException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class ForbiddenException extends \Exception
|
||||
{
|
||||
}
|
9
backend/src/exceptions/InvalidKeyException.php
Normal file
9
backend/src/exceptions/InvalidKeyException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class InvalidKeyException extends \Exception
|
||||
{
|
||||
}
|
9
backend/src/exceptions/NotFoundException.php
Normal file
9
backend/src/exceptions/NotFoundException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class NotFoundException extends \Exception
|
||||
{
|
||||
}
|
9
backend/src/exceptions/PluginNotFoundException.php
Normal file
9
backend/src/exceptions/PluginNotFoundException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class PluginNotFoundException extends \Exception
|
||||
{
|
||||
}
|
9
backend/src/exceptions/SemanticException.php
Normal file
9
backend/src/exceptions/SemanticException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class SemanticException extends \Exception
|
||||
{
|
||||
}
|
44
backend/src/middlewares/Authentication.php
Normal file
44
backend/src/middlewares/Authentication.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Middlewares;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class Authentication
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $container;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->container = $c;
|
||||
}
|
||||
|
||||
public function __invoke(Request $req, Response $res, callable $next)
|
||||
{
|
||||
$token = $req->getHeaderLine('X-Authentication');
|
||||
|
||||
$sessionStorage = new \Operations\Sessionstorage($this->container);
|
||||
|
||||
if ($sessionStorage->exists($token)) {
|
||||
$sessionTimeout = $this->container['config']['sessionstorage']['timeout'];
|
||||
|
||||
$userId = $sessionStorage->get($token, $sessionTimeout);
|
||||
|
||||
$this->logger->debug('Authentication was successfull', ['token' => $token, 'userId' => $userId]);
|
||||
|
||||
$req = $req->withAttribute('userId', $userId);
|
||||
return $next($req, $res);
|
||||
} else {
|
||||
$this->logger->warning('No valid authentication token found');
|
||||
return $res->withJson(['error' => 'No valid authentication token suplied', 'code' => 'invalid_session'], 403);
|
||||
}
|
||||
}
|
||||
}
|
52
backend/src/middlewares/ClientIp.php
Normal file
52
backend/src/middlewares/ClientIp.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Middlewares;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class ClientIp
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $container;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->container = $c;
|
||||
}
|
||||
|
||||
public function __invoke(Request $req, Response $res, callable $next)
|
||||
{
|
||||
$proxys = $this->container['config']['proxys'];
|
||||
|
||||
$headerContent = $req->getHeaderLine('X-Forwarded-For');
|
||||
|
||||
if (strlen($headerContent) === 0) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
} else {
|
||||
if (!in_array($_SERVER['REMOTE_ADDR'], $proxys)) { // Client is not trusted proxy
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
} else {
|
||||
$parts = array_map('trim', explode(',', $headerContent));
|
||||
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
for ($i = count($parts) - 1; $i >= 0; $i--) {
|
||||
if (!in_array($parts[$i], $proxys)) {
|
||||
$ip = $parts[$i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$req = $req->withAttribute('clientIp', $ip);
|
||||
return $next($req, $res);
|
||||
}
|
||||
}
|
26
backend/src/middlewares/LogRequests.php
Normal file
26
backend/src/middlewares/LogRequests.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Middlewares;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class LogRequests
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
}
|
||||
|
||||
public function __invoke(Request $req, Response $res, callable $next)
|
||||
{
|
||||
$this->logger->debug($req->getMethod() . ' ' . $req->getUri()->getPath());
|
||||
|
||||
return $next($req, $res);
|
||||
}
|
||||
}
|
30
backend/src/middlewares/RejectEmptyBody.php
Normal file
30
backend/src/middlewares/RejectEmptyBody.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Middlewares;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
class RejectEmptyBody
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
}
|
||||
|
||||
public function __invoke(Request $req, Response $res, callable $next)
|
||||
{
|
||||
if (($req->isPost() || $req->isPut() || $req->isPatch()) && $req->getParsedBody() == null) {
|
||||
$this->logger->warning('Got empty body in request with method ' . $req->getMethod());
|
||||
|
||||
return $res->withJson(['error' => 'The supplied body was empty'], 400);
|
||||
} else {
|
||||
return $next($req, $res);
|
||||
}
|
||||
}
|
||||
}
|
106
backend/src/operations/AccessControl.php
Normal file
106
backend/src/operations/AccessControl.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides access control for the application.
|
||||
*/
|
||||
class AccessControl
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given user has admin privileges.
|
||||
*
|
||||
* @param $userId User id of the user
|
||||
*
|
||||
* @return bool true if admin, false otherwise
|
||||
*/
|
||||
public function isAdmin(int $userId) : bool
|
||||
{
|
||||
$query = $this->db->prepare('SELECT type FROM users WHERE id=:id');
|
||||
$query->bindValue(':id', $userId, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
$this->logger->error('Queried record for non existing user id, this should not happen.', ['userId' => $userId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $record['type'] == 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given user has permissons for a given domain.
|
||||
*
|
||||
* @param $userId User id of the user
|
||||
* @param $domainId Domain to check
|
||||
*
|
||||
* @return bool true if access is granted, false otherwise
|
||||
*/
|
||||
public function canAccessDomain(int $userId, int $domainId) : bool
|
||||
{
|
||||
if ($this->isAdmin($userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT user_id,domain_id FROM permissions WHERE user_id=:userId AND domain_id=:domainId');
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given user has permissons for a given record.
|
||||
*
|
||||
* @param $userId User id of the user
|
||||
* @param $recordId Record to check
|
||||
*
|
||||
* @return bool true if access is granted, false otherwise
|
||||
*/
|
||||
public function canAccessRecord(int $userId, int $recordId) : bool
|
||||
{
|
||||
if ($this->isAdmin($userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('
|
||||
SELECT * FROM records R
|
||||
LEFT OUTER JOIN permissions P ON P.domain_id=R.domain_id
|
||||
WHERE R.id=:recordId AND P.user_id=:userId
|
||||
');
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':recordId', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
254
backend/src/operations/Credentials.php
Normal file
254
backend/src/operations/Credentials.php
Normal file
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
use function Monolog\Handler\error_log;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying credentials.
|
||||
*/
|
||||
class Credentials
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of credentials
|
||||
*
|
||||
* @param $pi PageInfo object, which is also updated with total page number
|
||||
* @param $recordId Id of the record for which the table should be retrieved
|
||||
*
|
||||
* @return array Array with credentials
|
||||
*/
|
||||
public function getCredentials(\Utils\PagingInfo &$pi, int $recordId) : array
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
//Count elements
|
||||
if ($pi->pageSize === null) {
|
||||
$pi->totalPages = 1;
|
||||
} else {
|
||||
$query = $this->db->prepare('
|
||||
SELECT COUNT(*) AS total
|
||||
FROM remote
|
||||
WHERE record=:recordId
|
||||
');
|
||||
|
||||
$query->bindValue(':recordId', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$pi->totalPages = ceil($record['total'] / $pi->pageSize);
|
||||
}
|
||||
|
||||
$pageStr = \Services\Database::makePagingString($pi);
|
||||
|
||||
$query = $this->db->prepare('SELECT id,description,type FROM remote WHERE record=:recordId ORDER BY id ASC' . $pageStr);
|
||||
$query->bindValue(':recordId', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$data = $query->fetchAll();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return array_map(function ($item) {
|
||||
$item['id'] = intval($item['id']);
|
||||
return $item;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new credential
|
||||
*
|
||||
* @param $record Record for which this credential should be valid
|
||||
* @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 addCredential(int $record, 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);
|
||||
} else {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM records WHERE id=:recordId');
|
||||
$query->bindValue(':recordId', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('INSERT INTO remote (record, description, type, security) VALUES (:record, :description, :type, :security)');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->bindValue(':description', $description, \PDO::PARAM_STR);
|
||||
$query->bindValue(':type', $type, \PDO::PARAM_STR);
|
||||
$query->bindValue(':security', $secret, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('SELECT id, description, type, security FROM remote ORDER BY id DESC LIMIT 1');
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$record['id'] = intval($record['id']);
|
||||
if ($record['type'] === 'key') {
|
||||
$record['key'] = $record['security'];
|
||||
unset($record['security']);
|
||||
} else {
|
||||
unset($record['security']);
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete credential
|
||||
*
|
||||
* @param $recordId Id of the record
|
||||
* @param $credentialId Id of the credential to delete
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if credential does not exist
|
||||
*/
|
||||
public function deleteCredential(int $recordId, int $credentialId) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM remote WHERE id=:id AND record=:record');
|
||||
$query->bindValue(':id', $credentialId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':record', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) { //Credential does not exist
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM remote WHERE id=:id');
|
||||
$query->bindValue(':id', $credentialId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get record
|
||||
*
|
||||
* @param $recordId Id of the record
|
||||
* @param $credentialId Id of the credential
|
||||
*
|
||||
* @return array Credential entry
|
||||
*
|
||||
* @throws NotFoundException if the credential does not exist
|
||||
*/
|
||||
public function getCredential(int $recordId, int $credentialId) : array
|
||||
{
|
||||
$query = $this->db->prepare('SELECT id,description,type,security FROM remote
|
||||
WHERE id=:credential AND record=:record');
|
||||
$query->bindValue(':credential', $credentialId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':record', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$record['id'] = intval($record['id']);
|
||||
if ($record['type'] === 'key') {
|
||||
$record['key'] = $record['security'];
|
||||
unset($record['security']);
|
||||
} else {
|
||||
unset($record['security']);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$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'];
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('UPDATE remote SET description=:description,type=:type,security=:security WHERE id=:credential');
|
||||
$query->bindValue(':description', $description);
|
||||
$query->bindValue(':type', $type);
|
||||
$query->bindValue(':security', $secret);
|
||||
$query->bindValue(':credential', $credential);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
293
backend/src/operations/Domains.php
Normal file
293
backend/src/operations/Domains.php
Normal file
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying domains.
|
||||
*/
|
||||
class Domains
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of domains according to filter criteria
|
||||
*
|
||||
* @param $pi PageInfo object, which is also updated with total page number
|
||||
* @param $userId Id of the user for which the table should be retrieved
|
||||
* @param $query Search query to search in the domain name, null for no filter
|
||||
* @param $sorting Sort string in format 'field-asc,field2-desc', null for default
|
||||
* @param $type Type to filter for, null for no filter
|
||||
*
|
||||
* @return array Array with matching domains
|
||||
*/
|
||||
public function getDomains(\Utils\PagingInfo &$pi, int $userId, ? string $query, ? string $sorting, ? string $type) : array
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
$userIsAdmin = $ac->isAdmin($userId);
|
||||
|
||||
$queryStr = $query === null ? '%' : '%' . $query . '%';
|
||||
|
||||
//Count elements
|
||||
if ($pi->pageSize === null) {
|
||||
$pi->totalPages = 1;
|
||||
} else {
|
||||
$query = $this->db->prepare('
|
||||
SELECT COUNT(*) AS total
|
||||
FROM domains D
|
||||
LEFT OUTER JOIN permissions P ON D.id = P.domain_id
|
||||
WHERE (P.user_id=:userId OR :userIsAdmin) AND
|
||||
(D.name LIKE :nameQuery) AND
|
||||
(D.type = :domainType OR :noTypeFilter)
|
||||
');
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userIsAdmin', intval($userIsAdmin), \PDO::PARAM_INT);
|
||||
$query->bindValue(':nameQuery', $queryStr, \PDO::PARAM_STR);
|
||||
$query->bindValue(':domainType', (string)$type, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$pi->totalPages = ceil($record['total'] / $pi->pageSize);
|
||||
}
|
||||
|
||||
//Query and return result
|
||||
$ordStr = \Services\Database::makeSortingString($sorting, [
|
||||
'id' => 'D.id',
|
||||
'name' => 'D.name',
|
||||
'type' => 'D.type',
|
||||
'records' => 'records'
|
||||
]);
|
||||
$pageStr = \Services\Database::makePagingString($pi);
|
||||
|
||||
$query = $this->db->prepare('
|
||||
SELECT D.id,D.name,D.type,D.master,count(R.domain_id) AS records
|
||||
FROM domains D
|
||||
LEFT OUTER JOIN records R ON D.id = R.domain_id AND R.type <> \'SOA\'
|
||||
LEFT OUTER JOIN permissions P ON D.id = P.domain_id
|
||||
WHERE (P.user_id=:userId OR :userIsAdmin) AND
|
||||
(R.type <> \'SOA\' OR R.type IS NULL)
|
||||
GROUP BY D.id
|
||||
HAVING
|
||||
(D.name LIKE :nameQuery) AND
|
||||
(D.type=:domainType OR :noTypeFilter)'
|
||||
. $ordStr . $pageStr);
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userIsAdmin', intval($userIsAdmin), \PDO::PARAM_INT);
|
||||
$query->bindValue(':nameQuery', $queryStr, \PDO::PARAM_STR);
|
||||
$query->bindValue(':domainType', (string)$type, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
|
||||
$data = $query->fetchAll();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return array_map(function ($item) {
|
||||
if ($item['type'] != 'SLAVE') {
|
||||
unset($item['master']);
|
||||
}
|
||||
$item['id'] = intval($item['id']);
|
||||
$item['records'] = intval($item['records']);
|
||||
return $item;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new domain
|
||||
*
|
||||
* @param $name Name of the new zone
|
||||
* @param $type Type of the new zone
|
||||
* @param $master Master for slave zones, otherwise null
|
||||
*
|
||||
* @return array New domain entry
|
||||
*
|
||||
* @throws AlreadyExistenException it the domain exists already
|
||||
*/
|
||||
public function addDomain(string $name, string $type, ? string $master) : array
|
||||
{
|
||||
if (!in_array($type, ['MASTER', 'SLAVE', 'NATIVE'])) {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$this->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();
|
||||
$record['id'] = intval($record['id']);
|
||||
if ($type !== 'SLAVE') {
|
||||
unset($record['master']);
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete domain
|
||||
*
|
||||
* @param $id Id of the domain to delete
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if domain does not exist
|
||||
*/
|
||||
public function deleteDomain(int $id) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM domains WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) { //Domain does not exist
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('
|
||||
DELETE E FROM remote E
|
||||
LEFT OUTER JOIN records R ON R.id=E.record
|
||||
WHERE R.domain_id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM records WHERE domain_id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM domains WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain
|
||||
*
|
||||
* @param $id Id of the domain to get
|
||||
*
|
||||
* @return array Domain data
|
||||
*
|
||||
* @throws NotFoundException if domain does not exist
|
||||
*/
|
||||
public function getDomain(int $id) : array
|
||||
{
|
||||
$query = $this->db->prepare('
|
||||
SELECT D.id,D.name,D.type,D.master,COUNT(R.domain_id) AS records FROM domains D
|
||||
LEFT OUTER JOIN records R ON D.id = R.domain_id AND R.type <> \'SOA\'
|
||||
WHERE D.id=:id AND (R.type <> \'SOA\' OR R.type IS NULL)
|
||||
GROUP BY D.id,D.name,D.type,D.master
|
||||
');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$record['id'] = intval($record['id']);
|
||||
$record['records'] = intval($record['records']);
|
||||
if ($record['type'] !== 'SLAVE') {
|
||||
unset($record['master']);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type of given domain
|
||||
*
|
||||
* @param int Domain id
|
||||
*
|
||||
* @return string Domain type
|
||||
*
|
||||
* @throws NotFoundException if domain does not exist
|
||||
*/
|
||||
public function getDomainType(int $id) : string
|
||||
{
|
||||
$query = $this->db->prepare('SELECT type FROM domains WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
return $record['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update master for slave zone
|
||||
*
|
||||
* @param int Domain id
|
||||
* @param string New master
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if domain does not exist
|
||||
* @throws SemanticException if domain is no slave zone
|
||||
*/
|
||||
public function updateSlave(int $id, string $master)
|
||||
{
|
||||
if ($this->getDomainType($id) !== 'SLAVE') {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('UPDATE domains SET master=:master WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->bindValue(':master', $master, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
}
|
||||
}
|
147
backend/src/operations/Permissions.php
Normal file
147
backend/src/operations/Permissions.php
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying permissions.
|
||||
*/
|
||||
class Permissions
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of permissions
|
||||
*
|
||||
* @param $pi PageInfo object, which is also updated with total page number
|
||||
* @param $userId Id of the user for which the permissions should be retrieved
|
||||
*
|
||||
* @return array Array with matching permissions
|
||||
*/
|
||||
public function getPermissions(\Utils\PagingInfo &$pi, int $userId) : array
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
//Count elements
|
||||
if ($pi->pageSize === null) {
|
||||
$pi->totalPages = 1;
|
||||
} else {
|
||||
$query = $this->db->prepare('SELECT COUNT(*) AS total FROM permissions WHERE user_id=:userId');
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$pi->totalPages = ceil($record['total'] / $pi->pageSize);
|
||||
}
|
||||
|
||||
$pageStr = \Services\Database::makePagingString($pi);
|
||||
|
||||
$query = $this->db->prepare('
|
||||
SELECT P.domain_id as domainId,D.name as domainName FROM permissions P
|
||||
LEFT OUTER JOIN domains D ON D.id=P.domain_id
|
||||
WHERE P.user_id=:userId'
|
||||
. $pageStr);
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
|
||||
$data = $query->fetchAll();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new permission
|
||||
*
|
||||
* @param $userId User id
|
||||
* @param $domainId Domain for which access should be granted
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException If domain or user was not found
|
||||
*/
|
||||
public function addPermission(int $userId, int $domainId) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM users WHERE id=:userId');
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM domains WHERE id=:domainId');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT * FROM permissions WHERE domain_id=:domainId AND user_id=:userId');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
$query = $this->db->prepare('INSERT INTO permissions (domain_id,user_id) VALUES (:domainId, :userId)');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a permission
|
||||
*
|
||||
* @param $userId User id
|
||||
* @param $domainId Domain for which access should be revoked
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if the entry was not found
|
||||
*/
|
||||
public function deletePermission(int $userId, int $domainId) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT * FROM permissions WHERE domain_id=:domainId AND user_id=:userId');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if ($query->fetch() === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM permissions WHERE domain_id=:domainId AND user_id=:userId');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
}
|
328
backend/src/operations/Records.php
Normal file
328
backend/src/operations/Records.php
Normal file
|
@ -0,0 +1,328 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
use function Monolog\Handler\error_log;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying domains.
|
||||
*/
|
||||
class Records
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of records according to filter criteria
|
||||
*
|
||||
* @param $pi PageInfo object, which is also updated with total page number
|
||||
* @param $userId Id of the user for which the table should be retrieved
|
||||
* @param $domain Comma separated list of domain ids
|
||||
* @param $queryName Search query to search in the record name, null for no filter
|
||||
* @param $type Comma separated list of types
|
||||
* @param $queryContent Search query to search in the record content, null for no filter
|
||||
* @param $sort Sort string in format 'field-asc,field2-desc', null for default
|
||||
*
|
||||
* @return array Array with matching records
|
||||
*/
|
||||
public function getRecords(
|
||||
\Utils\PagingInfo &$pi,
|
||||
int $userId,
|
||||
? string $domain,
|
||||
? string $queryName,
|
||||
? string $type,
|
||||
? string $queryContent,
|
||||
? string $sort
|
||||
) : array {
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$ac = new \Operations\AccessControl($this->c);
|
||||
$userIsAdmin = $ac->isAdmin($userId);
|
||||
|
||||
$queryName = $queryName === null ? '%' : '%' . $queryName . '%';
|
||||
$queryContent = $queryContent === null ? '%' : '%' . $queryContent . '%';
|
||||
|
||||
$setDomains = \Services\Database::makeSetString($this->db, $domain);
|
||||
$setTypes = \Services\Database::makeSetString($this->db, $type);
|
||||
|
||||
//Count elements
|
||||
if ($pi->pageSize === null) {
|
||||
$pi->totalPages = 1;
|
||||
} else {
|
||||
$query = $this->db->prepare('
|
||||
SELECT COUNT(*) AS total FROM records R
|
||||
LEFT OUTER JOIN domains D ON R.domain_id = D.id
|
||||
LEFT OUTER JOIN permissions P ON P.domain_id = R.domain_id
|
||||
WHERE (P.user_id=:userId OR :userIsAdmin) AND
|
||||
(R.domain_id IN ' . $setDomains . ' OR :noDomainFilter) AND
|
||||
(R.name LIKE :queryName) AND
|
||||
(R.type IN ' . $setTypes . ' OR :noTypeFilter) AND
|
||||
(R.content LIKE :queryContent) AND
|
||||
R.type <> \'SOA\'
|
||||
');
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userIsAdmin', intval($userIsAdmin), \PDO::PARAM_INT);
|
||||
$query->bindValue(':queryName', $queryName, \PDO::PARAM_STR);
|
||||
$query->bindValue(':queryContent', $queryContent, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noDomainFilter', intval($domain === null), \PDO::PARAM_INT);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$pi->totalPages = ceil($record['total'] / $pi->pageSize);
|
||||
}
|
||||
|
||||
//Query and return result
|
||||
$ordStr = \Services\Database::makeSortingString($sort, [
|
||||
'id' => 'R.id',
|
||||
'name' => 'R.name',
|
||||
'type' => 'R.type',
|
||||
'content' => 'R.content',
|
||||
'priority' => 'R.prio',
|
||||
'ttl' => 'R.ttl'
|
||||
]);
|
||||
$pageStr = \Services\Database::makePagingString($pi);
|
||||
|
||||
$query = $this->db->prepare('
|
||||
SELECT R.id,R.name,R.type,R.content,R.prio as priority,R.ttl,R.domain_id as domain FROM records R
|
||||
LEFT OUTER JOIN domains D ON R.domain_id = D.id
|
||||
LEFT OUTER JOIN permissions P ON P.domain_id = R.domain_id
|
||||
WHERE (P.user_id=:userId OR :userIsAdmin) AND
|
||||
(R.domain_id IN ' . $setDomains . ' OR :noDomainFilter) AND
|
||||
(R.name LIKE :queryName) AND
|
||||
(R.type IN ' . $setTypes . ' OR :noTypeFilter) AND
|
||||
(R.content LIKE :queryContent) AND
|
||||
R.type <> \'SOA\'
|
||||
GROUP BY R.id' . $ordStr . $pageStr);
|
||||
|
||||
$query->bindValue(':userId', $userId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':userIsAdmin', intval($userIsAdmin), \PDO::PARAM_INT);
|
||||
$query->bindValue(':queryName', $queryName, \PDO::PARAM_STR);
|
||||
$query->bindValue(':queryContent', $queryContent, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noDomainFilter', intval($domain === null), \PDO::PARAM_INT);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
|
||||
$data = $query->fetchAll();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return array_map(function ($item) {
|
||||
$item['id'] = intval($item['id']);
|
||||
$item['priority'] = intval($item['priority']);
|
||||
$item['ttl'] = intval($item['ttl']);
|
||||
$item['domain'] = intval($item['domain']);
|
||||
return $item;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new record
|
||||
*
|
||||
* @param $name Name of the new record
|
||||
* @param $type Type of the new record
|
||||
* @param $content Content of the new record
|
||||
* @param $priority Priority of the new record
|
||||
* @param $ttl TTL of the new record
|
||||
* @param $domain Domain id of the domain to add the record
|
||||
*
|
||||
* @return array New record entry
|
||||
*
|
||||
* @throws NotFoundException if the domain does not exist
|
||||
* @throws SemanticException if the record type is invalid
|
||||
*/
|
||||
public function addRecord(string $name, string $type, string $content, int $priority, int $ttl, int $domain) : array
|
||||
{
|
||||
if (!in_array($type, $this->c['config']['records']['allowedTypes'])) {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM domains WHERE id=:id AND type IN (\'MASTER\',\'NATIVE\')');
|
||||
$query->bindValue(':id', $domain, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) { // Domain does not exist
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date)
|
||||
VALUES (:domainId, :name, :type, :content, :ttl, :prio, :changeDate)');
|
||||
$query->bindValue(':domainId', $domain, \PDO::PARAM_INT);
|
||||
$query->bindValue(':name', $name, \PDO::PARAM_STR);
|
||||
$query->bindValue(':type', $type, \PDO::PARAM_STR);
|
||||
$query->bindValue(':content', $content, \PDO::PARAM_STR);
|
||||
$query->bindValue(':ttl', $ttl, \PDO::PARAM_INT);
|
||||
$query->bindValue(':prio', $priority, \PDO::PARAM_INT);
|
||||
$query->bindValue(':changeDate', time(), \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,name,type,content,prio AS priority,ttl,domain_id AS domain FROM records
|
||||
ORDER BY id DESC LIMIT 1');
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
$record['id'] = intval($record['id']);
|
||||
$record['priority'] = intval($record['priority']);
|
||||
$record['ttl'] = intval($record['ttl']);
|
||||
$record['domain'] = intval($record['domain']);
|
||||
|
||||
$soa = new \Operations\Soa($this->c);
|
||||
$soa->updateSerial($domain);
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete record
|
||||
*
|
||||
* @param $id Id of the record to delete
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if record does not exist
|
||||
*/
|
||||
public function deleteRecord(int $id) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,domain_id FROM records WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) { //Domain does not exist
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$domainId = intval($record['domain_id']);
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM remote WHERE record=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM records WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$soa = new \Operations\Soa($this->c);
|
||||
$soa->updateSerial($domainId);
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get record
|
||||
*
|
||||
* @param $recordId Name of the record
|
||||
*
|
||||
* @return array Record entry
|
||||
*
|
||||
* @throws NotFoundException if the record does not exist
|
||||
*/
|
||||
public function getRecord(int $recordId) : array
|
||||
{
|
||||
$query = $this->db->prepare('SELECT id,name,type,content,prio AS priority,ttl,domain_id AS domain FROM records
|
||||
WHERE id=:recordId');
|
||||
$query->bindValue(':recordId', $recordId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$record['id'] = intval($record['id']);
|
||||
$record['priority'] = intval($record['priority']);
|
||||
$record['ttl'] = intval($record['ttl']);
|
||||
$record['domain'] = intval($record['domain']);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/** Update Record
|
||||
*
|
||||
* If params are null do not change
|
||||
*
|
||||
* @param $recordId Record to update
|
||||
* @param $name New name
|
||||
* @param $type New type
|
||||
* @param $content New content
|
||||
* @param $priority New priority
|
||||
* @param $ttl New ttl
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException The given record does not exist
|
||||
* @throws SemanticException The given record type is invalid
|
||||
*/
|
||||
public function updateRecord(int $recordId, ? string $name, ? string $type, ? string $content, ? int $priority, ? int $ttl)
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,domain_id,name,type,content,prio,ttl FROM records WHERE id=:recordId');
|
||||
$query->bindValue(':recordId', $recordId);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
if ($type !== null && !in_array($type, $this->c['config']['records']['allowedTypes'])) {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$domainId = intval($record['domain_id']);
|
||||
|
||||
$name = $name === null ? $record['name'] : $name;
|
||||
$type = $type === null ? $record['type'] : $type;
|
||||
$content = $content === null ? $record['content'] : $content;
|
||||
$priority = $priority === null ? intval($record['prio']) : $priority;
|
||||
$ttl = $ttl === null ? intval($record['ttl']) : $ttl;
|
||||
|
||||
$query = $this->db->prepare('UPDATE records SET name=:name,type=:type,content=:content,prio=:priority,ttl=:ttl WHERE id=:recordId');
|
||||
$query->bindValue('recordId', $recordId);
|
||||
$query->bindValue(':name', $name);
|
||||
$query->bindValue(':type', $type);
|
||||
$query->bindValue(':content', $content);
|
||||
$query->bindValue(':priority', $priority);
|
||||
$query->bindValue(':ttl', $ttl);
|
||||
$query->execute();
|
||||
|
||||
$soa = new \Operations\Soa($this->c);
|
||||
$soa->updateSerial($domainId);
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
}
|
118
backend/src/operations/Remote.php
Normal file
118
backend/src/operations/Remote.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for the remote api.
|
||||
*/
|
||||
class Remote
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given record with password
|
||||
*
|
||||
* @param $record Name of the new record
|
||||
* @param $content Type of the new record
|
||||
* @param $password Content of the new record
|
||||
*
|
||||
* @throws NotFoundException if the record does not exist
|
||||
* @throws ForbiddenException if the password is not valid for the record
|
||||
*/
|
||||
public function updatePassword(int $record, string $content, string $password) : void
|
||||
{
|
||||
$query = $this->db->prepare('SELECT id FROM records WHERE id=:record');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT security FROM remote WHERE record=:record AND type=\'password\'');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$validPwFound = false;
|
||||
|
||||
while ($row = $query->fetch()) {
|
||||
if (password_verify($password, $row['security'])) {
|
||||
$validPwFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$validPwFound) {
|
||||
throw new \Exceptions\ForbiddenException();
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
$records->updateRecord($record, null, null, $content, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given record with password
|
||||
*
|
||||
* @param $record Name of the new record
|
||||
* @param $content Type of the new record
|
||||
* @param $time Timestamp of the signature
|
||||
* @param $signature Signature
|
||||
*
|
||||
* @throws NotFoundException if the record does not exist
|
||||
* @throws ForbiddenException if the signature is not valid for the record
|
||||
*/
|
||||
public function updateKey(int $record, string $content, int $time, string $signature) : void
|
||||
{
|
||||
$timestampWindow = $this->c['config']['remote']['timestampWindow'];
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM records WHERE id=:record');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT security FROM remote WHERE record=:record AND type=\'key\'');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if (abs($time - time()) > $timestampWindow) {
|
||||
throw new \Exceptions\ForbiddenException();
|
||||
}
|
||||
|
||||
$validKeyFound = false;
|
||||
|
||||
$verifyString = $record . $content . $time;
|
||||
|
||||
while ($row = $query->fetch()) {
|
||||
if (openssl_verify($verifyString, base64_decode($signature), $row['security'], OPENSSL_ALGO_SHA512)) {
|
||||
$validKeyFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$validKeyFound) {
|
||||
throw new \Exceptions\ForbiddenException();
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
$records->updateRecord($record, null, null, $content, null, null);
|
||||
}
|
||||
}
|
66
backend/src/operations/Sessionstorage.php
Normal file
66
backend/src/operations/Sessionstorage.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This is a proxy class which load the configured plugin as
|
||||
* backend and proxies queries to it.
|
||||
*/
|
||||
class Sessionstorage
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var InterfaceSessionstorage */
|
||||
private $backend;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
|
||||
$config = $c['config']['sessionstorage'];
|
||||
|
||||
$plugin = $config['plugin'];
|
||||
$pluginConfig = $config['config'];
|
||||
|
||||
$pluginClass = '\\Plugins\\Sessionstorage\\' . $plugin;
|
||||
|
||||
//Check if plugin is available
|
||||
if (!class_exists($pluginClass)) {
|
||||
$this->logger->critical('The configured session storage plugin does not exist', ['plugin' => $plugin]);
|
||||
exit();
|
||||
}
|
||||
|
||||
//Try to create class with given name
|
||||
$this->backend = new $pluginClass($this->logger, $pluginConfig);
|
||||
|
||||
if (!$this->backend instanceof \Plugins\Sessionstorage\InterfaceSessionstorage) {
|
||||
$this->logger->critical('The configured plugin does not implement InterfaceSessionstorage', ['pluginname' => $plugin]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$this->logger->debug("Session storage plugin was loaded", ['plugin' => $plugin]);
|
||||
}
|
||||
|
||||
public function set(string $key, string $value, int $ttl) : void
|
||||
{
|
||||
$this->backend->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
public function exists(string $key) : bool
|
||||
{
|
||||
return $this->backend->exists($key);
|
||||
}
|
||||
|
||||
public function get(string $key, int $ttl) : string
|
||||
{
|
||||
return $this->backend->get($key, $ttl);
|
||||
}
|
||||
|
||||
public function delete(string $key) : void
|
||||
{
|
||||
$this->backend->delete($key);
|
||||
}
|
||||
}
|
220
backend/src/operations/Soa.php
Normal file
220
backend/src/operations/Soa.php
Normal file
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying soa records.
|
||||
*/
|
||||
class Soa
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of domains according to filter criteria
|
||||
*
|
||||
* @param $domainId Domain to update soa
|
||||
* @param $mail Mail of zone master
|
||||
* @param $primary The primary nameserver
|
||||
* @param $refresh The refresh interval
|
||||
* @param $retry The retry interval
|
||||
* @param $expire The expire timeframe
|
||||
* @param $ttl The zone ttl
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException If the given domain does not exist
|
||||
*/
|
||||
public function setSoa(int $domainId, string $mail, string $primary, int $refresh, int $retry, int $expire, int $ttl)
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,name,type FROM domains WHERE id=:id');
|
||||
$query->bindValue(':id', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
} elseif ($record['type'] === 'SLAVE') {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\SemanticException();
|
||||
} else {
|
||||
$domainName = $record['name'];
|
||||
}
|
||||
|
||||
//Generate soa content string without serial
|
||||
$soaArray = [
|
||||
$primary,
|
||||
$this->fromEmail($mail),
|
||||
'serial',
|
||||
$refresh,
|
||||
$retry,
|
||||
$expire,
|
||||
$ttl
|
||||
];
|
||||
|
||||
$query = $this->db->prepare('SELECT content FROM records WHERE domain_id=:id AND type=\'SOA\'');
|
||||
$query->bindValue(':id', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$content = $query->fetch();
|
||||
|
||||
if ($content === false) { //No soa exists yet
|
||||
$soaArray[2] = strval($this->calculateSerial(0));
|
||||
$soaString = implode(' ', $soaArray);
|
||||
$changeDate = strval(time());
|
||||
|
||||
$query = $this->db->prepare('
|
||||
INSERT INTO records (domain_id, name, type, content, ttl, change_date)
|
||||
VALUES (:domainId, :name, \'SOA\', :content, :ttl, :changeDate)
|
||||
');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':name', $domainName, \PDO::PARAM_STR);
|
||||
$query->bindValue(':content', $soaString, \PDO::PARAM_STR);
|
||||
$query->bindValue(':ttl', $ttl, \PDO::PARAM_STR);
|
||||
$query->bindValue(':changeDate', $changeDate, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
} else {
|
||||
$oldSerial = intval(explode(' ', $content['content'])[2]);
|
||||
|
||||
$soaArray[2] = strval($this->calculateSerial($oldSerial));
|
||||
$soaString = implode(' ', $soaArray);
|
||||
$changeDate = strval(time());
|
||||
|
||||
$query = $this->db->prepare('UPDATE records SET content=:content, ttl=:ttl,
|
||||
change_date=:changeDate WHERE domain_id=:domainId AND type=\'SOA\'');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->bindValue(':content', $soaString, \PDO::PARAM_STR);
|
||||
$query->bindValue(':ttl', $ttl, \PDO::PARAM_STR);
|
||||
$query->bindValue(':changeDate', $changeDate, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get soa record for domain
|
||||
*
|
||||
* @param $domainId Domain to get soa from
|
||||
*
|
||||
* @return array Soa data as associative array
|
||||
*/
|
||||
public function getSoa(int $domainId)
|
||||
{
|
||||
$query = $this->db->prepare('SELECT content FROM records WHERE domain_id=:domainId AND type=\'SOA\'');
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$soaArray = explode(' ', $record['content']);
|
||||
|
||||
return [
|
||||
'primary' => $soaArray[0],
|
||||
'email' => $this->toEmail($soaArray[1]),
|
||||
'serial' => intval($soaArray[2]),
|
||||
'refresh' => intval($soaArray[3]),
|
||||
'retry' => intval($soaArray[4]),
|
||||
'expire' => intval($soaArray[5]),
|
||||
'ttl' => intval($soaArray[6])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the serial number of the given domain to the next required.
|
||||
*
|
||||
* If domain has no present soa record this method does nothing.
|
||||
*
|
||||
* @param $domainId Domain to update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateSerial(int $domainId) : void
|
||||
{
|
||||
$query = $this->db->prepare('SELECT content FROM records WHERE domain_id=:id AND type=\'SOA\'');
|
||||
$query->bindValue(':id', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$content = $query->fetch();
|
||||
|
||||
if ($content === false) {
|
||||
$this->logger->warning('Trying to update serial of domain without soa set it first', ['domainId' => $domainId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$soaArray = explode(' ', $content['content']);
|
||||
$soaArray[2] = strval($this->calculateSerial(intval($soaArray[2])));
|
||||
$soaString = implode(' ', $soaArray);
|
||||
|
||||
$query = $this->db->prepare('UPDATE records SET content=:content WHERE domain_id=:domainId AND type=\'SOA\'');
|
||||
$query->bindValue(':content', $soaString, \PDO::PARAM_STR);
|
||||
$query->bindValue(':domainId', $domainId, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate new serial from old
|
||||
*
|
||||
* @param $oldSerial Old serial number
|
||||
*
|
||||
* @return int New serial number
|
||||
*/
|
||||
private function calculateSerial(int $oldSerial) : int
|
||||
{
|
||||
$time = new \DateTime(null, new \DateTimeZone('UTC'));
|
||||
$currentTime = intval($time->format('Ymd')) * 100;
|
||||
|
||||
return \max($oldSerial + 1, $currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert email to soa mail string
|
||||
*
|
||||
* @param $email Email address
|
||||
*
|
||||
* @return string Soa email address
|
||||
*/
|
||||
private function fromEmail(string $email)
|
||||
{
|
||||
$parts = explode('@', $email);
|
||||
$parts[0] = str_replace('.', '\.', $parts[0]);
|
||||
$parts[] = '';
|
||||
return rtrim(implode(".", $parts), ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert soa mail to mail string
|
||||
*
|
||||
* @param $soaMail Soa email address
|
||||
*
|
||||
* @return string Email address
|
||||
*/
|
||||
private function toEmail(string $soaEmail)
|
||||
{
|
||||
$tmp = preg_replace('/([^\\\\])\\./', '\\1@', $soaEmail, 1);
|
||||
$tmp = preg_replace('/\\\\\\./', ".", $tmp);
|
||||
$tmp = preg_replace('/\\.$/', "", $tmp);
|
||||
return $tmp;
|
||||
}
|
||||
}
|
163
backend/src/operations/UserAuth.php
Normal file
163
backend/src/operations/UserAuth.php
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Exceptions\PluginNotFoundException as PluginNotFoundException;
|
||||
|
||||
/**
|
||||
* This class provides user authentication for the application.
|
||||
* Its main purpose is to find the apropriate authentication
|
||||
* plugin. It also ensures that a user entry for that user is
|
||||
* in the database.
|
||||
*/
|
||||
class UserAuth
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user with username/password combination.
|
||||
*
|
||||
* @param $username Username
|
||||
* @param $password Password
|
||||
*
|
||||
* @return int -1 if authentication failed, the user id otherwise
|
||||
*
|
||||
* @throws \Exceptions\PluginNotFoundExecption if no matching backend can be found
|
||||
*/
|
||||
public function authenticate(string $username, string $password) : int
|
||||
{
|
||||
if (strpos($username, '/') === false) { // no explicit backend specification
|
||||
$prefix = 'default';
|
||||
$name = $username;
|
||||
} else {
|
||||
$parts = preg_split('/\//', $username, 2);
|
||||
$prefix = $parts[0];
|
||||
$name = $parts[1];
|
||||
}
|
||||
|
||||
$this->logger->debug('Trying to authenticate with info', ['prefix' => $prefix, 'name' => $name]);
|
||||
|
||||
try {
|
||||
$backend = '';
|
||||
if ($this->authenticateBackend($prefix, $name, $password, $backend)) {
|
||||
return $this->localUser($backend, $name, $password);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} catch (\Exceptions\PluginNotFoundException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function searches for an apropriate backend and calls it
|
||||
* to authenticate the user.
|
||||
*
|
||||
* @param $backend The name of the backend to use
|
||||
* @param $username The username to use
|
||||
* @param $password The password to use
|
||||
* @param $backendId Output to return the backend id used
|
||||
*
|
||||
* @return bool true if authentication successfull false otherwise
|
||||
*
|
||||
* @throws \Exceptions\PluginNotFoundExecption if no matching backend can be found
|
||||
*/
|
||||
private function authenticateBackend(string $backend, string $username, string $password, string &$backendId) : bool
|
||||
{
|
||||
$config = $this->c['config']['authentication'];
|
||||
|
||||
$configForPrefix = array_filter($config, function ($v, $k) use ($backend) {
|
||||
return $backend === $v['prefix'];
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
if (count($configForPrefix) === 0) { // Check if backend is configured for prefix
|
||||
$this->logger->warning('No authentication backend configured for prefix', ['prefix' => $backend]);
|
||||
throw new PluginNotFoundException('No authentication backend configured for this user.');
|
||||
} elseif (count($configForPrefix) > 1) {
|
||||
$this->logger->error('Two authentication backends configured for prefix.', ['prefix' => $backend]);
|
||||
}
|
||||
|
||||
$backendId = array_keys($configForPrefix)[0];
|
||||
|
||||
$plugin = $config[$backendId]['plugin'];
|
||||
$pluginClass = '\\Plugins\\UserAuth\\' . $plugin;
|
||||
$pluginConfig = $config[$backendId]['config'];
|
||||
|
||||
if (!class_exists($pluginClass)) { // Check if given backend class exists
|
||||
$this->logger->error('The configured UserAuth plugin does not exist', ['prefix' => $backend, 'plugin' => $plugin]);
|
||||
throw new PluginNotFoundException('The authentication request can not be processed.');
|
||||
}
|
||||
|
||||
//Try to create class with given name
|
||||
$backendObj = new $pluginClass($this->logger, $this->db, $pluginConfig);
|
||||
|
||||
if (!$backendObj instanceof \Plugins\UserAuth\InterfaceUserAuth) { // Check if class implements interface
|
||||
$this->logger->error('The configured plugin does not implement InterfaceUserAuth', ['plugin' => $plugin, 'prefix' => $backend]);
|
||||
throw new PluginNotFoundException('The authentication request can not be processed.');
|
||||
}
|
||||
|
||||
$this->logger->debug("UserAuth plugin was loaded", ['plugin' => $plugin, 'prefix' => $backend, 'backend' => $backendId]);
|
||||
|
||||
return $backendObj->authenticate($username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the user from the given backend has a entry in the local database,
|
||||
* then returns the user id.
|
||||
*
|
||||
* @param $backend The name of the backend to use
|
||||
* @param $username The username to use
|
||||
* @param $password The password to use
|
||||
*
|
||||
* @return int The local user id
|
||||
*/
|
||||
private function localUser(string $backend, string $username, string $password) : int
|
||||
{
|
||||
$config = $this->c['config']['authentication'];
|
||||
$backendId = $config[$backend]['plugin'];
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM users WHERE name=:name AND backend=:backend');
|
||||
$query->bindValue(':name', $username, \PDO::PARAM_STR);
|
||||
$query->bindValue(':backend', $backendId, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
$insert = $this->db->prepare('INSERT INTO users (name,backend,type) VALUES (:name, :backend, \'user\')');
|
||||
$insert->bindValue(':name', $username, \PDO::PARAM_STR);
|
||||
$insert->bindValue(':backend', $backendId, \PDO::PARAM_STR);
|
||||
$insert->execute();
|
||||
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
$this->logger->info('Non existing user created', ['username' => $username, 'backendId' => $backendId, 'newId' => $record['id']]);
|
||||
} else {
|
||||
$this->logger->debug('User was found in database', ['username' => $username, 'backendId' => $backendId, 'id' => $record['id']]);
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record['id'];
|
||||
}
|
||||
}
|
309
backend/src/operations/Users.php
Normal file
309
backend/src/operations/Users.php
Normal file
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
namespace Operations;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This class provides functions for retrieving and modifying users.
|
||||
*/
|
||||
class Users
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var \Slim\Container */
|
||||
private $c;
|
||||
|
||||
public function __construct(\Slim\Container $c)
|
||||
{
|
||||
$this->logger = $c->logger;
|
||||
$this->db = $c->db;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of users according to filter criteria
|
||||
*
|
||||
* @param $pi PageInfo object, which is also updated with total page number
|
||||
* @param $nameQuery Search query, may be null
|
||||
* @param $type Type of the user, comma separated, null for no filter
|
||||
* @param $sorting Sort string in format 'field-asc,field2-desc', null for default
|
||||
*
|
||||
* @return array Array with matching users
|
||||
*/
|
||||
public function getUsers(\Utils\PagingInfo &$pi, ? string $nameQuery, ? string $type, ? string $sorting) : array
|
||||
{
|
||||
$config = $this->c['config']['authentication'];
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$nameQuery = $nameQuery !== null ? '%' . $nameQuery . '%' : '%';
|
||||
|
||||
//Count elements
|
||||
if ($pi->pageSize === null) {
|
||||
$pi->totalPages = 1;
|
||||
} else {
|
||||
$query = $this->db->prepare('
|
||||
SELECT COUNT(*) AS total
|
||||
FROM users U
|
||||
WHERE (U.name LIKE :nameQuery) AND
|
||||
(U.type IN ' . \Services\Database::makeSetString($this->db, $type) . ' OR :noTypeFilter)
|
||||
');
|
||||
|
||||
$query->bindValue(':nameQuery', $nameQuery, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
$record = $query->fetch();
|
||||
|
||||
$pi->totalPages = ceil($record['total'] / $pi->pageSize);
|
||||
}
|
||||
|
||||
//Query and return result
|
||||
$ordStr = \Services\Database::makeSortingString($sorting, [
|
||||
'id' => 'U.id',
|
||||
'name' => 'U.name',
|
||||
'type' => 'U.type'
|
||||
]);
|
||||
$pageStr = \Services\Database::makePagingString($pi);
|
||||
|
||||
$query = $this->db->prepare('
|
||||
SELECT id, name, type, backend
|
||||
FROM users U
|
||||
WHERE (U.name LIKE :nameQuery) AND
|
||||
(U.type IN ' . \Services\Database::makeSetString($this->db, $type) . ' OR :noTypeFilter)'
|
||||
. $ordStr . $pageStr);
|
||||
|
||||
$query->bindValue(':nameQuery', $nameQuery, \PDO::PARAM_STR);
|
||||
$query->bindValue(':noTypeFilter', intval($type === null), \PDO::PARAM_INT);
|
||||
|
||||
$query->execute();
|
||||
|
||||
$data = $query->fetchAll();
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
$dataTransformed = array_map(
|
||||
function ($item) use ($config) {
|
||||
if (!array_key_exists($item['backend'], $config)) {
|
||||
return null;
|
||||
}
|
||||
if (!array_key_exists('prefix', $config[$item['backend']])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prefix = $config[$item['backend']]['prefix'];
|
||||
|
||||
if ($prefix === 'default') {
|
||||
$name = $item['name'];
|
||||
} else {
|
||||
$name = $prefix . '/' . $item['name'];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => intval($item['id']),
|
||||
'name' => $name,
|
||||
'type' => $item['type'],
|
||||
'native' => $item['backend'] === 'native'
|
||||
];
|
||||
},
|
||||
$data
|
||||
);
|
||||
|
||||
return array_filter($dataTransformed, function ($v) {
|
||||
return $v !== null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new user
|
||||
*
|
||||
* @param $name Name of the new zone
|
||||
* @param $type Type of the new zone
|
||||
* @param $password Password for the new user
|
||||
*
|
||||
* @return array New user entry
|
||||
*
|
||||
* @throws AlreadyExistenException it the user exists already
|
||||
*/
|
||||
public function addUser(string $name, string $type, string $password) : array
|
||||
{
|
||||
if (!in_array($type, ['admin', 'user'])) {
|
||||
throw new \Exceptions\SemanticException();
|
||||
}
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM users WHERE name=:name AND backend=\'native\'');
|
||||
$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();
|
||||
}
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$query = $this->db->prepare('INSERT INTO users (name, backend, type, password) VALUES(:name, \'native\', :type, :password)');
|
||||
$query->bindValue(':name', $name, \PDO::PARAM_STR);
|
||||
$query->bindValue(':type', $type, \PDO::PARAM_STR);
|
||||
$query->bindValue(':password', $passwordHash, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,name,type FROM users WHERE name=:name AND backend=\'native\'');
|
||||
$query->bindValue(':name', $name, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
$record['id'] = intval($record['id']);
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
*
|
||||
* @param $id Id of the user to delete
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException if user does not exist
|
||||
*/
|
||||
public function deleteDomain(int $id) : void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM users WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) { //User does not exist
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM permissions WHERE user_id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $this->db->prepare('DELETE FROM users WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user
|
||||
*
|
||||
* @param $id Id of the user to get
|
||||
*
|
||||
* @return array User data
|
||||
*
|
||||
* @throws NotFoundException if user does not exist
|
||||
*/
|
||||
public function getUser(int $id) : array
|
||||
{
|
||||
$config = $this->c['config']['authentication'];
|
||||
|
||||
$query = $this->db->prepare('SELECT id,name,type,backend FROM users WHERE id=:id');
|
||||
$query->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
if (!array_key_exists($record['backend'], $config)) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
if (!array_key_exists('prefix', $config[$record['backend']])) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$prefix = $config[$record['backend']]['prefix'];
|
||||
|
||||
if ($prefix === 'default') {
|
||||
$name = $record['name'];
|
||||
} else {
|
||||
$name = $prefix . '/' . $record['name'];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => intval($record['id']),
|
||||
'name' => $name,
|
||||
'type' => $record['type'],
|
||||
'native' => $record['backend'] === 'native'
|
||||
];
|
||||
}
|
||||
|
||||
/** Update user
|
||||
*
|
||||
* If params are null do not change. If user is not native, name and password are ignored.
|
||||
*
|
||||
* @param $userId User to update
|
||||
* @param $name New name
|
||||
* @param $type New type
|
||||
* @param $password New password
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws NotFoundException The given record does not exist
|
||||
* @throws AlreadyExistentException The given record name does already exist
|
||||
*/
|
||||
public function updateUser(int $userId, ? string $name, ? string $type, ? string $password)
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$query = $this->db->prepare('SELECT id,name,type,backend,password FROM users WHERE id=:userId');
|
||||
$query->bindValue(':userId', $userId);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
$this->db->rollBack();
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
if ($record['backend'] !== 'native') {
|
||||
$name = null;
|
||||
$password = null;
|
||||
}
|
||||
|
||||
if ($record['backend'] === 'native' && $name !== null) {
|
||||
//Check if user with new name already exists
|
||||
$query = $this->db->prepare('SELECT id FROM users WHERE name=:name AND backend=\'native\'');
|
||||
$query->bindValue(':name', $name);
|
||||
$query->execute();
|
||||
$recordTest = $query->fetch();
|
||||
if ($recordTest !== false && intval($recordTest['id']) !== $userId) {
|
||||
throw new \Exceptions\AlreadyExistentException();
|
||||
}
|
||||
}
|
||||
|
||||
$name = $name === null ? $record['name'] : $name;
|
||||
$type = $type === null ? $record['type'] : $type;
|
||||
$password = $password === null ? $record['password'] : password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$query = $this->db->prepare('UPDATE users SET name=:name,type=:type,password=:password WHERE id=:userId');
|
||||
$query->bindValue(':userId', $userId);
|
||||
$query->bindValue(':name', $name);
|
||||
$query->bindValue(':type', $type);
|
||||
$query->bindValue(':password', $password);
|
||||
$query->execute();
|
||||
|
||||
$this->db->commit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\Sessionstorage;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This interface provides the neccessary functions for a session storage backend
|
||||
*/
|
||||
interface InterfaceSessionstorage
|
||||
{
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, array $config = null);
|
||||
|
||||
/**
|
||||
* Save new entry.
|
||||
*
|
||||
* @param $key The key for the entry
|
||||
* @param $value The value for the entry
|
||||
* @param $ttl The time (in s) for which this item should be available
|
||||
*/
|
||||
public function set(string $key, string $value, int $ttl) : void;
|
||||
|
||||
/**
|
||||
* Queries the existence of some entry.
|
||||
*
|
||||
* @param $key The key to query
|
||||
*/
|
||||
public function exists(string $key) : bool;
|
||||
|
||||
/**
|
||||
* Get the value for a given key. This should also reset the ttl to the given value.
|
||||
*
|
||||
* @param $key The key for the entry to get
|
||||
* @param $ttl The new ttl for the entry
|
||||
*/
|
||||
public function get(string $key, int $ttl) : string;
|
||||
|
||||
/**
|
||||
* Delete the value for a given key.
|
||||
*
|
||||
* @param $key The key to delete
|
||||
*/
|
||||
public function delete(string $key) : void;
|
||||
}
|
90
backend/src/plugins/Sessionstorage/apcu.php
Normal file
90
backend/src/plugins/Sessionstorage/apcu.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\Sessionstorage;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Implements a session storage plugin for using PHPs APCu.
|
||||
*/
|
||||
class apcu implements InterfaceSessionstorage
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, array $config = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
if (!function_exists('apcu_store')) {
|
||||
$this->logger->critical('PHP APCu extension is not available but configured as session storage backend exiting now');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save new entry.
|
||||
*
|
||||
* @param $key The key for the entry
|
||||
* @param $value The value for the entry
|
||||
* @param $ttl The time (in s) for which this item should be available
|
||||
*/
|
||||
public function set(string $key, string $value, int $ttl) : void
|
||||
{
|
||||
$this->logger->debug('Storing data to APCu', ['key' => $key, 'value' => $value, 'ttl' => $ttl]);
|
||||
|
||||
apcu_store($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the existence of some entry.
|
||||
*
|
||||
* @param $key The key to query
|
||||
*/
|
||||
public function exists(string $key) : bool
|
||||
{
|
||||
$this->logger->debug('Checking for APCu key existence', ['key' => $key]);
|
||||
|
||||
return apcu_exists($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given key. This should also reset the ttl to the given value.
|
||||
*
|
||||
* @param $key The key for the entry to get
|
||||
* @param $ttl The new ttl for the entry
|
||||
*/
|
||||
public function get(string $key, int $ttl) : string
|
||||
{
|
||||
$this->logger->debug('Getting data from APCu', ['key' => $key, 'ttl' => $ttl]);
|
||||
|
||||
$value = apcu_fetch($key);
|
||||
|
||||
if ($value == false) {
|
||||
$this->logger->error('Non existing key was queried from APCu', ['key' => $key]);
|
||||
throw new \InvalidArgumentException('The requested key was not in the database!');
|
||||
}
|
||||
|
||||
apcu_store($key, $value, $ttl);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the value for a given key.
|
||||
*
|
||||
* @param $key The key to delete
|
||||
*/
|
||||
public function delete(string $key) : void
|
||||
{
|
||||
$this->logger->debug('Deleting key from APCu', ['key' => $key]);
|
||||
|
||||
apcu_delete($key);
|
||||
}
|
||||
}
|
100
backend/src/plugins/Sessionstorage/memcached.php
Normal file
100
backend/src/plugins/Sessionstorage/memcached.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\Sessionstorage;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Implements a session storage plugin for using PHPs APCu.
|
||||
*/
|
||||
class memcached implements InterfaceSessionstorage
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \Memcached */
|
||||
private $memcached;
|
||||
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, array $config = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
if (!class_exists('Memcached')) {
|
||||
$this->logger->critical('PHP Memcached extension is not available but configured as session storage backend exiting now');
|
||||
exit();
|
||||
}
|
||||
$this->memcached = new \Memcached();
|
||||
if (!array_key_exists('host', $config) || !array_key_exists('port', $config)) {
|
||||
$this->logger->critical('Memcached session configuration missing host or port value');
|
||||
exit();
|
||||
}
|
||||
$this->memcached->addServer($config['host'], $config['port']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save new entry.
|
||||
*
|
||||
* @param $key The key for the entry
|
||||
* @param $value The value for the entry
|
||||
* @param $ttl The time (in s) for which this item should be available
|
||||
*/
|
||||
public function set(string $key, string $value, int $ttl) : void
|
||||
{
|
||||
$this->logger->debug('Storing data to Memcached', ['key' => $key, 'value' => $value, 'ttl' => $ttl]);
|
||||
|
||||
$this->memcached->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the existence of some entry.
|
||||
*
|
||||
* @param $key The key to query
|
||||
*/
|
||||
public function exists(string $key) : bool
|
||||
{
|
||||
$this->logger->debug('Checking for Memcached key existence', ['key' => $key]);
|
||||
|
||||
$this->memcached->get($key);
|
||||
return \Memcached::RES_NOTFOUND !== $this->memcached->getResultCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given key. This should also reset the ttl to the given value.
|
||||
*
|
||||
* @param $key The key for the entry to get
|
||||
* @param $ttl The new ttl for the entry
|
||||
*/
|
||||
public function get(string $key, int $ttl) : string
|
||||
{
|
||||
$this->logger->debug('Getting data from Memcached', ['key' => $key, 'ttl' => $ttl]);
|
||||
|
||||
$value = $this->memcached->get($key);
|
||||
|
||||
if ($value == false) {
|
||||
$this->logger->error('Non existing key was queried from Memcached', ['key' => $key]);
|
||||
throw new \InvalidArgumentException('The requested key was not in the database!');
|
||||
}
|
||||
|
||||
$this->memcached->touch($key, $ttl);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the value for a given key.
|
||||
*
|
||||
* @param $key The key to delete
|
||||
*/
|
||||
public function delete(string $key) : void
|
||||
{
|
||||
$this->logger->debug('Deleting key from Memcached', ['key' => $key]);
|
||||
|
||||
$this->memcached->delete($key);
|
||||
}
|
||||
}
|
31
backend/src/plugins/UserAuth/InterfaceUserAuth.php
Normal file
31
backend/src/plugins/UserAuth/InterfaceUserAuth.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\UserAuth;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This interface provides the neccessary functions for
|
||||
* a user authentication backend.
|
||||
*/
|
||||
interface InterfaceUserAuth
|
||||
{
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $db Database connection
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, \PDO $db, array $config = null);
|
||||
|
||||
/**
|
||||
* Authenticate user.
|
||||
*
|
||||
* @param $username The username for authentication
|
||||
* @param $password The password for authentication
|
||||
*
|
||||
* @return true if valid false otherwise
|
||||
*/
|
||||
public function authenticate(string $username, string $password) : bool;
|
||||
}
|
54
backend/src/plugins/UserAuth/config.php
Normal file
54
backend/src/plugins/UserAuth/config.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\UserAuth;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This provides a simple user auth mechanism where users can be
|
||||
* stored in the config file. The config property therefore should
|
||||
* be a array mapping usernames to results of password_hash()
|
||||
*/
|
||||
|
||||
class Config implements InterfaceUserAuth
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/** @var array */
|
||||
private $userList;
|
||||
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $db Database connection
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, \PDO $db, array $config = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->db = $db;
|
||||
$this->userList = $config ? $config : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user.
|
||||
*
|
||||
* @param $username The username for authentication
|
||||
* @param $password The password for authentication
|
||||
*
|
||||
* @return true if valid false otherwise
|
||||
*/
|
||||
public function authenticate(string $username, string $password) : bool
|
||||
{
|
||||
if (!array_key_exists($username, $this->userList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return password_verify($password, $this->userList[$username]);
|
||||
}
|
||||
}
|
54
backend/src/plugins/UserAuth/native.php
Normal file
54
backend/src/plugins/UserAuth/native.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Plugins\UserAuth;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* This provides the native authentication done in the
|
||||
* PDNSManager database.
|
||||
*/
|
||||
class Native implements InterfaceUserAuth
|
||||
{
|
||||
/** @var \Monolog\Logger */
|
||||
private $logger;
|
||||
|
||||
/** @var \PDO */
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Construct the object
|
||||
*
|
||||
* @param $logger Monolog logger instance for error handling
|
||||
* @param $db Database connection
|
||||
* @param $config The configuration for the Plugin if any was provided
|
||||
*/
|
||||
public function __construct(\Monolog\Logger $logger, \PDO $db, array $config = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user.
|
||||
*
|
||||
* @param $username The username for authentication
|
||||
* @param $password The password for authentication
|
||||
*
|
||||
* @return true if valid false otherwise
|
||||
*/
|
||||
public function authenticate(string $username, string $password) : bool
|
||||
{
|
||||
$query = $this->db->prepare('SELECT id, password FROM users WHERE name=:name AND backend=\'native\'');
|
||||
$query->bindValue(':name', $username, \PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
$record = $query->fetch();
|
||||
|
||||
if ($record === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return password_verify($password, $record['password']);
|
||||
}
|
||||
}
|
83
backend/src/public/index.php
Normal file
83
backend/src/public/index.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
// Load config
|
||||
$config = require('../config/ConfigDefault.php');
|
||||
|
||||
// If no config exists load installer
|
||||
if ($config === false) {
|
||||
require('setup.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Prepare dependency container
|
||||
$container = new \Slim\Container($config);
|
||||
|
||||
$container['logger'] = new \Services\Logger;
|
||||
$container['db'] = new \Services\Database;
|
||||
|
||||
$container['notFoundHandler'] = new \Controllers\NotFound;
|
||||
$container['notAllowedHandler'] = new \Controllers\NotAllowed;
|
||||
|
||||
// Create application
|
||||
$app = new \Slim\App($container);
|
||||
|
||||
// Configure routing
|
||||
$app->group('/v1', function () {
|
||||
$this->post('/sessions', '\Controllers\Sessions:post');
|
||||
|
||||
$this->get('/remote/ip', '\Controllers\Remote:ip');
|
||||
$this->get('/remote/servertime', '\Controllers\Remote:servertime');
|
||||
$this->get('/remote/updatepw', '\Controllers\Remote:updatePassword');
|
||||
$this->post('/remote/updatekey', '\Controllers\Remote:updateKey');
|
||||
|
||||
$this->get('/update', '\Controllers\Update:get');
|
||||
$this->post('/update', '\Controllers\Update:post');
|
||||
|
||||
$this->group('', function () {
|
||||
$this->delete('/sessions/{sessionId}', '\Controllers\Sessions:delete');
|
||||
|
||||
$this->get('/domains', '\Controllers\Domains:getList');
|
||||
$this->post('/domains', '\Controllers\Domains:postNew');
|
||||
$this->delete('/domains/{domainId}', '\Controllers\Domains:delete');
|
||||
$this->get('/domains/{domainId}', '\Controllers\Domains:getSingle');
|
||||
$this->put('/domains/{domainId}', '\Controllers\Domains:put');
|
||||
|
||||
$this->put('/domains/{domainId}/soa', '\Controllers\Domains:putSoa');
|
||||
$this->get('/domains/{domainId}/soa', '\Controllers\Domains:getSoa');
|
||||
|
||||
$this->get('/records', '\Controllers\Records:getList');
|
||||
$this->post('/records', '\Controllers\Records:postNew');
|
||||
$this->delete('/records/{recordId}', '\Controllers\Records:delete');
|
||||
$this->get('/records/{recordId}', '\Controllers\Records:getSingle');
|
||||
$this->put('/records/{recordId}', '\Controllers\Records:put');
|
||||
|
||||
$this->get('/records/{recordId}/credentials', '\Controllers\Credentials:getList');
|
||||
$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');
|
||||
|
||||
$this->get('/users', '\Controllers\Users:getList');
|
||||
$this->post('/users', '\Controllers\Users:postNew');
|
||||
$this->delete('/users/{user}', '\Controllers\Users:delete');
|
||||
$this->get('/users/{user}', '\Controllers\Users:getSingle');
|
||||
$this->put('/users/{user}', '\Controllers\Users:put');
|
||||
|
||||
$this->get('/users/{user}/permissions', '\Controllers\Permissions:getList');
|
||||
$this->post('/users/{user}/permissions', '\Controllers\Permissions:postNew');
|
||||
$this->delete('/users/{user}/permissions/{domainId}', '\Controllers\Permissions:delete');
|
||||
})->add('\Middlewares\Authentication');
|
||||
});
|
||||
|
||||
// Add global middlewares
|
||||
$app->add('\Middlewares\LogRequests');
|
||||
$app->add('\Middlewares\RejectEmptyBody');
|
||||
$app->add('\Middlewares\ClientIp');
|
||||
|
||||
// Run application
|
||||
$app->run();
|
23
backend/src/public/setup.php
Normal file
23
backend/src/public/setup.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
use \Slim\Http\Request as Request;
|
||||
use \Slim\Http\Response as Response;
|
||||
|
||||
if (file_exists('../config/ConfigUser.php')) {
|
||||
echo "Not accessible!";
|
||||
http_response_code(403);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Prepare dependency container
|
||||
$container = new \Slim\Container();
|
||||
|
||||
// Create application
|
||||
$app = new \Slim\App($container);
|
||||
|
||||
// Create route
|
||||
$app->post('/v1/setup', '\Controllers\Setup:setup');
|
||||
|
||||
// Run application
|
||||
$app->run();
|
124
backend/src/services/Database.php
Normal file
124
backend/src/services/Database.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Services;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class Database
|
||||
{
|
||||
public function __invoke(\Slim\Container $c)
|
||||
{
|
||||
$config = $c['config']['db'];
|
||||
|
||||
try {
|
||||
$pdo = new \PDO(
|
||||
'mysql:host=' . $config['host'] . ';port=' . $config['port'] . ';dbname=' . $config['dbname'],
|
||||
$config['user'],
|
||||
$config['password']
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
$c->logger->critical("SQL Connect Error: " . $e->getMessage());
|
||||
$c->logger->critical("DB Config was", $config);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
|
||||
} catch (\PDOException $e) {
|
||||
$c->logger->critical("SQL Parameter Error: " . $e->getMessage());
|
||||
exit();
|
||||
}
|
||||
|
||||
$c->logger->debug("Database setup successfull");
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a SQL LIMIT string from paging information
|
||||
*
|
||||
* @param $pi PagingInfo object to use
|
||||
*
|
||||
* @return string SQL string to use
|
||||
*/
|
||||
public static function makePagingString(\Utils\PagingInfo $pi) : string
|
||||
{
|
||||
if ($pi->pageSize === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($pi->page === null) {
|
||||
$pi->page = 1;
|
||||
}
|
||||
|
||||
$offset = ($pi->page - 1) * $pi->pageSize;
|
||||
|
||||
return ' LIMIT ' . intval($pi->pageSize) . ' OFFSET ' . intval($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a SQL ORDER BY string from order information.
|
||||
*
|
||||
* This is done from a string with format 'field-asc,field2-desc'
|
||||
* where fields are mapped to columns in param $colMap. This also
|
||||
* should prevent SQL injections.
|
||||
*
|
||||
* @param $sort Sort string
|
||||
* @param $colMap Map which assigns to each field name a column to use
|
||||
*
|
||||
* @return string SQL string to use
|
||||
*/
|
||||
public static function makeSortingString(? string $sort, array $colMap) : string
|
||||
{
|
||||
if ($sort === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$orderStrings = [];
|
||||
|
||||
foreach (explode(',', $sort) as $value) {
|
||||
$parts = explode('-', $value);
|
||||
|
||||
if (array_key_exists($parts[0], $colMap) && count($parts) == 2) { // is valid known field
|
||||
if ($parts[1] == 'asc') {
|
||||
$orderStrings[] = $colMap[$parts[0]] . ' ASC';
|
||||
} elseif ($parts[1] == 'desc') {
|
||||
$orderStrings[] = $colMap[$parts[0]] . ' DESC';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($orderStrings) == 0) { // none was valid
|
||||
return '';
|
||||
}
|
||||
|
||||
return ' ORDER BY ' . implode(', ', $orderStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a string which works to use with an IN SQL clause.
|
||||
*
|
||||
* Input is a comma separated list, all items are escaped and joint
|
||||
* to the form ('a','b')
|
||||
*
|
||||
* @param $db PDO object used for escaping
|
||||
* @param $input Comma separated list of items
|
||||
*
|
||||
* @return string SQL string to use
|
||||
*/
|
||||
public static function makeSetString(\PDO $db, ? string $input) : string
|
||||
{
|
||||
if ($input === null || $input === '') {
|
||||
return '(\'\')';
|
||||
}
|
||||
|
||||
$parts = explode(',', $input);
|
||||
|
||||
$partsEscaped = array_map(function ($item) use ($db) {
|
||||
return $db->quote($item, \PDO::PARAM_STR);
|
||||
}, $parts);
|
||||
|
||||
return '(' . implode(', ', $partsEscaped) . ')';
|
||||
}
|
||||
}
|
28
backend/src/services/Logger.php
Normal file
28
backend/src/services/Logger.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Services;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class Logger
|
||||
{
|
||||
public function __invoke(\Slim\Container $c)
|
||||
{
|
||||
$config = $c['config']['logging'];
|
||||
|
||||
$logger = new \Monolog\Logger('pdnsmanager');
|
||||
|
||||
$loglevel = \Monolog\Logger::toMonologLevel($config['level']);
|
||||
$path = $config['path'];
|
||||
|
||||
if (\strlen($path) > 0) {
|
||||
$fileHandler = new \Monolog\Handler\StreamHandler($path, $loglevel);
|
||||
$logger->pushHandler($fileHandler);
|
||||
} else {
|
||||
$errorLogHandler = new \Monolog\Handler\ErrorLogHandler(0, $loglevel);
|
||||
$logger->pushHandler($errorLogHandler);
|
||||
}
|
||||
|
||||
return $logger;
|
||||
}
|
||||
}
|
21
backend/src/sql/Update1.sql
Normal file
21
backend/src/sql/Update1.sql
Normal file
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS remote (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
record int(11) NOT NULL,
|
||||
description varchar(255) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
security varchar(2000) NOT NULL,
|
||||
nonce varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY record (record)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT `remote_ibfk_1` FOREIGN KEY (`record`) REFERENCES `records` (`id`);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS options (
|
||||
name varchar(255) NOT NULL,
|
||||
value varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
INSERT INTO options(name,value) VALUES ('schema_version', 1);
|
22
backend/src/sql/Update2.sql
Normal file
22
backend/src/sql/Update2.sql
Normal file
|
@ -0,0 +1,22 @@
|
|||
ALTER TABLE permissions
|
||||
DROP FOREIGN KEY permissions_ibfk_1;
|
||||
|
||||
ALTER TABLE permissions
|
||||
DROP FOREIGN KEY permissions_ibfk_2;
|
||||
|
||||
ALTER TABLE permissions
|
||||
ADD CONSTRAINT permissions_ibfk_1 FOREIGN KEY (domain) REFERENCES domains (id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE permissions
|
||||
ADD CONSTRAINT permissions_ibfk_2 FOREIGN KEY (user) REFERENCES user (id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE remote
|
||||
DROP FOREIGN KEY remote_ibfk_1;
|
||||
|
||||
ALTER TABLE remote
|
||||
ADD CONSTRAINT remote_ibfk_1 FOREIGN KEY (record) REFERENCES records (id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE records
|
||||
ADD CONSTRAINT records_ibfk_1 FOREIGN KEY (domain_id) REFERENCES domains (id) ON DELETE CASCADE;
|
||||
|
||||
UPDATE options SET value=2 WHERE name='schema_version';
|
12
backend/src/sql/Update3.sql
Normal file
12
backend/src/sql/Update3.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS domainmetadata (
|
||||
id INT AUTO_INCREMENT,
|
||||
domain_id INT NOT NULL,
|
||||
kind VARCHAR(32),
|
||||
content TEXT,
|
||||
PRIMARY KEY (id)
|
||||
) Engine=InnoDB;
|
||||
|
||||
ALTER TABLE records ADD disabled TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE records ADD auth TINYINT(1) DEFAULT 1;
|
||||
|
||||
UPDATE options SET value=3 WHERE name='schema_version';
|
12
backend/src/sql/Update4.sql
Normal file
12
backend/src/sql/Update4.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
ALTER TABLE permissions DROP FOREIGN KEY permissions_ibfk_2;
|
||||
|
||||
RENAME TABLE user TO users;
|
||||
|
||||
ALTER TABLE permissions CHANGE user userid INT(11);
|
||||
|
||||
ALTER TABLE permissions
|
||||
ADD CONSTRAINT permissions_ibfk_2 FOREIGN KEY (userid) REFERENCES users (id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE users ADD CONSTRAINT UNIQUE KEY user_name_index (name);
|
||||
|
||||
UPDATE options SET value=4 WHERE name='schema_version';
|
100
backend/src/sql/Update5.sql
Normal file
100
backend/src/sql/Update5.sql
Normal file
|
@ -0,0 +1,100 @@
|
|||
ALTER DATABASE CHARACTER SET utf8 COLLATE = utf8_general_ci;
|
||||
|
||||
ALTER TABLE `domainmetadata`
|
||||
ADD INDEX domainmetadata_idx (domain_id,kind);
|
||||
|
||||
ALTER TABLE `domains`
|
||||
DROP PRIMARY KEY,
|
||||
DROP INDEX name_index,
|
||||
ADD PRIMARY KEY(`id`),
|
||||
ADD UNIQUE INDEX name_index (name),
|
||||
CHANGE COLUMN notified_serial notified_serial int(10) unsigned NULL;
|
||||
|
||||
ALTER TABLE `permissions`
|
||||
DROP FOREIGN KEY permissions_ibfk_1,
|
||||
DROP FOREIGN KEY permissions_ibfk_2,
|
||||
DROP INDEX domain,
|
||||
DROP PRIMARY KEY,
|
||||
CHANGE `userid` `user_id` INT(11) NOT NULL,
|
||||
CHANGE `domain` `domain_id` INT(11) NOT NULL;
|
||||
|
||||
ALTER TABLE `permissions`
|
||||
ADD CONSTRAINT permissions_ibfk_1 FOREIGN KEY(user_id) REFERENCES `users`(id),
|
||||
ADD CONSTRAINT permissions_ibfk_2 FOREIGN KEY(domain_id) REFERENCES `domains`(id),
|
||||
ADD PRIMARY KEY(`domain_id`,`user_id`),
|
||||
ADD INDEX permissions_ibfk_3 (domain_id),
|
||||
COLLATE=utf8_general_ci;
|
||||
|
||||
ALTER TABLE `records`
|
||||
DROP FOREIGN KEY records_ibfk_1,
|
||||
DROP INDEX rec_name_index,
|
||||
DROP INDEX nametype_index,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY(`id`),
|
||||
DROP INDEX domain_id;
|
||||
|
||||
ALTER TABLE `records`
|
||||
ADD CONSTRAINT records_ibfk_1 FOREIGN KEY(domain_id) REFERENCES `domains`(id),
|
||||
ADD INDEX nametype_index (name,type),
|
||||
ADD INDEX domain_id (domain_id),
|
||||
ADD INDEX ordername (ordername),
|
||||
CHANGE COLUMN content content varchar(64000) NULL,
|
||||
CHANGE COLUMN type type varchar(10) NULL,
|
||||
ADD COLUMN ordername varchar(255) NULL AFTER disabled,
|
||||
CHANGE COLUMN prio prio int(11) NULL,
|
||||
CHANGE COLUMN auth auth tinyint(1) NULL DEFAULT '1';
|
||||
|
||||
ALTER TABLE `remote`
|
||||
DROP FOREIGN KEY remote_ibfk_1,
|
||||
DROP INDEX record,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY(`id`);
|
||||
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT remote_ibfk_1 FOREIGN KEY(record) REFERENCES `records`(id),
|
||||
ADD INDEX remote_ibfk_2 (record),
|
||||
COLLATE=utf8_general_ci;
|
||||
|
||||
ALTER TABLE `users`
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY(`id`),
|
||||
CHANGE COLUMN password password varchar(255) NULL AFTER type,
|
||||
ADD COLUMN backend varchar(50) NOT NULL AFTER name,
|
||||
COLLATE=utf8_general_ci;
|
||||
|
||||
CREATE TABLE `comments` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`type` varchar(10) NOT NULL,
|
||||
`modified_at` int(11) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`comment` text CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `cryptokeys` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`flags` int(11) NOT NULL,
|
||||
`active` tinyint(1) DEFAULT NULL,
|
||||
`content` text,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `supermasters` (
|
||||
`ip` varchar(64) NOT NULL,
|
||||
`nameserver` varchar(255) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`ip`, `nameserver`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `tsigkeys` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`algorithm` varchar(50) DEFAULT NULL,
|
||||
`secret` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
UPDATE options SET value=5 WHERE name='schema_version';
|
3
backend/src/sql/Update6.sql
Normal file
3
backend/src/sql/Update6.sql
Normal file
|
@ -0,0 +1,3 @@
|
|||
UPDATE users SET backend='native' WHERE backend='';
|
||||
|
||||
UPDATE options SET value=6 WHERE name='schema_version';
|
10
backend/src/sql/Update7.sql
Normal file
10
backend/src/sql/Update7.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
ALTER TABLE `remote`
|
||||
DROP FOREIGN KEY remote_ibfk_1;
|
||||
|
||||
ALTER TABLE records MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
|
||||
ALTER TABLE remote MODIFY record BIGINT;
|
||||
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT remote_ibfk_1 FOREIGN KEY(record) REFERENCES `records`(id);
|
||||
|
||||
UPDATE options SET value=7 WHERE name='schema_version';
|
231
backend/src/sql/setup.sql
Normal file
231
backend/src/sql/setup.sql
Normal file
|
@ -0,0 +1,231 @@
|
|||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `comments`
|
||||
--
|
||||
|
||||
CREATE TABLE `comments` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`type` varchar(10) NOT NULL,
|
||||
`modified_at` int(11) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`comment` text CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `cryptokeys`
|
||||
--
|
||||
|
||||
CREATE TABLE `cryptokeys` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`flags` int(11) NOT NULL,
|
||||
`active` tinyint(1) DEFAULT NULL,
|
||||
`content` text,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `domainmetadata`
|
||||
--
|
||||
|
||||
CREATE TABLE `domainmetadata` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`kind` varchar(32) DEFAULT NULL,
|
||||
`content` text,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `domains`
|
||||
--
|
||||
|
||||
CREATE TABLE `domains` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`master` varchar(128) DEFAULT NULL,
|
||||
`last_check` int(11) DEFAULT NULL,
|
||||
`type` varchar(6) NOT NULL,
|
||||
`notified_serial` int(10) UNSIGNED DEFAULT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `permissions`
|
||||
--
|
||||
|
||||
CREATE TABLE `permissions` (
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`domain_id`, `user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `records`
|
||||
--
|
||||
|
||||
CREATE TABLE `records` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`domain_id` int(11) DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`type` varchar(10) DEFAULT NULL,
|
||||
`content` varchar(64000) DEFAULT NULL,
|
||||
`ttl` int(11) DEFAULT NULL,
|
||||
`prio` int(11) DEFAULT NULL,
|
||||
`change_date` int(11) DEFAULT NULL,
|
||||
`disabled` tinyint(1) DEFAULT '0',
|
||||
`ordername` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
|
||||
`auth` tinyint(1) DEFAULT '1',
|
||||
PRIMARY KEY(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `remote`
|
||||
--
|
||||
|
||||
CREATE TABLE `remote` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`record` bigint(20) NOT NULL,
|
||||
`description` varchar(255) NOT NULL,
|
||||
`type` varchar(20) NOT NULL,
|
||||
`security` varchar(2000) NOT NULL,
|
||||
`nonce` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `supermasters`
|
||||
--
|
||||
|
||||
CREATE TABLE `supermasters` (
|
||||
`ip` varchar(64) NOT NULL,
|
||||
`nameserver` varchar(255) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`ip`, `nameserver`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tsigkeys`
|
||||
--
|
||||
|
||||
CREATE TABLE `tsigkeys` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`algorithm` varchar(50) DEFAULT NULL,
|
||||
`secret` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `users`
|
||||
--
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`backend` varchar(50) NOT NULL,
|
||||
`type` varchar(20) NOT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `options`
|
||||
--
|
||||
|
||||
CREATE TABLE `options` (
|
||||
`name` varchar(255) NOT NULL,
|
||||
`value` varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Dumping data for table `options`
|
||||
--
|
||||
|
||||
INSERT INTO `options` (`name`, `value`) VALUES
|
||||
('schema_version', '7');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `comments`
|
||||
--
|
||||
ALTER TABLE `comments`
|
||||
ADD KEY `comments_name_type_idx` (`name`,`type`),
|
||||
ADD KEY `comments_order_idx` (`domain_id`,`modified_at`);
|
||||
|
||||
--
|
||||
-- Indexes for table `cryptokeys`
|
||||
--
|
||||
ALTER TABLE `cryptokeys`
|
||||
ADD KEY `domainidindex` (`domain_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `domainmetadata`
|
||||
--
|
||||
ALTER TABLE `domainmetadata`
|
||||
ADD KEY `domainmetadata_idx` (`domain_id`,`kind`);
|
||||
|
||||
--
|
||||
-- Indexes for table `records`
|
||||
--
|
||||
ALTER TABLE `records`
|
||||
ADD KEY `nametype_index` (`name`,`type`),
|
||||
ADD KEY `domain_id` (`domain_id`),
|
||||
ADD KEY `ordername` (`ordername`);
|
||||
|
||||
|
||||
--
|
||||
-- Indexes for table `tsigkeys`
|
||||
--
|
||||
ALTER TABLE `tsigkeys`
|
||||
ADD UNIQUE KEY `namealgoindex` (`name`,`algorithm`);
|
||||
|
||||
--
|
||||
-- Constraints for table `permissions`
|
||||
--
|
||||
ALTER TABLE `permissions`
|
||||
ADD CONSTRAINT `permissions_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `permissions_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `records`
|
||||
--
|
||||
ALTER TABLE `records`
|
||||
ADD CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `remote`
|
||||
--
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT `remote_ibfk_1` FOREIGN KEY (`record`) REFERENCES `records` (`id`) ON DELETE CASCADE;
|
37
backend/src/utils/PagingInfo.php
Normal file
37
backend/src/utils/PagingInfo.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Utils;
|
||||
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
class PagingInfo
|
||||
{
|
||||
/** @var int */
|
||||
public $page;
|
||||
|
||||
/** @var int */
|
||||
public $pageSize;
|
||||
|
||||
/** @var int */
|
||||
public $totalPages;
|
||||
|
||||
public function __construct(? int $page, ? int $pageSize, int $totalPages = null)
|
||||
{
|
||||
$this->page = $page === null ? 1 : $page;
|
||||
$this->pageSize = $pageSize;
|
||||
$this->totalPages = $totalPages;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$val = [
|
||||
'page' => $this->page,
|
||||
'total' => $this->totalPages,
|
||||
'pagesize' => $this->pageSize
|
||||
];
|
||||
|
||||
return array_filter($val, function ($var) {
|
||||
return !is_null($var);
|
||||
});
|
||||
}
|
||||
}
|
396
backend/test/db.sql
Normal file
396
backend/test/db.sql
Normal file
|
@ -0,0 +1,396 @@
|
|||
-- phpMyAdmin SQL Dump
|
||||
-- version 4.5.4.1deb2ubuntu2
|
||||
-- http://www.phpmyadmin.net
|
||||
--
|
||||
-- Host: localhost
|
||||
-- Erstellungszeit: 25. Dez 2019 um 23:22
|
||||
-- Server-Version: 5.7.23-0ubuntu0.16.04.1
|
||||
-- PHP-Version: 7.0.30-0ubuntu0.16.04.1
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
DROP TABLE IF EXISTS `comments`, `cryptokeys`, `domainmetadata`, `domains`, `options`, `permissions`, `records`, `remote`, `supermasters`, `tsigkeys`, `users`;
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Datenbank: `pdnsnew`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `comments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
CREATE TABLE `comments` (
|
||||
`id` int(11) NOT NULL,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`type` varchar(10) NOT NULL,
|
||||
`modified_at` int(11) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`comment` text CHARACTER SET utf8 NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `cryptokeys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `cryptokeys`;
|
||||
CREATE TABLE `cryptokeys` (
|
||||
`id` int(11) NOT NULL,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`flags` int(11) NOT NULL,
|
||||
`active` tinyint(1) DEFAULT NULL,
|
||||
`content` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `domainmetadata`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `domainmetadata`;
|
||||
CREATE TABLE `domainmetadata` (
|
||||
`id` int(11) NOT NULL,
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`kind` varchar(32) DEFAULT NULL,
|
||||
`content` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `domains`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `domains`;
|
||||
CREATE TABLE `domains` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`master` varchar(128) DEFAULT NULL,
|
||||
`last_check` int(11) DEFAULT NULL,
|
||||
`type` varchar(6) NOT NULL,
|
||||
`notified_serial` int(10) UNSIGNED DEFAULT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `domains`
|
||||
--
|
||||
|
||||
INSERT INTO `domains` (`id`, `name`, `master`, `last_check`, `type`, `notified_serial`, `account`) VALUES
|
||||
(1, 'example.com', NULL, NULL, 'MASTER', NULL, NULL),
|
||||
(2, 'slave.example.net', '12.34.56.78', NULL, 'SLAVE', NULL, NULL),
|
||||
(3, 'foo.de', NULL, NULL, 'NATIVE', NULL, NULL),
|
||||
(4, 'bar.net', NULL, NULL, 'MASTER', NULL, NULL),
|
||||
(5, 'baz.org', NULL, NULL, 'MASTER', NULL, NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `options`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `options`;
|
||||
CREATE TABLE `options` (
|
||||
`name` varchar(255) NOT NULL,
|
||||
`value` varchar(2000) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `options`
|
||||
--
|
||||
|
||||
INSERT INTO `options` (`name`, `value`) VALUES
|
||||
('schema_version', '7');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `permissions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
CREATE TABLE `permissions` (
|
||||
`domain_id` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `permissions`
|
||||
--
|
||||
|
||||
INSERT INTO `permissions` (`domain_id`, `user_id`) VALUES
|
||||
(1, 2),
|
||||
(2, 2);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `records`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `records`;
|
||||
CREATE TABLE `records` (
|
||||
`id` bigint(20) NOT NULL,
|
||||
`domain_id` int(11) DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`type` varchar(10) DEFAULT NULL,
|
||||
`content` varchar(64000) DEFAULT NULL,
|
||||
`ttl` int(11) DEFAULT NULL,
|
||||
`prio` int(11) DEFAULT NULL,
|
||||
`change_date` int(11) DEFAULT NULL,
|
||||
`disabled` tinyint(1) DEFAULT '0',
|
||||
`ordername` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
|
||||
`auth` tinyint(1) DEFAULT '1'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `records`
|
||||
--
|
||||
|
||||
INSERT INTO `records` (`id`, `domain_id`, `name`, `type`, `content`, `ttl`, `prio`, `change_date`, `disabled`, `ordername`, `auth`) VALUES
|
||||
(1, 1, 'test.example.com', 'A', '12.34.56.78', 86400, 0, 1521645110, 0, NULL, 1),
|
||||
(2, 1, 'sdfdf.example.com', 'TXT', 'foo bar baz', 60, 10, 1522321931, 0, NULL, 1),
|
||||
(3, 1, 'foo.example.com', 'AAAA', '::1', 86400, 0, 1522321902, 0, NULL, 1),
|
||||
(4, 3, 'foo.de', 'A', '9.8.7.6', 86400, 0, 1522321989, 0, NULL, 1),
|
||||
(5, 1, 'example.com', 'SOA', 'ns1.example.com hostmaster.example.com 2018041300 3600 900 604800 86400', 86400, NULL, 1523629358, 0, NULL, 1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `remote`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `remote`;
|
||||
CREATE TABLE `remote` (
|
||||
`id` int(11) NOT NULL,
|
||||
`record` bigint(20) DEFAULT NULL,
|
||||
`description` varchar(255) NOT NULL,
|
||||
`type` varchar(20) NOT NULL,
|
||||
`security` varchar(2000) NOT NULL,
|
||||
`nonce` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `remote`
|
||||
--
|
||||
|
||||
INSERT INTO `remote` (`id`, `record`, `description`, `type`, `security`, `nonce`) VALUES
|
||||
(1, 1, 'Password Test', 'password', '$2y$10$abocd6jj/Tw4jzDtqTnjreNzwcerzkXwoVc.JvZBoZ6p0grEKDWoW', NULL),
|
||||
(2, 4, 'Key Test', 'key', '-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5mu3aH90uSXY9sVLgVSz\nKj4FEctrpFDPyVC4ufbJa/44fuLABFe+IizgZUheNBBO7FjpLJYvsL24o6TEeht4\no5j0KHrRHXqp4WQuAL3ZREv/AhNaOC9/xyjoGwUkKkdC2bIfh0J/ACkezxvUrPsh\nbzhzY+co/M9PqlgTbjKjvlv/pRj2dSp98FzUme3HCh7Nn1EOM3yPMtaKNA9Qkkz1\noalfR3xmJjIanoS9zcK77/yyQ8VwI//CgxvnpnWbORZG0B9W2ZBoI8Bj4zprbbFG\nKNmrb403wfDijYF3MXpSMjKvJ5YVuZsn35EWIi5tqFc0oV7Ryy9nBHzKeoYN7Szs\nrXIS5+ZcQDLuN+pqJ7ByVaw4aVn85py8IdO0IYD5xeKd1i0iqm+KSoFTS1jiNSZu\n6iVl4odixWtW7oPLYBbd/vD2F7Ua5cLd12Rs+6kEVtlpnIf7txyFQL4QHYJxB7fI\ny+m70mfufVvKbFh/mHkhe+Arv71ERDMfAV3AD8++axLqYfU/LLFzanjwIBctAA9a\nj++G0lwl1adURwnBeq8+YrMU4/wg9efquKXLR40dU9nkMJOm5tPm+XHt4o3wio4X\n2FqnD57I7qJCWVc00HtpeWno5vHL+eJu0TdxjBuYXnQfwa1z9pWvGaoBtg7tyHgv\ng7YZJzF1MW5N9ZqnkdFJVEsCAwEAAQ==\n-----END PUBLIC KEY-----', NULL),
|
||||
(3, 1, 'Key Test 2', 'key', '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrJ/UoQoN5rO1nwrWBNDr3TgPB\nkm6UmN/B6NY7RXcYTJOFEP6iWqTj9Pw8aT8/DSn2uTMeQK6kWNUAWmRaylQI2QHQ\ndPtrI6piTpjvKm+KbR+n3e4QJ/zOcg06cHYJJiyhPjfC12j3ZxINOV3LDbEKq4s0\nHxMGYZHPu+UezapeeQIDAQAB\n-----END PUBLIC KEY-----', NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `supermasters`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `supermasters`;
|
||||
CREATE TABLE `supermasters` (
|
||||
`ip` varchar(64) NOT NULL,
|
||||
`nameserver` varchar(255) NOT NULL,
|
||||
`account` varchar(40) CHARACTER SET utf8 NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `tsigkeys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tsigkeys`;
|
||||
CREATE TABLE `tsigkeys` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`algorithm` varchar(50) DEFAULT NULL,
|
||||
`secret` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`backend` varchar(50) NOT NULL,
|
||||
`type` varchar(20) NOT NULL,
|
||||
`password` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `users`
|
||||
--
|
||||
|
||||
INSERT INTO `users` (`id`, `name`, `backend`, `type`, `password`) VALUES
|
||||
(1, 'admin', 'native', 'admin', '$2y$10$9iIDHWgjY0pEsz8pZLXPx.gkMNDxTMzb7U0Um5hUGjKmUUHWQNXcW'),
|
||||
(2, 'user', 'native', 'user', '$2y$10$MktCI4XcfD0FpIFSkxex6OVifnIw3Nqw6QJueWmjVte99wx6XGBoq'),
|
||||
(3, 'configuser', 'config', 'user', NULL);
|
||||
|
||||
--
|
||||
-- Indizes der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `comments`
|
||||
--
|
||||
ALTER TABLE `comments`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `comments_name_type_idx` (`name`,`type`),
|
||||
ADD KEY `comments_order_idx` (`domain_id`,`modified_at`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `cryptokeys`
|
||||
--
|
||||
ALTER TABLE `cryptokeys`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `domainidindex` (`domain_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `domainmetadata`
|
||||
--
|
||||
ALTER TABLE `domainmetadata`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `domainmetadata_idx` (`domain_id`,`kind`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `domains`
|
||||
--
|
||||
ALTER TABLE `domains`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `options`
|
||||
--
|
||||
ALTER TABLE `options`
|
||||
ADD PRIMARY KEY (`name`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `permissions`
|
||||
--
|
||||
ALTER TABLE `permissions`
|
||||
ADD PRIMARY KEY (`domain_id`,`user_id`),
|
||||
ADD KEY `permissions_ibfk_2` (`user_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `records`
|
||||
--
|
||||
ALTER TABLE `records`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `nametype_index` (`name`,`type`),
|
||||
ADD KEY `domain_id` (`domain_id`),
|
||||
ADD KEY `ordername` (`ordername`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `remote`
|
||||
--
|
||||
ALTER TABLE `remote`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `remote_ibfk_1` (`record`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `supermasters`
|
||||
--
|
||||
ALTER TABLE `supermasters`
|
||||
ADD PRIMARY KEY (`ip`,`nameserver`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `tsigkeys`
|
||||
--
|
||||
ALTER TABLE `tsigkeys`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `namealgoindex` (`name`,`algorithm`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für exportierte Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `comments`
|
||||
--
|
||||
ALTER TABLE `comments`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `cryptokeys`
|
||||
--
|
||||
ALTER TABLE `cryptokeys`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `domainmetadata`
|
||||
--
|
||||
ALTER TABLE `domainmetadata`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `domains`
|
||||
--
|
||||
ALTER TABLE `domains`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `records`
|
||||
--
|
||||
ALTER TABLE `records`
|
||||
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `remote`
|
||||
--
|
||||
ALTER TABLE `remote`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `tsigkeys`
|
||||
--
|
||||
ALTER TABLE `tsigkeys`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
--
|
||||
-- Constraints der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `permissions`
|
||||
--
|
||||
ALTER TABLE `permissions`
|
||||
ADD CONSTRAINT `permissions_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `permissions_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `records`
|
||||
--
|
||||
ALTER TABLE `records`
|
||||
ADD CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `remote`
|
||||
--
|
||||
ALTER TABLE `remote`
|
||||
ADD CONSTRAINT `remote_ibfk_1` FOREIGN KEY (`record`) REFERENCES `records` (`id`);
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
61
backend/test/package-lock.json
generated
Normal file
61
backend/test/package-lock.json
generated
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "backend-test",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
|
||||
"integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10",
|
||||
"is-buffer": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"cartesian-product": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cartesian-product/-/cartesian-product-2.1.2.tgz",
|
||||
"integrity": "sha1-yahGLFSrGaDF/TIZKSLiOatMpP0="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node-rsa": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
|
||||
"integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
|
||||
"requires": {
|
||||
"asn1": "0.2.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
backend/test/package.json
Normal file
10
backend/test/package.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "backend-test",
|
||||
"version": "1.0.0",
|
||||
"description": "Dependencies for pdnsmanager test",
|
||||
"dependencies": {
|
||||
"axios": "^0.18.1",
|
||||
"cartesian-product": "^2.1.2",
|
||||
"node-rsa": "^0.4.2"
|
||||
}
|
||||
}
|
134
backend/test/test.sh
Executable file
134
backend/test/test.sh
Executable file
|
@ -0,0 +1,134 @@
|
|||
#!/bin/bash
|
||||
|
||||
function makeConfig() {
|
||||
source config.sh
|
||||
touch "logfile.log"
|
||||
cat <<EOM > "../src/config/ConfigOverride.php"
|
||||
<?php
|
||||
|
||||
return [
|
||||
'db' => [
|
||||
'host' => '$DBHOST',
|
||||
'user' => '$DBUSER',
|
||||
'password' => '$DBPASSWORD',
|
||||
'dbname' => '$DBNAME'
|
||||
],
|
||||
'logging' => [
|
||||
'level' => 'error',
|
||||
'path' => '../../test/logfile.log'
|
||||
],
|
||||
'authentication' => [
|
||||
'native' => [
|
||||
'plugin' => 'native',
|
||||
'prefix' => 'default',
|
||||
'config' => null
|
||||
],
|
||||
'config' => [
|
||||
'plugin' => 'config',
|
||||
'prefix' => 'config',
|
||||
'config' => [
|
||||
'configuser' => '\$2y\$10\$twlIJ0hYeaHqMsiM7OdLr.4HkV6/EEQneDg9uZiU.l7yn1bpxSD1.',
|
||||
'notindb' => '\$2y\$10\$z1dD1Q5u68l5iqEmqnOAVuoR5VWR77HUfxMUycJ9TdDG3H5dLZGVW'
|
||||
]
|
||||
]
|
||||
],
|
||||
'proxys' => ['127.0.0.1']
|
||||
];
|
||||
EOM
|
||||
}
|
||||
|
||||
function clearConfig() {
|
||||
rm "../src/config/ConfigOverride.php"
|
||||
rm "logfile.log"
|
||||
}
|
||||
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
SCRIPTPATH=$(dirname "$SCRIPT")
|
||||
|
||||
source config.sh
|
||||
|
||||
cd "$SCRIPTPATH"
|
||||
|
||||
if [ $# -lt 1 ]
|
||||
then
|
||||
echo "The script needs either run or all as parameter."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $1 == "run" ]
|
||||
then
|
||||
if [ $# -lt 2 ]
|
||||
then
|
||||
echo "run needs an argument."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
makeConfig
|
||||
|
||||
echo "Preparing Database"
|
||||
if [ -z "$DBPASSWORD" ]
|
||||
then
|
||||
mysql "-h$DBHOST" "-u$DBUSER" "$DBNAME" < db.sql
|
||||
else
|
||||
mysql "-h$DBHOST" "-u$DBUSER" "-p$DBPASSWORD" "$DBNAME" < db.sql
|
||||
fi
|
||||
|
||||
echo "Executing test"
|
||||
if ! node "tests/$2.js" "$TESTURL"
|
||||
then
|
||||
echo "Test failed"
|
||||
clearConfig
|
||||
exit 1
|
||||
else
|
||||
if [ $(cat logfile.log | wc -l) -gt 0 ]
|
||||
then
|
||||
echo "Errors in logfile:"
|
||||
cat "logfile.log"
|
||||
clearConfig
|
||||
exit 2
|
||||
else
|
||||
echo "Test successfull"
|
||||
clearConfig
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
elif [ $1 == "all" ]
|
||||
then
|
||||
for test in tests/*
|
||||
do
|
||||
makeConfig
|
||||
|
||||
echo -n $(basename $test .js) "..."
|
||||
|
||||
if [ -z "$DBPASSWORD" ]
|
||||
then
|
||||
mysql "-h$DBHOST" "-u$DBUSER" "$DBNAME" < db.sql
|
||||
else
|
||||
mysql "-h$DBHOST" "-u$DBUSER" "-p$DBPASSWORD" "$DBNAME" < db.sql
|
||||
fi
|
||||
|
||||
echo -n "..."
|
||||
|
||||
if ! node "$test" "$TESTURL"
|
||||
then
|
||||
clearConfig
|
||||
exit 1
|
||||
else
|
||||
if [ $(cat logfile.log | wc -l) -gt 0 ]
|
||||
then
|
||||
cat "logfile.log"
|
||||
clearConfig
|
||||
exit 2
|
||||
else
|
||||
echo " OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
clearConfig
|
||||
done
|
||||
else
|
||||
echo "$1 is not a valid command."
|
||||
exit 3
|
||||
fi
|
||||
|
||||
|
76
backend/test/testlib.js
Normal file
76
backend/test/testlib.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
const assert = require('assert');
|
||||
const axios = require('axios');
|
||||
|
||||
async function runTest(user, f) {
|
||||
const assertObj = {
|
||||
equal: assert.deepStrictEqual,
|
||||
true: assert.ok
|
||||
};
|
||||
|
||||
var requestObj = axios.create({
|
||||
baseURL: process.argv[2],
|
||||
validateStatus: function () { return true; }
|
||||
});
|
||||
|
||||
try {
|
||||
const token = await logIn(assertObj, requestObj, user);
|
||||
|
||||
requestObj = axios.create({
|
||||
baseURL: process.argv[2],
|
||||
validateStatus: function () { return true; },
|
||||
headers: { 'X-Authentication': token }
|
||||
});
|
||||
|
||||
await f(assertObj, requestObj);
|
||||
|
||||
await logOut(assertObj, requestObj, token);
|
||||
} catch (e) {
|
||||
if (e instanceof assert.AssertionError) {
|
||||
console.log(e.toString());
|
||||
console.log('\nExpected:');
|
||||
console.log(e.expected);
|
||||
console.log('\nGot:');
|
||||
console.log(e.actual);
|
||||
process.exit(2);
|
||||
} else {
|
||||
console.log(e.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function run(f) {
|
||||
await f();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function logIn(assert, req, username) {
|
||||
//Try to login with valid username and password
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: username,
|
||||
password: username
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'LOGIN: Status not valid');
|
||||
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;
|
||||
}
|
||||
|
||||
async function logOut(assert, req, token) {
|
||||
//Try to logout check if this works
|
||||
var res = await req({
|
||||
url: '/sessions/' + token,
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'LOGOUT: Answer should be successfull but empty');
|
||||
}
|
||||
|
||||
module.exports = runTest;
|
||||
module.exports.run = run;
|
312
backend/test/tests/credentials-crud.js
Normal file
312
backend/test/tests/credentials-crud.js
Normal file
|
@ -0,0 +1,312 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test missing field
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422);
|
||||
|
||||
//Test invalid type
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test',
|
||||
type: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 400);
|
||||
|
||||
//Test missing key
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test',
|
||||
type: 'key'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422);
|
||||
|
||||
//Test missing password
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test',
|
||||
type: 'password'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422);
|
||||
|
||||
//Test invalid key
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test',
|
||||
type: 'key',
|
||||
key: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 400);
|
||||
|
||||
//Test invalid record
|
||||
var res = await req({
|
||||
url: '/records/100/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test',
|
||||
type: 'password',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Not existent record should trigger error.');
|
||||
|
||||
//Add key (key is intensionally very short but valid) and get it
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test Key',
|
||||
type: 'key',
|
||||
key: '-----BEGIN PUBLIC KEY-----\nMDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMOLSxmtlYxSkEKep11gjq200PTKVUaA\nyalonAKxw3XnAgMBAAE=\n-----END PUBLIC KEY-----'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Adding key should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 4,
|
||||
description: 'Test Key',
|
||||
type: 'key',
|
||||
key: '-----BEGIN PUBLIC KEY-----\nMDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMOLSxmtlYxSkEKep11gjq200PTKVUaA\nyalonAKxw3XnAgMBAAE=\n-----END PUBLIC KEY-----'
|
||||
}, 'Adding credential data fail.');
|
||||
|
||||
var res = await req({
|
||||
url: '/records/1/credentials/4',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Added key should be found.');
|
||||
assert.equal(res.data, {
|
||||
id: 4,
|
||||
description: 'Test Key',
|
||||
type: 'key',
|
||||
key: '-----BEGIN PUBLIC KEY-----\nMDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMOLSxmtlYxSkEKep11gjq200PTKVUaA\nyalonAKxw3XnAgMBAAE=\n-----END PUBLIC KEY-----'
|
||||
}, 'Added key does not match.');
|
||||
|
||||
//Add password and get it
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test Password',
|
||||
type: 'password',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Adding password should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 5,
|
||||
description: 'Test Password',
|
||||
type: 'password',
|
||||
}, 'Adding credential data fail.');
|
||||
|
||||
var res = await req({
|
||||
url: '/records/1/credentials/5',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Added key should be found.');
|
||||
assert.equal(res.data, {
|
||||
id: 5,
|
||||
description: 'Test Password',
|
||||
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',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Deletion of entry should succeed.');
|
||||
|
||||
//Delete not existing entry
|
||||
var res = await req({
|
||||
url: '/records/1/credentials/100',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Deletion of not existing entry should fail.');
|
||||
|
||||
//Delete entry via wrong record
|
||||
var res = await req({
|
||||
url: '/records/4/credentials/5',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Deletion of entry via wrong record should fail.');
|
||||
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Add password with missing permissions
|
||||
var res = await req({
|
||||
url: '/records/4/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test Password',
|
||||
type: 'password',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Adding password should fail for missing permissions.');
|
||||
|
||||
//Add password with missing permissions
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'post',
|
||||
data: {
|
||||
description: 'Test Password',
|
||||
type: 'password',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Adding password should succeed for user.');
|
||||
assert.equal(res.data, {
|
||||
id: 6,
|
||||
description: 'Test Password',
|
||||
type: 'password',
|
||||
}, 'Adding credential data fail.');
|
||||
|
||||
//Delete entry
|
||||
var res = await req({
|
||||
url: '/records/1/credentials/6',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Deletion of entry should succeed for user.');
|
||||
|
||||
//Delete entry without permission
|
||||
var res = await req({
|
||||
url: '/records/4/credentials/2',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Deletion of entry without permission should fail.');
|
||||
});
|
||||
});
|
70
backend/test/tests/credentials-get.js
Normal file
70
backend/test/tests/credentials-get.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test query
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 1,
|
||||
description: 'Password Test',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
description: 'Key Test 2',
|
||||
type: 'key'
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
|
||||
//Test query
|
||||
var res = await req({
|
||||
url: '/records/4/credentials',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 2,
|
||||
description: 'Key Test',
|
||||
type: 'key'
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Test query
|
||||
var res = await req({
|
||||
url: '/records/1/credentials',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 1,
|
||||
description: 'Password Test',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
description: 'Key Test 2',
|
||||
type: 'key'
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
|
||||
//Test permissions
|
||||
var res = await req({
|
||||
url: '/records/4/credentials',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Request should fail without permissions.');
|
||||
});
|
||||
});
|
167
backend/test/tests/domain-soa.js
Normal file
167
backend/test/tests/domain-soa.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Try to set soa for non exitent domain
|
||||
var res = await req({
|
||||
url: '/domains/100/soa',
|
||||
method: 'put',
|
||||
data: {
|
||||
primary: 'ns1.example.com',
|
||||
email: 'hostmaster@example.com',
|
||||
refresh: 3600,
|
||||
retry: 900,
|
||||
expire: 604800,
|
||||
ttl: 86400
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Updating SOA for not existing domain should fail');
|
||||
|
||||
//Try to set soa for slave domain
|
||||
var res = await req({
|
||||
url: '/domains/2/soa',
|
||||
method: 'put',
|
||||
data: {
|
||||
primary: 'ns1.example.com',
|
||||
email: 'hostmaster@example.com',
|
||||
refresh: 3600,
|
||||
retry: 900,
|
||||
expire: 604800,
|
||||
ttl: 86400
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 405, 'Updating SOA for slave domain should fail');
|
||||
|
||||
//Try to set soa with missing fields
|
||||
var res = await req({
|
||||
url: '/domains/2/soa',
|
||||
method: 'put',
|
||||
data: {
|
||||
primary: 'ns1.example.com',
|
||||
retry: 900,
|
||||
expire: 604800,
|
||||
ttl: 86400
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Updating SOA with missing fields should fail.');
|
||||
|
||||
//Getting soa data from master zone without soa should fail
|
||||
var res = await req({
|
||||
url: '/domains/4/soa',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Not existing soa should trigger error');
|
||||
|
||||
//Getting soa data from slave zone should fail
|
||||
var res = await req({
|
||||
url: '/domains/2/soa',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Geting soa from slave should trigger error');
|
||||
|
||||
//Soa data for test
|
||||
var soaData = {
|
||||
primary: 'ns1.example.com',
|
||||
email: 'hostmaster@example.com',
|
||||
refresh: 3600,
|
||||
retry: 900,
|
||||
expire: 604800,
|
||||
ttl: 86400
|
||||
};
|
||||
|
||||
//Set soa for zone without one
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'put',
|
||||
data: soaData
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Updating SOA for Zone without one should succeed.');
|
||||
|
||||
//Get the new soa
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Getting soa should succeed.');
|
||||
const firstSerial = res.data.serial;
|
||||
delete res.data['serial'];
|
||||
assert.equal(res.data, soaData, 'The set and get data should be equal');
|
||||
|
||||
//Soa data for update test
|
||||
soaData = {
|
||||
primary: 'ns2.example.com',
|
||||
email: 'hostmasterFoo@example.com',
|
||||
refresh: 3601,
|
||||
retry: 901,
|
||||
expire: 604801,
|
||||
ttl: 86401
|
||||
};
|
||||
|
||||
//Update soa with new values
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'put',
|
||||
data: soaData
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Updating SOA for Zone should succeed.');
|
||||
|
||||
//Check if update suceeded
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Getting updated soa should succeed.');
|
||||
assert.true(firstSerial < res.data.serial, 'Serial value should increase with update');
|
||||
delete res.data['serial'];
|
||||
assert.equal(res.data, soaData, 'The set and get data should be equal after update');
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Soa data for test
|
||||
var soaData = {
|
||||
primary: 'ns1.example.com',
|
||||
email: 'hostmaster@example.com',
|
||||
refresh: 3600,
|
||||
retry: 900,
|
||||
expire: 604800,
|
||||
ttl: 86400
|
||||
};
|
||||
|
||||
//Updating soa for domain with permissions should work
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'put',
|
||||
data: soaData
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Updating SOA for Zone should succeed for user.');
|
||||
|
||||
//Get the updated soa
|
||||
var res = await req({
|
||||
url: '/domains/1/soa',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Getting soa should succeed for user.');
|
||||
delete res.data['serial'];
|
||||
assert.equal(res.data, soaData, 'The set and get data should be equal');
|
||||
|
||||
//Updating soa for domain with permissions should work
|
||||
var res = await req({
|
||||
url: '/domains/4/soa',
|
||||
method: 'put',
|
||||
data: soaData
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Updating SOA for Zone without permissions should fail.');
|
||||
});
|
||||
});
|
279
backend/test/tests/domains-crud.js
Normal file
279
backend/test/tests/domains-crud.js
Normal file
|
@ -0,0 +1,279 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('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: 'abc.de',
|
||||
type: 'FOO'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 400, 'Invalid domain type 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',
|
||||
master: '1.2.3.4'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Creation should be successfull');
|
||||
assert.equal(res.data, {
|
||||
id: 8,
|
||||
name: 'slave.de',
|
||||
type: 'SLAVE',
|
||||
master: '1.2.3.4'
|
||||
}, 'Creation result fail.')
|
||||
|
||||
//Get master domain
|
||||
var res = await req({
|
||||
url: '/domains/6',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Domain access for master domain should be OK.');
|
||||
assert.equal(res.data, {
|
||||
id: 6,
|
||||
name: 'master.de',
|
||||
type: 'MASTER',
|
||||
records: 0
|
||||
}, 'Master domain data mismatch');
|
||||
|
||||
//Get native domain
|
||||
var res = await req({
|
||||
url: '/domains/7',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Domain access for native domain should be OK.');
|
||||
assert.equal(res.data, {
|
||||
id: 7,
|
||||
name: 'native.de',
|
||||
type: 'NATIVE',
|
||||
records: 0
|
||||
}, 'Native domain data mismatch');
|
||||
|
||||
//Get slave domain
|
||||
var res = await req({
|
||||
url: '/domains/8',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Domain access for slave domain should be OK.');
|
||||
assert.equal(res.data, {
|
||||
id: 8,
|
||||
name: 'slave.de',
|
||||
type: 'SLAVE',
|
||||
records: 0,
|
||||
master: '1.2.3.4'
|
||||
}, 'Slave domain data mismatch');
|
||||
|
||||
//Update slave domain
|
||||
var res = await req({
|
||||
url: '/domains/8',
|
||||
method: 'put',
|
||||
data: {
|
||||
master: '9.8.7.6'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Slave update should return no content');
|
||||
|
||||
//Check if update succeded
|
||||
var res = await req({
|
||||
url: '/domains/8',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Slave domain should be accessible after update.');
|
||||
assert.equal(res.data.master, '9.8.7.6', 'Slave update had no effect');
|
||||
|
||||
//Check if update fails for non existing domain
|
||||
var res = await req({
|
||||
url: '/domains/100',
|
||||
method: 'put',
|
||||
data: {
|
||||
master: '9.8.7.6'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Update on not existing domain should fail.');
|
||||
|
||||
//Check if update fails for master zone
|
||||
var res = await req({
|
||||
url: '/domains/1',
|
||||
method: 'put',
|
||||
data: {
|
||||
master: '9.8.7.6'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 405, 'Update on master zone should fail.');
|
||||
|
||||
//Check if update fails for missing field
|
||||
var res = await req({
|
||||
url: '/domains/100',
|
||||
method: 'put',
|
||||
data: {
|
||||
foo: 'bar'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Update with missing master field should fail.');
|
||||
|
||||
//Delete not existing domain
|
||||
var res = await req({
|
||||
url: '/domains/100',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Non existing domain deletion should be 404.');
|
||||
|
||||
//Delete existing domain
|
||||
var res = await req({
|
||||
url: '/domains/8',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Deletion of domain 8 should be successfull.');
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Test insufficient privileges for add
|
||||
var res = await req({
|
||||
url: '/domains',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'foo.de'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Domain creation should be forbidden for users.')
|
||||
|
||||
//Test insufficient privileges for delete
|
||||
var res = await req({
|
||||
url: '/domains/1',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Domain deletion should be forbidden for users.');
|
||||
|
||||
//Test update for domain with permissions
|
||||
var res = await req({
|
||||
url: '/domains/2',
|
||||
method: 'put',
|
||||
data: {
|
||||
master: '9.8.7.6'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Update of slave zone should work if user has permissions.');
|
||||
|
||||
//Test insufficient permissions
|
||||
var res = await req({
|
||||
url: '/domains/3',
|
||||
method: 'put',
|
||||
data: {
|
||||
master: '9.8.7.6'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Update of slave zone should fail without permissions.');
|
||||
|
||||
//Test insufficient privileges for get
|
||||
var res = await req({
|
||||
url: '/domains/3',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Domain get for domain 3 should be forbidden.');
|
||||
|
||||
//Test privileges for get
|
||||
var res = await req({
|
||||
url: '/domains/1',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Domain access for domain 1 should be OK.');
|
||||
assert.equal(res.data, {
|
||||
id: 1,
|
||||
name: 'example.com',
|
||||
type: 'MASTER',
|
||||
records: 3
|
||||
}, 'Domain 3 data mismatch');
|
||||
});
|
||||
});
|
134
backend/test/tests/domains-get.js
Normal file
134
backend/test/tests/domains-get.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
const test = require('../testlib');
|
||||
const cartesianProduct = require('cartesian-product');
|
||||
|
||||
test.run(async function () {
|
||||
await test('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
|
||||
const sortCombinations = cartesianProduct([
|
||||
['', 'id-asc', 'id-desc'],
|
||||
['', 'name-asc', 'name-desc'],
|
||||
['', 'type-asc', 'type-desc'],
|
||||
['', 'records-asc', 'records-desc']
|
||||
]);
|
||||
|
||||
for (list of sortCombinations) {
|
||||
list = list.filter((str) => str.length > 0);
|
||||
var sortQuery = list.join(',');
|
||||
|
||||
var res = await req({
|
||||
url: '/domains?sort=' + sortQuery,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
|
||||
var sortedData = res.data.results.slice();
|
||||
sortedData.sort(function (a, b) {
|
||||
for (sort of list) {
|
||||
var spec = sort.split('-');
|
||||
if (a[spec[0]] < b[spec[0]]) {
|
||||
return spec[1] == 'asc' ? -1 : 1;
|
||||
} else if (a[spec[0]] > b[spec[0]]) {
|
||||
return spec[1] == 'asc' ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
assert.equal(res.data.results, sortedData, 'Sort failed for ' + res.config.url);
|
||||
}
|
||||
|
||||
//Test paging
|
||||
var res = await req({
|
||||
url: '/domains?pagesize=3',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.paging, {
|
||||
page: 1,
|
||||
total: 2,
|
||||
pagesize: 3
|
||||
}, 'Paging data fail for ' + res.config.url);
|
||||
assert.equal(res.data.results.length, 3, "Should be 3 results.");
|
||||
|
||||
var res = await req({
|
||||
url: '/domains?pagesize=3&page=2',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.paging, {
|
||||
page: 2,
|
||||
total: 2,
|
||||
pagesize: 3
|
||||
}, 'Paging data fail for ' + res.config.url);
|
||||
assert.equal(res.data.results.length, 2, "Should be 2 results.");
|
||||
|
||||
//Test query
|
||||
var res = await req({
|
||||
url: '/domains?query=.net&sort=id-asc',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 2,
|
||||
name: 'slave.example.net',
|
||||
type: 'SLAVE',
|
||||
master: '12.34.56.78',
|
||||
records: 0
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'bar.net',
|
||||
type: 'MASTER',
|
||||
records: 0
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
|
||||
//Type filter
|
||||
var res = await req({
|
||||
url: '/domains?type=NATIVE',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 3,
|
||||
name: 'foo.de',
|
||||
type: 'NATIVE',
|
||||
records: 1
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Type filter
|
||||
var res = await req({
|
||||
url: '/domains',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK for user');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 1,
|
||||
name: 'example.com',
|
||||
type: 'MASTER',
|
||||
records: 3
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'slave.example.net',
|
||||
type: 'SLAVE',
|
||||
master: '12.34.56.78',
|
||||
records: 0
|
||||
}
|
||||
], 'Result fail for user on ' + res.config.url);
|
||||
});
|
||||
});
|
111
backend/test/tests/permissions.js
Normal file
111
backend/test/tests/permissions.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test paging
|
||||
var res = await req({
|
||||
url: '/users/2/permissions?pagesize=1&page=2',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.paging, {
|
||||
page: 2,
|
||||
total: 2,
|
||||
pagesize: 1
|
||||
}, 'Paging data fail for ' + res.config.url);
|
||||
assert.equal(res.data.results.length, 1, "Should be 1 results.");
|
||||
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Get of permissions should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
domainId: '1',
|
||||
domainName: 'example.com'
|
||||
},
|
||||
{
|
||||
domainId: '2',
|
||||
domainName: 'slave.example.net'
|
||||
}
|
||||
], 'Get permissions result fail');
|
||||
|
||||
//Add permission with missing field
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'post',
|
||||
data: {
|
||||
foo: 100
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Add of permission should fail for missing field.');
|
||||
|
||||
//Add permission which exists
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'post',
|
||||
data: {
|
||||
domainId: 1
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Add of permission should succeed for existing permission.');
|
||||
|
||||
//Add permission which does not exist
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'post',
|
||||
data: {
|
||||
domainId: 3
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Add of permission should succeed for not existing permission.');
|
||||
|
||||
// Revoke the new permission
|
||||
var res = await req({
|
||||
url: '/users/2/permissions/3',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Revoking should succeed');
|
||||
|
||||
// Revoke the new permission again
|
||||
var res = await req({
|
||||
url: '/users/2/permissions/3',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Second revocation of the same permission should fail');
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Get of permissions should fail for user.');
|
||||
|
||||
var res = await req({
|
||||
url: '/users/2/permissions',
|
||||
method: 'post',
|
||||
data: {
|
||||
domainId: 100
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Add of permission should fail for user.');
|
||||
|
||||
var res = await req({
|
||||
url: '/users/2/permissions/1',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Revoke of permission should fail for user.');
|
||||
});
|
||||
});
|
279
backend/test/tests/records-crud.js
Normal file
279
backend/test/tests/records-crud.js
Normal file
|
@ -0,0 +1,279 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test missing fields
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'foo.abc.de',
|
||||
type: 'A'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Missing fields should trigger error.');
|
||||
|
||||
//Test invalid record type
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: "dns.example.com",
|
||||
type: "FOOBARBAZ",
|
||||
content: "1.2.3.4",
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 400, 'Invalid record type should trigger error.');
|
||||
|
||||
//Test adding for slave zone
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: "dns.example.com",
|
||||
type: "A",
|
||||
content: "1.2.3.4",
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 2
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Adding record for slave should trigger error.');
|
||||
|
||||
//Test adding for not existing zone
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: "dns.example.com",
|
||||
type: "A",
|
||||
content: "1.2.3.4",
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 100
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Adding record to not existing domain should trigger error.');
|
||||
|
||||
//Test adding of record
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Adding of record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 6,
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}, 'Adding record return data fail.');
|
||||
|
||||
//Get not existing record
|
||||
var res = await req({
|
||||
url: '/records/100',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Get of not existing record should fail.');
|
||||
|
||||
//Get created record
|
||||
var res = await req({
|
||||
url: '/records/6',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Get of created record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 6,
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}, 'Record data should be the same it was created with.');
|
||||
|
||||
|
||||
//Update record
|
||||
var res = await req({
|
||||
url: '/records/6',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'foo.example.com'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Updating record should succeed');
|
||||
|
||||
//Get updated record
|
||||
var res = await req({
|
||||
url: '/records/6',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Get updated record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 6,
|
||||
name: 'foo.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}, 'Updated record has wrong data.');
|
||||
|
||||
//Delete not existing record
|
||||
var res = await req({
|
||||
url: '/records/100',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Deletion of not existing record should fail.');
|
||||
|
||||
//Delete existing record
|
||||
var res = await req({
|
||||
url: '/records/6',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Deletion of existing record should succeed.');
|
||||
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Test insufficient privileges for add
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 3
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Adding of record should fail for user.');
|
||||
|
||||
//Test insufficient privileges for delete
|
||||
var res = await req({
|
||||
url: '/records/4',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Deletion of record should fail for user.');
|
||||
|
||||
//Test insufficient privileges for update
|
||||
var res = await req({
|
||||
url: '/records/4',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'foo.example.com',
|
||||
ttl: 60
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Updating record should succeed');
|
||||
|
||||
//Test adding of record
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Adding of record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 7,
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}, 'Adding record return data fail.');
|
||||
|
||||
//Get created record
|
||||
var res = await req({
|
||||
url: '/records/7',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Get of created record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 7,
|
||||
name: 'dns.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}, 'Record data should be the same it was created with.');
|
||||
|
||||
|
||||
//Update record
|
||||
var res = await req({
|
||||
url: '/records/7',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'foo.example.com',
|
||||
ttl: 60
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Updating record should succeed');
|
||||
|
||||
//Get updated record
|
||||
var res = await req({
|
||||
url: '/records/7',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Get updated record should succeed.');
|
||||
assert.equal(res.data, {
|
||||
id: 7,
|
||||
name: 'foo.example.com',
|
||||
type: 'A',
|
||||
content: '1.2.3.4',
|
||||
priority: 0,
|
||||
ttl: 60,
|
||||
domain: 1
|
||||
}, 'Updated record has wrong data.');
|
||||
|
||||
//Delete existing record
|
||||
var res = await req({
|
||||
url: '/records/7',
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Deletion of existing record should succeed.');
|
||||
});
|
||||
});
|
192
backend/test/tests/records-get.js
Normal file
192
backend/test/tests/records-get.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
const test = require('../testlib');
|
||||
const cartesianProduct = require('cartesian-product');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test sorting in all combinations
|
||||
const sortCombinations = cartesianProduct([
|
||||
['', 'id-asc', 'id-desc'],
|
||||
['', 'name-asc', 'name-desc'],
|
||||
['', 'type-asc', 'type-desc'],
|
||||
['', 'content-asc', 'content-desc'],
|
||||
['', 'priority-asc', 'priority-desc'],
|
||||
['', 'ttl-asc', 'ttl-desc'],
|
||||
]);
|
||||
|
||||
for (list of sortCombinations) {
|
||||
list = list.filter((str) => str.length > 0);
|
||||
var sortQuery = list.join(',');
|
||||
|
||||
var res = await req({
|
||||
url: '/records?sort=' + sortQuery,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
|
||||
var sortedData = res.data.results.slice();
|
||||
sortedData.sort(function (a, b) {
|
||||
for (sort of list) {
|
||||
var spec = sort.split('-');
|
||||
if (a[spec[0]] < b[spec[0]]) {
|
||||
return spec[1] == 'asc' ? -1 : 1;
|
||||
} else if (a[spec[0]] > b[spec[0]]) {
|
||||
return spec[1] == 'asc' ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
assert.equal(res.data.results, sortedData, 'Sort failed for ' + res.config.url);
|
||||
|
||||
assert.equal(res.data.results.filter((i) => i.type === 'SOA').length, 0, 'No soa should be in records');
|
||||
}
|
||||
|
||||
//Test paging
|
||||
var res = await req({
|
||||
url: '/records?pagesize=2',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.paging, {
|
||||
page: 1,
|
||||
total: 2,
|
||||
pagesize: 2
|
||||
}, 'Paging data fail for ' + res.config.url);
|
||||
assert.equal(res.data.results.length, 2, "Should be 2 results.");
|
||||
|
||||
var res = await req({
|
||||
url: '/records?pagesize=2&page=2',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.paging, {
|
||||
page: 2,
|
||||
total: 2,
|
||||
pagesize: 2
|
||||
}, 'Paging data fail for ' + res.config.url);
|
||||
assert.equal(res.data.results.length, 2, "Should be 2 results.");
|
||||
|
||||
//Test query name
|
||||
var res = await req({
|
||||
url: '/records?queryName=foo&sort=id-asc',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [{
|
||||
id: 3,
|
||||
name: 'foo.example.com',
|
||||
type: 'AAAA',
|
||||
content: '::1',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'foo.de',
|
||||
type: 'A',
|
||||
content: '9.8.7.6',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 3
|
||||
},
|
||||
], 'Result fail for ' + res.config.url);
|
||||
|
||||
//Type filter
|
||||
var res = await req({
|
||||
url: '/records?type=TXT,AAAA',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [{
|
||||
id: 2,
|
||||
name: 'sdfdf.example.com',
|
||||
type: 'TXT',
|
||||
content: 'foo bar baz',
|
||||
priority: 10,
|
||||
ttl: 60,
|
||||
domain: 1
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'foo.example.com',
|
||||
type: 'AAAA',
|
||||
content: '::1',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}], 'Result fail for ' + res.config.url);
|
||||
|
||||
//Test query content
|
||||
var res = await req({
|
||||
url: '/records?queryContent=6&sort=id-asc',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test.example.com',
|
||||
type: 'A',
|
||||
content: '12.34.56.78',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'foo.de',
|
||||
type: 'A',
|
||||
content: '9.8.7.6',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 3
|
||||
}
|
||||
], 'Result fail for ' + res.config.url);
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
//Type filter
|
||||
var res = await req({
|
||||
url: '/records',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200, 'Status should be OK for user');
|
||||
assert.equal(res.data.results, [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test.example.com',
|
||||
type: 'A',
|
||||
content: '12.34.56.78',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'sdfdf.example.com',
|
||||
type: 'TXT',
|
||||
content: 'foo bar baz',
|
||||
priority: 10,
|
||||
ttl: 60,
|
||||
domain: 1
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'foo.example.com',
|
||||
type: 'AAAA',
|
||||
content: '::1',
|
||||
priority: 0,
|
||||
ttl: 86400,
|
||||
domain: 1
|
||||
}
|
||||
], 'Result fail for user on ' + res.config.url);
|
||||
});
|
||||
});
|
102
backend/test/tests/remote-changekey.js
Normal file
102
backend/test/tests/remote-changekey.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
const NodeRSA = require('node-rsa');
|
||||
|
||||
const privkey =
|
||||
`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQCrJ/UoQoN5rO1nwrWBNDr3TgPBkm6UmN/B6NY7RXcYTJOFEP6i
|
||||
WqTj9Pw8aT8/DSn2uTMeQK6kWNUAWmRaylQI2QHQdPtrI6piTpjvKm+KbR+n3e4Q
|
||||
J/zOcg06cHYJJiyhPjfC12j3ZxINOV3LDbEKq4s0HxMGYZHPu+UezapeeQIDAQAB
|
||||
AoGAGGkbgwFxhPIP7gOMJYBQhKMA0CPVV6YyC5LsswlmQfXx+EGDP56T89sl+mu8
|
||||
VH7JJGInk0IAZnow7tr1gylmMJ0ir6KfDKZQG95tkFHwCVM3ZqUx/X8VAVuZT2mo
|
||||
6ckAC7/ZrqORiFCNDC1kWgiaNj7GldvcbNOGUIBOkStgM4ECQQDVLWI/hO0fiPhT
|
||||
QWVu+4md1NjSv9MZdaIdm+FEVKyTjN/j1fDLNFIguC24veYvsgKf2AyYAJqiAihz
|
||||
RQWey38RAkEAzYmjjZuKmtsaUknZxmYVJwZlatvHv/3V2REa3UwhVXhgpbBGahav
|
||||
khH8W5u4JJ/VUpX34wje8g/Gp2M6aCg46QJAGtux8jDMM1ntd4fYwMfeSc1kWAEl
|
||||
FqMUfsiB9Dr610g7eRgeU2vPISIzWIBMfRvfasYsqAYDdX/yGrvKfnxDEQJAcTUr
|
||||
aXbPfAXMVKCqm3Vkly8VsyrEtcHZBItAUb156rq3+OrDjfFa2MihR8/YOAv1ElzZ
|
||||
wSoEqiz4TQABjpcA6QJAX1QXYhHQpjLj4UF+8TkZg93Zmd86W5CN/gXSTFJGrZ8M
|
||||
3DOyePDIw1omSzyfvYa3Rbl/NL5BxFH6cURg++z8FA==
|
||||
-----END RSA PRIVATE KEY-----`;
|
||||
const key = new NodeRSA(privkey, 'pkcs1', { signingScheme: 'pkcs1-sha512' });
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
// Test updating
|
||||
var time = Math.floor(new Date() / 1000);
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbaz' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Update should succeed');
|
||||
|
||||
var res = await req({
|
||||
url: '/records/1',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.data.content, 'foobarbaz', 'Updating should change content.');
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403);
|
||||
|
||||
// Test not existing record
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 100,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Not existing record should trigger error');
|
||||
|
||||
// Test missing fields
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 100,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Missing field should fail');
|
||||
|
||||
// Test wrong time
|
||||
var time = Math.floor(new Date() / 1000) - 60;
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbaz' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Wrong time should fail');
|
||||
});
|
||||
});
|
36
backend/test/tests/remote-changepw.js
Normal file
36
backend/test/tests/remote-changepw.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
// Test updating
|
||||
var res = await req({
|
||||
url: '/remote/updatepw?record=1&content=foobarbaz&password=test',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204);
|
||||
|
||||
var res = await req({
|
||||
url: '/records/1',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.data.content, 'foobarbaz', 'Updating should change content.');
|
||||
|
||||
// Test updating with invalid password
|
||||
var res = await req({
|
||||
url: '/remote/updatepw?record=1&content=foobarbaz&password=foo',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403);
|
||||
|
||||
// Test updating non existing record
|
||||
var res = await req({
|
||||
url: '/remote/updatepw?record=100&content=foobarbaz&password=foo',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404);
|
||||
});
|
||||
});
|
46
backend/test/tests/remote-ip.js
Normal file
46
backend/test/tests/remote-ip.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
var res = await req({
|
||||
url: '/remote/ip',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.data, { ip: '127.0.0.1' }, 'No proxy header should return tcp client ip.');
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/ip',
|
||||
method: 'get',
|
||||
headers: {
|
||||
'X-Forwarded-For': '1.2.3.4, 127.0.0.1'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.data, { ip: '1.2.3.4' }, 'X-Forwarded-For Test 1');
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/ip',
|
||||
method: 'get',
|
||||
headers: {
|
||||
'X-Forwarded-For': '4.3.2.1, 1.2.3.4, 127.0.0.1'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.data, { ip: '1.2.3.4' }, 'X-Forwarded-For Test 2');
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/ip',
|
||||
method: 'get',
|
||||
headers: {
|
||||
'X-Forwarded-For': '4.3.2.1, 1.2.3.4'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.data, { ip: '1.2.3.4' }, 'X-Forwarded-For Test 3');
|
||||
});
|
||||
});
|
15
backend/test/tests/remote-servertime.js
Normal file
15
backend/test/tests/remote-servertime.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
var res = await req({
|
||||
url: '/remote/servertime',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
const curTime = Math.floor(new Date() / 1000);
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.true(Math.abs(curTime - res.data.time) < 2, 'Returned time is not within tolerance!');
|
||||
});
|
||||
});
|
76
backend/test/tests/session.js
Normal file
76
backend/test/tests/session.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Try to login with invalid username and password
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'foo',
|
||||
password: 'bar'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Status not valid');
|
||||
|
||||
//Try to login with invalid username
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'foo',
|
||||
password: 'admin'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Status not valid');
|
||||
|
||||
//Try to login with invalid password
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Status not valid');
|
||||
|
||||
//Try to login with missing field
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
password: 'admin'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Status not valid');
|
||||
|
||||
//Try to login with prefix
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'config/configuser',
|
||||
password: 'configuser'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Status not valid');
|
||||
|
||||
//Try to login with prefix but no db entry
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'config/notindb',
|
||||
password: 'notindb'
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 201, 'Status not valid');
|
||||
});
|
||||
});
|
231
backend/test/tests/users-crud.js
Normal file
231
backend/test/tests/users-crud.js
Normal file
|
@ -0,0 +1,231 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
//Test missing fields
|
||||
var res = await req({
|
||||
url: '/users',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'newadmin',
|
||||
type: 'admin'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 422, 'Missing fields should trigger error.');
|
||||
|
||||
//Test invalid type
|
||||
var res = await req({
|
||||
url: '/users',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'newadmin',
|
||||
type: 'foo',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 400, 'Invalid type should trigger error.');
|
||||
|
||||
//Test duplicate user
|
||||
var res = await req({
|
||||
url: '/users',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'admin',
|
||||
type: 'admin',
|
||||
password: 'foo'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 409, 'Duplicate user should trigger error.');
|
||||
|
||||
//Test user creation
|
||||
var res = await req({
|
||||
url: '/users',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: 'newadmin',
|
||||
type: 'admin',
|
||||
password: 'newadmin'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'User creation should succeed.');
|
||||
assert.equal(res.data, { id: 4, name: 'newadmin', type: 'admin' }, 'Add user data fail.');
|
||||
|
||||
//Test if new user can log in
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'newadmin',
|
||||
password: 'newadmin'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'Login with new user should succeed.');
|
||||
|
||||
//Test user get
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 200, 'New user should be found.');
|
||||
assert.equal(res.data, { id: 4, name: 'newadmin', type: 'admin', native: true }, 'New user data fail.');
|
||||
|
||||
//Test user change without data
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'put',
|
||||
data: { dummy: 'foo' }
|
||||
});
|
||||
assert.equal(res.status, 204, 'Update without field should succeed.');
|
||||
|
||||
//Test user get
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 200, 'New user should be found after update.');
|
||||
assert.equal(res.data, { id: 4, name: 'newadmin', type: 'admin', native: true }, 'New user should not change by noop update.');
|
||||
|
||||
//Test user update
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'foo1',
|
||||
password: 'bar',
|
||||
type: 'user'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 204, 'Update should succeed.');
|
||||
|
||||
//Test if updated user can log in
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'foo1',
|
||||
password: 'bar'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'Login with updated user should succeed.');
|
||||
|
||||
//Test user update without password
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'foo',
|
||||
type: 'user'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 204, 'Update should succeed.');
|
||||
|
||||
//Test if updated user can log in
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'foo',
|
||||
password: 'bar'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'Login with updated user should succeed.');
|
||||
|
||||
//Test user get
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 200, 'New user should be found after second update.');
|
||||
assert.equal(res.data, { id: 4, name: 'foo', type: 'user', native: true }, 'New user should change by update.');
|
||||
|
||||
//Test user update conflict
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'put',
|
||||
data: {
|
||||
name: 'admin'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 409, 'Update with existent name should fail.');
|
||||
|
||||
//Test user delete for not existing user
|
||||
var res = await req({
|
||||
url: '/users/100',
|
||||
method: 'delete'
|
||||
});
|
||||
assert.equal(res.status, 404, 'Deletion of not existens user should fail.');
|
||||
|
||||
//Test user delete
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'delete'
|
||||
});
|
||||
assert.equal(res.status, 204, 'Deletion of user should succeed.');
|
||||
|
||||
var res = await req({
|
||||
url: '/users/4',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 404, 'New user should not be found after deletion.');
|
||||
|
||||
// Test me alias get
|
||||
var res = await req({
|
||||
url: '/users/me',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 200, 'Admin should be able to use /me.');
|
||||
assert.equal(res.data, { id: 1, name: 'admin', type: 'admin', native: true }, 'Admin /me data fail.');
|
||||
|
||||
// Test me alias update
|
||||
var res = await req({
|
||||
url: '/users/me',
|
||||
method: 'put',
|
||||
data: {
|
||||
password: 'abc'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 204, 'Admin should be able to update /me.');
|
||||
|
||||
//Test if updated user can log in
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'abc'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'Login with updated admin should succeed.');
|
||||
});
|
||||
|
||||
await test('user', async function (assert, req) {
|
||||
// Test me alias get
|
||||
var res = await req({
|
||||
url: '/users/me',
|
||||
method: 'get'
|
||||
});
|
||||
assert.equal(res.status, 200, 'User should be able to use /me.');
|
||||
assert.equal(res.data, { id: 2, name: 'user', type: 'user', native: true }, 'User /me data fail.');
|
||||
|
||||
// Test me alias update
|
||||
var res = await req({
|
||||
url: '/users/me',
|
||||
method: 'put',
|
||||
data: {
|
||||
password: 'abc'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 204, 'User should be able to update /me.');
|
||||
|
||||
//Test if updated user can log in
|
||||
var res = await req({
|
||||
url: '/sessions',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'user',
|
||||
password: 'abc'
|
||||
}
|
||||
});
|
||||
assert.equal(res.status, 201, 'Login with updated user should succeed.');
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue