mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2024-11-09 15:10:27 +00:00
Merge branch 'release/0.4.1'
This commit is contained in:
commit
a8895ffe7a
2
.github/workflows/mega-linter.yml
vendored
2
.github/workflows/mega-linter.yml
vendored
@ -4,8 +4,10 @@
|
|||||||
name: MegaLinter
|
name: MegaLinter
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
|
- "*"
|
||||||
- "dev"
|
- "dev"
|
||||||
- "main"
|
- "main"
|
||||||
- "master"
|
- "master"
|
||||||
|
@ -27,6 +27,7 @@ CAPTCHA_SESSION_KEY = 'captcha_image'
|
|||||||
SESSION_TYPE = 'sqlalchemy'
|
SESSION_TYPE = 'sqlalchemy'
|
||||||
|
|
||||||
### DATABASE - MySQL
|
### DATABASE - MySQL
|
||||||
|
## Don't forget to uncomment the import in the top
|
||||||
#SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format(
|
#SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format(
|
||||||
# urllib.parse.quote_plus(SQLA_DB_USER),
|
# urllib.parse.quote_plus(SQLA_DB_USER),
|
||||||
# urllib.parse.quote_plus(SQLA_DB_PASSWORD),
|
# urllib.parse.quote_plus(SQLA_DB_PASSWORD),
|
||||||
@ -34,6 +35,15 @@ SESSION_TYPE = 'sqlalchemy'
|
|||||||
# SQLA_DB_NAME
|
# SQLA_DB_NAME
|
||||||
#)
|
#)
|
||||||
|
|
||||||
|
### DATABASE - PostgreSQL
|
||||||
|
## Don't forget to uncomment the import in the top
|
||||||
|
#SQLALCHEMY_DATABASE_URI = 'postgres://{}:{}@{}/{}'.format(
|
||||||
|
# urllib.parse.quote_plus(SQLA_DB_USER),
|
||||||
|
# urllib.parse.quote_plus(SQLA_DB_PASSWORD),
|
||||||
|
# SQLA_DB_HOST,
|
||||||
|
# SQLA_DB_NAME
|
||||||
|
#)
|
||||||
|
|
||||||
### DATABASE - SQLite
|
### DATABASE - SQLite
|
||||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
# import everything from environment variables
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
# Defaults for Docker image
|
# Defaults for Docker image
|
||||||
BIND_ADDRESS = '0.0.0.0'
|
BIND_ADDRESS = '0.0.0.0'
|
||||||
PORT = 80
|
PORT = 80
|
||||||
@ -23,6 +28,7 @@ legal_envvars = (
|
|||||||
'OIDC_OAUTH_EMAIL',
|
'OIDC_OAUTH_EMAIL',
|
||||||
'BIND_ADDRESS',
|
'BIND_ADDRESS',
|
||||||
'PORT',
|
'PORT',
|
||||||
|
'SERVER_EXTERNAL_SSL',
|
||||||
'LOG_LEVEL',
|
'LOG_LEVEL',
|
||||||
'SALT',
|
'SALT',
|
||||||
'SQLALCHEMY_TRACK_MODIFICATIONS',
|
'SQLALCHEMY_TRACK_MODIFICATIONS',
|
||||||
@ -97,20 +103,17 @@ legal_envvars_bool = (
|
|||||||
'SESSION_COOKIE_SECURE',
|
'SESSION_COOKIE_SECURE',
|
||||||
'CSRF_COOKIE_SECURE',
|
'CSRF_COOKIE_SECURE',
|
||||||
'CAPTCHA_ENABLE',
|
'CAPTCHA_ENABLE',
|
||||||
|
'SERVER_EXTERNAL_SSL',
|
||||||
)
|
)
|
||||||
|
|
||||||
legal_envvars_dict = (
|
legal_envvars_dict = (
|
||||||
'SQLALCHEMY_ENGINE_OPTIONS',
|
'SQLALCHEMY_ENGINE_OPTIONS',
|
||||||
)
|
)
|
||||||
|
|
||||||
# import everything from environment variables
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
|
|
||||||
def str2bool(v):
|
def str2bool(v):
|
||||||
return v.lower() in ("true", "yes", "1")
|
return v.lower() in ("true", "yes", "1")
|
||||||
|
|
||||||
|
|
||||||
def dictfromstr(v, ret):
|
def dictfromstr(v, ret):
|
||||||
try:
|
try:
|
||||||
return json.loads(ret)
|
return json.loads(ret)
|
||||||
@ -119,10 +122,11 @@ def dictfromstr(v,ret):
|
|||||||
print(e)
|
print(e)
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
for v in legal_envvars:
|
for v in legal_envvars:
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
# _FILE suffix will allow to read value from file, usefull for Docker's
|
# _FILE suffix will allow to read value from file, useful for Docker containers.
|
||||||
# secrets feature
|
# secrets feature
|
||||||
if v + '_FILE' in os.environ:
|
if v + '_FILE' in os.environ:
|
||||||
if v in os.environ:
|
if v in os.environ:
|
||||||
|
@ -11,6 +11,7 @@ RUN apt-get update -y \
|
|||||||
libffi-dev \
|
libffi-dev \
|
||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
|
libpq-dev \
|
||||||
libsasl2-dev \
|
libsasl2-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
|
@ -2,6 +2,7 @@ FROM alpine:3.17 AS builder
|
|||||||
|
|
||||||
ARG BUILD_DEPENDENCIES="build-base \
|
ARG BUILD_DEPENDENCIES="build-base \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
|
libpq-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
mariadb-connector-c-dev \
|
mariadb-connector-c-dev \
|
||||||
openldap-dev \
|
openldap-dev \
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
## Using PowerDNS-Admin
|
## Using PowerDNS-Admin
|
||||||
|
|
||||||
- Setting up a domain
|
- Setting up a zone
|
||||||
- Adding a record
|
- Adding a record
|
||||||
- <whatever else>
|
- <whatever else>
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ Active Directory Setup - Tested with Windows Server 2012
|
|||||||
5) Fill in the required info -
|
5) Fill in the required info -
|
||||||
|
|
||||||
* LDAP URI - ldap://ip.of.your.domain.controller:389
|
* LDAP URI - ldap://ip.of.your.domain.controller:389
|
||||||
* LDAP Base DN - dc=youdomain,dc=com
|
* LDAP Base DN - dc=yourdomain,dc=com
|
||||||
* Active Directory domain - yourdomain.com
|
* Active Directory domain - yourdomain.com
|
||||||
* Basic filter - (objectCategory=person)
|
* Basic filter - (objectCategory=person)
|
||||||
* the brackets here are **very important**
|
* the brackets here are **very important**
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Supported environment variables
|
# Supported environment variables
|
||||||
|
|
||||||
| Variable | Description | Required | Default value |
|
| Variable | Description | Required | Default value |
|
||||||
| ---------| ----------- | -------- | ------------- |
|
|--------------------------------|--------------------------------------------------------------------------|------------|---------------|
|
||||||
| BIND_ADDRESS |
|
| BIND_ADDRESS |
|
||||||
| CSRF_COOKIE_SECURE |
|
| CSRF_COOKIE_SECURE |
|
||||||
| SESSION_TYPE | null | filesystem | sqlalchemy | | filesystem |
|
| SESSION_TYPE | null | filesystem | sqlalchemy | | filesystem |
|
||||||
@ -22,6 +22,7 @@
|
|||||||
| OIDC_OAUTH_TOKEN_URL | | | |
|
| OIDC_OAUTH_TOKEN_URL | | | |
|
||||||
| OIDC_OAUTH_METADATA_URL | | | |
|
| OIDC_OAUTH_METADATA_URL | | | |
|
||||||
| PORT |
|
| PORT |
|
||||||
|
| SERVER_EXTERNAL_SSL | Forceful override of URL schema detection when using the url_for method. | False | None |
|
||||||
| REMOTE_USER_COOKIES |
|
| REMOTE_USER_COOKIES |
|
||||||
| REMOTE_USER_LOGOUT_URL |
|
| REMOTE_USER_LOGOUT_URL |
|
||||||
| SALT |
|
| SALT |
|
||||||
|
17
docs/wiki/configuration/basic_settings.md
Normal file
17
docs/wiki/configuration/basic_settings.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
### PowerDNSAdmin basic settings
|
||||||
|
|
||||||
|
PowerDNSAdmin has many features and settings available to be turned either off or on.
|
||||||
|
In this docs those settings will be explain.
|
||||||
|
To find the settings in the the dashboard go to settings>basic.
|
||||||
|
|
||||||
|
allow_user_create_domain: This setting is used to allow users with the `user` role to create a domain, not possible by
|
||||||
|
default.
|
||||||
|
|
||||||
|
allow_user_remove_domain: Same as `allow_user_create_domain` but for removing a domain.
|
||||||
|
|
||||||
|
allow_user_view_history: Allow a user with the role `user` to view and access the history.
|
||||||
|
|
||||||
|
custom_history_header: This is a string type variable, when inputting an header name, if exists in the request it will
|
||||||
|
be in the created_by column in the history, if empty or not mentioned will default to the api_key description.
|
||||||
|
|
||||||
|
site_name: This will be the site name.
|
@ -15,10 +15,9 @@ The below will create a database called powerdnsadmindb and a user of powerdnsad
|
|||||||
```
|
```
|
||||||
$ sudo su - postgres
|
$ sudo su - postgres
|
||||||
$ createuser powerdnsadmin
|
$ createuser powerdnsadmin
|
||||||
$ createdb powerdnsadmindb
|
$ createdb -E UTF8 -l en_US.UTF-8 -O powerdnsadmin -T template0 powerdnsadmindb 'The database for PowerDNS-Admin'
|
||||||
$ psql
|
$ psql
|
||||||
postgres=# alter user powerdnsadmin with encrypted password 'powerdnsadmin';
|
postgres=# ALTER ROLE powerdnsadmin WITH PASSWORD 'powerdnsadmin_password';
|
||||||
postgres=# grant all privileges on database powerdnsadmindb to powerdnsadmin;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -51,18 +50,14 @@ On debian based systems these files are located in:
|
|||||||
|
|
||||||
## Install required packages:
|
## Install required packages:
|
||||||
### Red-hat based systems:
|
### Red-hat based systems:
|
||||||
|
TODO: confirm this is correct
|
||||||
```
|
```
|
||||||
sudo yum install postgresql-libs
|
sudo yum install postgresql-libs
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debian based systems:
|
### Debian based systems:
|
||||||
```
|
```
|
||||||
apt install libpq-dev python-dev
|
apt install python3-psycopg2
|
||||||
```
|
|
||||||
|
|
||||||
### Install python packages:
|
|
||||||
```
|
|
||||||
pip3 install psycopg2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Known Issues:
|
## Known Issues:
|
||||||
|
@ -7,19 +7,30 @@ First setup your database accordingly:
|
|||||||
|
|
||||||
### Install required packages for building python libraries from requirements.txt file
|
### Install required packages for building python libraries from requirements.txt file
|
||||||
|
|
||||||
|
For Debian 11 (bullseye) and above:
|
||||||
```bash
|
```bash
|
||||||
sudo apt install -y python3-dev git libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev pkg-config apt-transport-https virtualenv build-essential curl
|
sudo apt install -y python3-dev git libsasl2-dev libldap2-dev python3-venv libmariadb-dev pkg-config build-essential curl libpq-dev
|
||||||
|
```
|
||||||
|
Older systems might also need the following:
|
||||||
|
```bash
|
||||||
|
sudo apt install -y libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev apt-transport-https virtualenv
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install NodeJs
|
### Install NodeJs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
|
||||||
apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install yarn to build asset files
|
### Install yarn to build asset files
|
||||||
|
For Debian 11 (bullseye) and above:
|
||||||
|
```bash
|
||||||
|
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
sudo apt update && sudo apt install -y yarn
|
||||||
|
```
|
||||||
|
For older Debian systems:
|
||||||
```bash
|
```bash
|
||||||
sudo curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
sudo curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
@ -23,7 +23,7 @@ def upgrade():
|
|||||||
with op.batch_alter_table('user') as batch_op:
|
with op.batch_alter_table('user') as batch_op:
|
||||||
user = sa.sql.table('user', sa.sql.column('confirmed'))
|
user = sa.sql.table('user', sa.sql.column('confirmed'))
|
||||||
batch_op.execute(user.update().values(confirmed=False))
|
batch_op.execute(user.update().values(confirmed=False))
|
||||||
batch_op.alter_column('confirmed', nullable=False)
|
batch_op.alter_column('confirmed', nullable=False, existing_type=sa.Boolean(), existing_nullable=True, existing_server_default=False)
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
"jquery-sparkline": "^2.4.0",
|
"jquery-sparkline": "^2.4.0",
|
||||||
"jquery-ui-dist": "^1.13.2",
|
"jquery-ui-dist": "^1.13.2",
|
||||||
"jquery.quicksearch": "^2.4.0",
|
"jquery.quicksearch": "^2.4.0",
|
||||||
|
"jquery-validation": "^1.19.5",
|
||||||
"jtimeout": "^3.2.0",
|
"jtimeout": "^3.2.0",
|
||||||
|
"knockout": "^3.5.1",
|
||||||
"multiselect": "^0.9.12"
|
"multiselect": "^0.9.12"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@ -20,6 +20,7 @@ js_login = Bundle(
|
|||||||
'node_modules/jquery/dist/jquery.js',
|
'node_modules/jquery/dist/jquery.js',
|
||||||
'node_modules/bootstrap/dist/js/bootstrap.js',
|
'node_modules/bootstrap/dist/js/bootstrap.js',
|
||||||
'node_modules/icheck/icheck.js',
|
'node_modules/icheck/icheck.js',
|
||||||
|
'node_modules/knockout/build/output/knockout-latest.js',
|
||||||
'custom/js/custom.js',
|
'custom/js/custom.js',
|
||||||
filters=(ConcatFilter, 'rjsmin'),
|
filters=(ConcatFilter, 'rjsmin'),
|
||||||
output='generated/login.js')
|
output='generated/login.js')
|
||||||
@ -47,6 +48,7 @@ js_main = Bundle(
|
|||||||
'node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js',
|
'node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js',
|
||||||
'node_modules/jquery-sparkline/jquery.sparkline.js',
|
'node_modules/jquery-sparkline/jquery.sparkline.js',
|
||||||
'node_modules/jquery-slimscroll/jquery.slimscroll.js',
|
'node_modules/jquery-slimscroll/jquery.slimscroll.js',
|
||||||
|
'node_modules/jquery-validation/dist/jquery.validate.js',
|
||||||
'node_modules/icheck/icheck.js',
|
'node_modules/icheck/icheck.js',
|
||||||
'node_modules/fastclick/lib/fastclick.js',
|
'node_modules/fastclick/lib/fastclick.js',
|
||||||
'node_modules/moment/moment.js',
|
'node_modules/moment/moment.js',
|
||||||
@ -55,6 +57,8 @@ js_main = Bundle(
|
|||||||
'node_modules/datatables.net-plugins/sorting/natural.js',
|
'node_modules/datatables.net-plugins/sorting/natural.js',
|
||||||
'node_modules/jtimeout/src/jTimeout.js',
|
'node_modules/jtimeout/src/jTimeout.js',
|
||||||
'node_modules/jquery.quicksearch/src/jquery.quicksearch.js',
|
'node_modules/jquery.quicksearch/src/jquery.quicksearch.js',
|
||||||
|
'node_modules/knockout/build/output/knockout-latest.js',
|
||||||
|
'custom/js/app-authentication-settings-editor.js',
|
||||||
'custom/js/custom.js',
|
'custom/js/custom.js',
|
||||||
'node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
|
'node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
|
||||||
filters=(ConcatFilter, 'rjsmin'),
|
filters=(ConcatFilter, 'rjsmin'),
|
||||||
|
@ -133,13 +133,24 @@ def api_basic_auth(f):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
auth_header = request.headers.get('Authorization')
|
auth_header = request.headers.get('Authorization')
|
||||||
if auth_header:
|
|
||||||
auth_header = auth_header.replace('Basic ', '', 1)
|
if not auth_header:
|
||||||
|
current_app.logger.error('Error: Authorization header missing!')
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
if auth_header[:6] != "Basic ":
|
||||||
|
current_app.logger.error('Error: Unsupported authorization mechanism!')
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
# Remove "Basic " from the header value
|
||||||
|
auth_header = auth_header[6:]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
auth_header = str(base64.b64decode(auth_header), 'utf-8')
|
auth_header = str(base64.b64decode(auth_header), 'utf-8')
|
||||||
username, password = auth_header.split(":")
|
# NK: We use auth_components here as we don't know if we'll have a :, we split it maximum 1 times to grab the
|
||||||
except binascii.Error as e:
|
# username, the rest of the string would be the password.
|
||||||
|
auth_components = auth_header.split(':', maxsplit=1)
|
||||||
|
except (binascii.Error, UnicodeDecodeError) as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Invalid base64-encoded of credential. Error {0}'.format(
|
'Invalid base64-encoded of credential. Error {0}'.format(
|
||||||
e))
|
e))
|
||||||
@ -148,6 +159,12 @@ def api_basic_auth(f):
|
|||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
abort(401)
|
abort(401)
|
||||||
|
|
||||||
|
# If we don't have two auth components (username, password), we can abort
|
||||||
|
if len(auth_components) != 2:
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
(username, password) = auth_components
|
||||||
|
|
||||||
user = User(username=username,
|
user = User(username=username,
|
||||||
password=password,
|
password=password,
|
||||||
plain_text_password=password)
|
plain_text_password=password)
|
||||||
@ -161,8 +178,7 @@ def api_basic_auth(f):
|
|||||||
|
|
||||||
auth_method = request.args.get('auth_method', 'LOCAL')
|
auth_method = request.args.get('auth_method', 'LOCAL')
|
||||||
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
|
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
|
||||||
auth = user.is_validate(method=auth_method,
|
auth = user.is_validate(method=auth_method, src_ip=request.remote_addr)
|
||||||
src_ip=request.remote_addr)
|
|
||||||
|
|
||||||
if not auth:
|
if not auth:
|
||||||
current_app.logger.error('Checking user password failed')
|
current_app.logger.error('Checking user password failed')
|
||||||
@ -173,9 +189,6 @@ def api_basic_auth(f):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
abort(401)
|
abort(401)
|
||||||
else:
|
|
||||||
current_app.logger.error('Error: Authorization header missing!')
|
|
||||||
abort(401)
|
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
@ -257,7 +270,7 @@ def api_can_create_domain(f):
|
|||||||
if current_user.role.name not in [
|
if current_user.role.name not in [
|
||||||
'Administrator', 'Operator'
|
'Administrator', 'Operator'
|
||||||
] and not Setting().get('allow_user_create_domain'):
|
] and not Setting().get('allow_user_create_domain'):
|
||||||
msg = "User {0} does not have enough privileges to create domain"
|
msg = "User {0} does not have enough privileges to create zone"
|
||||||
current_app.logger.error(msg.format(current_user.username))
|
current_app.logger.error(msg.format(current_user.username))
|
||||||
raise NotEnoughPrivileges()
|
raise NotEnoughPrivileges()
|
||||||
|
|
||||||
@ -286,7 +299,7 @@ def apikey_can_create_domain(f):
|
|||||||
if g.apikey.role.name not in [
|
if g.apikey.role.name not in [
|
||||||
'Administrator', 'Operator'
|
'Administrator', 'Operator'
|
||||||
] and not Setting().get('allow_user_create_domain'):
|
] and not Setting().get('allow_user_create_domain'):
|
||||||
msg = "ApiKey #{0} does not have enough privileges to create domain"
|
msg = "ApiKey #{0} does not have enough privileges to create zone"
|
||||||
current_app.logger.error(msg.format(g.apikey.id))
|
current_app.logger.error(msg.format(g.apikey.id))
|
||||||
raise NotEnoughPrivileges()
|
raise NotEnoughPrivileges()
|
||||||
|
|
||||||
@ -316,7 +329,7 @@ def apikey_can_remove_domain(http_methods=[]):
|
|||||||
g.apikey.role.name not in ['Administrator', 'Operator'] and
|
g.apikey.role.name not in ['Administrator', 'Operator'] and
|
||||||
not Setting().get('allow_user_remove_domain')
|
not Setting().get('allow_user_remove_domain')
|
||||||
):
|
):
|
||||||
msg = "ApiKey #{0} does not have enough privileges to remove domain"
|
msg = "ApiKey #{0} does not have enough privileges to remove zone"
|
||||||
current_app.logger.error(msg.format(g.apikey.id))
|
current_app.logger.error(msg.format(g.apikey.id))
|
||||||
raise NotEnoughPrivileges()
|
raise NotEnoughPrivileges()
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
@ -331,7 +344,7 @@ def apikey_is_admin(f):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if g.apikey.role.name != 'Administrator':
|
if g.apikey.role.name != 'Administrator':
|
||||||
msg = "Apikey {0} does not have enough privileges to create domain"
|
msg = "Apikey {0} does not have enough privileges to create zone"
|
||||||
current_app.logger.error(msg.format(g.apikey.id))
|
current_app.logger.error(msg.format(g.apikey.id))
|
||||||
raise NotEnoughPrivileges()
|
raise NotEnoughPrivileges()
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
@ -447,10 +460,8 @@ def apikey_auth(f):
|
|||||||
if auth_header:
|
if auth_header:
|
||||||
try:
|
try:
|
||||||
apikey_val = str(base64.b64decode(auth_header), 'utf-8')
|
apikey_val = str(base64.b64decode(auth_header), 'utf-8')
|
||||||
except binascii.Error as e:
|
except (binascii.Error, UnicodeDecodeError) as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error('Invalid base64-encoded X-API-KEY. Error {0}'.format(e))
|
||||||
'Invalid base64-encoded of credential. Error {0}'.format(
|
|
||||||
e))
|
|
||||||
abort(401)
|
abort(401)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
|
@ -8,6 +8,7 @@ SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
|
|||||||
BIND_ADDRESS = '0.0.0.0'
|
BIND_ADDRESS = '0.0.0.0'
|
||||||
PORT = 9191
|
PORT = 9191
|
||||||
HSTS_ENABLED = False
|
HSTS_ENABLED = False
|
||||||
|
SERVER_EXTERNAL_SSL = None
|
||||||
|
|
||||||
SESSION_TYPE = 'sqlalchemy'
|
SESSION_TYPE = 'sqlalchemy'
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
|
@ -21,7 +21,7 @@ class StructuredException(Exception):
|
|||||||
class DomainNotExists(StructuredException):
|
class DomainNotExists(StructuredException):
|
||||||
status_code = 404
|
status_code = 404
|
||||||
|
|
||||||
def __init__(self, name=None, message="Domain does not exist"):
|
def __init__(self, name=None, message="Zone does not exist"):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -30,7 +30,7 @@ class DomainNotExists(StructuredException):
|
|||||||
class DomainAlreadyExists(StructuredException):
|
class DomainAlreadyExists(StructuredException):
|
||||||
status_code = 409
|
status_code = 409
|
||||||
|
|
||||||
def __init__(self, name=None, message="Domain already exists"):
|
def __init__(self, name=None, message="Zone already exists"):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -39,7 +39,7 @@ class DomainAlreadyExists(StructuredException):
|
|||||||
class DomainAccessForbidden(StructuredException):
|
class DomainAccessForbidden(StructuredException):
|
||||||
status_code = 403
|
status_code = 403
|
||||||
|
|
||||||
def __init__(self, name=None, message="Domain access not allowed"):
|
def __init__(self, name=None, message="Zone access not allowed"):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -47,7 +47,7 @@ class DomainAccessForbidden(StructuredException):
|
|||||||
class DomainOverrideForbidden(StructuredException):
|
class DomainOverrideForbidden(StructuredException):
|
||||||
status_code = 409
|
status_code = 409
|
||||||
|
|
||||||
def __init__(self, name=None, message="Domain override of record not allowed"):
|
def __init__(self, name=None, message="Zone override of record not allowed"):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -67,7 +67,7 @@ class ApiKeyNotUsable(StructuredException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name=None,
|
name=None,
|
||||||
message=("Api key must have domains or accounts"
|
message=("Api key must have zones or accounts"
|
||||||
" or an administrative role")):
|
" or an administrative role")):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@ -229,7 +229,7 @@ def ensure_list(l):
|
|||||||
|
|
||||||
def pretty_domain_name(domain_name):
|
def pretty_domain_name(domain_name):
|
||||||
# Add a debugging statement to print out the domain name
|
# Add a debugging statement to print out the domain name
|
||||||
print("Received domain name:", domain_name)
|
print("Received zone name:", domain_name)
|
||||||
|
|
||||||
# Check if the domain name is encoded using Punycode
|
# Check if the domain name is encoded using Punycode
|
||||||
if domain_name.endswith('.xn--'):
|
if domain_name.endswith('.xn--'):
|
||||||
@ -238,9 +238,9 @@ def pretty_domain_name(domain_name):
|
|||||||
domain_name = idna.decode(domain_name)
|
domain_name = idna.decode(domain_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If the decoding fails, raise an exception with more information
|
# If the decoding fails, raise an exception with more information
|
||||||
raise Exception('Cannot decode IDN domain: {}'.format(e))
|
raise Exception('Cannot decode IDN zone: {}'.format(e))
|
||||||
|
|
||||||
# Return the "pretty" version of the domain name
|
# Return the "pretty" version of the zone name
|
||||||
return domain_name
|
return domain_name
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,13 +68,13 @@ class Domain(db.Model):
|
|||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Can not create setting {0} for domain {1}. {2}'.format(
|
'Can not create setting {0} for zone {1}. {2}'.format(
|
||||||
setting, self.name, e))
|
setting, self.name, e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_domain_info(self, domain_name):
|
def get_domain_info(self, domain_name):
|
||||||
"""
|
"""
|
||||||
Get all domains which has in PowerDNS
|
Get all zones which has in PowerDNS
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
jdata = utils.fetch_json(urljoin(
|
jdata = utils.fetch_json(urljoin(
|
||||||
@ -88,7 +88,7 @@ class Domain(db.Model):
|
|||||||
|
|
||||||
def get_domains(self):
|
def get_domains(self):
|
||||||
"""
|
"""
|
||||||
Get all domains which has in PowerDNS
|
Get all zones which has in PowerDNS
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
jdata = utils.fetch_json(
|
jdata = utils.fetch_json(
|
||||||
@ -108,17 +108,17 @@ class Domain(db.Model):
|
|||||||
return domain.id
|
return domain.id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Domain does not exist. ERROR: {0}'.format(e))
|
'Zone does not exist. ERROR: {0}'.format(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def search_idn_domains(self, search_string):
|
def search_idn_domains(self, search_string):
|
||||||
"""
|
"""
|
||||||
Search for IDN domains using the provided search string.
|
Search for IDN zones using the provided search string.
|
||||||
"""
|
"""
|
||||||
# Compile the regular expression pattern for matching IDN domain names
|
# Compile the regular expression pattern for matching IDN zone names
|
||||||
idn_pattern = re.compile(r'^xn--')
|
idn_pattern = re.compile(r'^xn--')
|
||||||
|
|
||||||
# Search for domain names that match the IDN pattern
|
# Search for zone names that match the IDN pattern
|
||||||
idn_domains = [
|
idn_domains = [
|
||||||
domain for domain in self.get_domains() if idn_pattern.match(domain)
|
domain for domain in self.get_domains() if idn_pattern.match(domain)
|
||||||
]
|
]
|
||||||
@ -129,12 +129,12 @@ class Domain(db.Model):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""
|
"""
|
||||||
Fetch zones (domains) from PowerDNS and update into DB
|
Fetch zones (zones) from PowerDNS and update into DB
|
||||||
"""
|
"""
|
||||||
db_domain = Domain.query.all()
|
db_domain = Domain.query.all()
|
||||||
list_db_domain = [d.name for d in db_domain]
|
list_db_domain = [d.name for d in db_domain]
|
||||||
dict_db_domain = dict((x.name, x) for x in db_domain)
|
dict_db_domain = dict((x.name, x) for x in db_domain)
|
||||||
current_app.logger.info("Found {} domains in PowerDNS-Admin".format(
|
current_app.logger.info("Found {} zones in PowerDNS-Admin".format(
|
||||||
len(list_db_domain)))
|
len(list_db_domain)))
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
try:
|
try:
|
||||||
@ -149,17 +149,17 @@ class Domain(db.Model):
|
|||||||
"Found {} zones in PowerDNS server".format(len(list_jdomain)))
|
"Found {} zones in PowerDNS server".format(len(list_jdomain)))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# domains should remove from db since it doesn't exist in powerdns anymore
|
# zones should remove from db since it doesn't exist in powerdns anymore
|
||||||
should_removed_db_domain = list(
|
should_removed_db_domain = list(
|
||||||
set(list_db_domain).difference(list_jdomain))
|
set(list_db_domain).difference(list_jdomain))
|
||||||
for domain_name in should_removed_db_domain:
|
for domain_name in should_removed_db_domain:
|
||||||
self.delete_domain_from_pdnsadmin(domain_name, do_commit=False)
|
self.delete_domain_from_pdnsadmin(domain_name, do_commit=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Can not delete domain from DB. DETAIL: {0}'.format(e))
|
'Can not delete zone from DB. DETAIL: {0}'.format(e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
# update/add new domain
|
# update/add new zone
|
||||||
account_cache = {}
|
account_cache = {}
|
||||||
for data in jdata:
|
for data in jdata:
|
||||||
if 'account' in data:
|
if 'account' in data:
|
||||||
@ -187,16 +187,16 @@ class Domain(db.Model):
|
|||||||
self.add_domain_to_powerdns_admin(domain=data, do_commit=False)
|
self.add_domain_to_powerdns_admin(domain=data, do_commit=False)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.info('Update domain finished')
|
current_app.logger.info('Update zone finished')
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
'msg': 'Domain table has been updated successfully'
|
'msg': 'Zone table has been updated successfully'
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot update domain table. Error: {0}'.format(e))
|
'Cannot update zone table. Error: {0}'.format(e))
|
||||||
return {'status': 'error', 'msg': 'Cannot update domain table'}
|
return {'status': 'error', 'msg': 'Cannot update zone table'}
|
||||||
|
|
||||||
def update_pdns_admin_domain(self, domain, account_id, data, do_commit=True):
|
def update_pdns_admin_domain(self, domain, account_id, data, do_commit=True):
|
||||||
# existing domain, only update if something actually has changed
|
# existing domain, only update if something actually has changed
|
||||||
@ -218,11 +218,11 @@ class Domain(db.Model):
|
|||||||
try:
|
try:
|
||||||
if do_commit:
|
if do_commit:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.info("Updated PDNS-Admin domain {0}".format(
|
current_app.logger.info("Updated PDNS-Admin zone {0}".format(
|
||||||
domain.name))
|
domain.name))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.info("Rolled back Domain {0} {1}".format(
|
current_app.logger.info("Rolled back zone {0} {1}".format(
|
||||||
domain.name, e))
|
domain.name, e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ class Domain(db.Model):
|
|||||||
domain_master_ips=[],
|
domain_master_ips=[],
|
||||||
account_name=None):
|
account_name=None):
|
||||||
"""
|
"""
|
||||||
Add a domain to power dns
|
Add a zone to power dns
|
||||||
"""
|
"""
|
||||||
|
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
||||||
@ -269,23 +269,23 @@ class Domain(db.Model):
|
|||||||
if 'error' in jdata.keys():
|
if 'error' in jdata.keys():
|
||||||
current_app.logger.error(jdata['error'])
|
current_app.logger.error(jdata['error'])
|
||||||
if jdata.get('http_code') == 409:
|
if jdata.get('http_code') == 409:
|
||||||
return {'status': 'error', 'msg': 'Domain already exists'}
|
return {'status': 'error', 'msg': 'Zone already exists'}
|
||||||
return {'status': 'error', 'msg': jdata['error']}
|
return {'status': 'error', 'msg': jdata['error']}
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'Added domain successfully to PowerDNS: {0}'.format(
|
'Added zone successfully to PowerDNS: {0}'.format(
|
||||||
domain_name))
|
domain_name))
|
||||||
self.add_domain_to_powerdns_admin(domain_dict=post_data)
|
self.add_domain_to_powerdns_admin(domain_dict=post_data)
|
||||||
return {'status': 'ok', 'msg': 'Added domain successfully'}
|
return {'status': 'ok', 'msg': 'Added zone successfully'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Cannot add domain {0} {1}'.format(
|
current_app.logger.error('Cannot add zone {0} {1}'.format(
|
||||||
domain_name, e))
|
domain_name, e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return {'status': 'error', 'msg': 'Cannot add this domain.'}
|
return {'status': 'error', 'msg': 'Cannot add this zone.'}
|
||||||
|
|
||||||
def add_domain_to_powerdns_admin(self, domain=None, domain_dict=None, do_commit=True):
|
def add_domain_to_powerdns_admin(self, domain=None, domain_dict=None, do_commit=True):
|
||||||
"""
|
"""
|
||||||
Read Domain from PowerDNS and add into PDNS-Admin
|
Read zone from PowerDNS and add into PDNS-Admin
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
if not domain:
|
if not domain:
|
||||||
@ -299,7 +299,7 @@ class Domain(db.Model):
|
|||||||
timeout=int(Setting().get('pdns_api_timeout')),
|
timeout=int(Setting().get('pdns_api_timeout')),
|
||||||
verify=Setting().get('verify_ssl_connections'))
|
verify=Setting().get('verify_ssl_connections'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Can not read domain from PDNS')
|
current_app.logger.error('Can not read zone from PDNS')
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
@ -325,20 +325,20 @@ class Domain(db.Model):
|
|||||||
if do_commit:
|
if do_commit:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"Synced PowerDNS Domain to PDNS-Admin: {0}".format(d.name))
|
"Synced PowerDNS zone to PDNS-Admin: {0}".format(d.name))
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
'msg': 'Added Domain successfully to PowerDNS-Admin'
|
'msg': 'Added zone successfully to PowerDNS-Admin'
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.info("Rolled back Domain {0}".format(d.name))
|
current_app.logger.info("Rolled back zone {0}".format(d.name))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def update_soa_setting(self, domain_name, soa_edit_api):
|
def update_soa_setting(self, domain_name, soa_edit_api):
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
if not domain:
|
if not domain:
|
||||||
return {'status': 'error', 'msg': 'Domain does not exist.'}
|
return {'status': 'error', 'msg': 'Zone does not exist.'}
|
||||||
|
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ class Domain(db.Model):
|
|||||||
return {'status': 'error', 'msg': jdata['error']}
|
return {'status': 'error', 'msg': jdata['error']}
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'soa-edit-api changed for domain {0} successfully'.format(
|
'soa-edit-api changed for zone {0} successfully'.format(
|
||||||
domain_name))
|
domain_name))
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
@ -375,11 +375,11 @@ class Domain(db.Model):
|
|||||||
current_app.logger.debug(e)
|
current_app.logger.debug(e)
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot change soa-edit-api for domain {0}'.format(
|
'Cannot change soa-edit-api for zone {0}'.format(
|
||||||
domain_name))
|
domain_name))
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Cannot change soa-edit-api for this domain.'
|
'msg': 'Cannot change soa-edit-api for this zone.'
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_kind(self, domain_name, kind, masters=[]):
|
def update_kind(self, domain_name, kind, masters=[]):
|
||||||
@ -388,7 +388,7 @@ class Domain(db.Model):
|
|||||||
"""
|
"""
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
if not domain:
|
if not domain:
|
||||||
return {'status': 'error', 'msg': 'Domain does not exist.'}
|
return {'status': 'error', 'msg': 'Znoe does not exist.'}
|
||||||
|
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
||||||
|
|
||||||
@ -409,26 +409,26 @@ class Domain(db.Model):
|
|||||||
return {'status': 'error', 'msg': jdata['error']}
|
return {'status': 'error', 'msg': jdata['error']}
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'Update domain kind for {0} successfully'.format(
|
'Update zone kind for {0} successfully'.format(
|
||||||
domain_name))
|
domain_name))
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
'msg': 'Domain kind changed successfully'
|
'msg': 'Zone kind changed successfully'
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot update kind for domain {0}. Error: {1}'.format(
|
'Cannot update kind for zone {0}. Error: {1}'.format(
|
||||||
domain_name, e))
|
domain_name, e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Cannot update kind for this domain.'
|
'msg': 'Cannot update kind for this zone.'
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_reverse_domain(self, domain_name, domain_reverse_name):
|
def create_reverse_domain(self, domain_name, domain_reverse_name):
|
||||||
"""
|
"""
|
||||||
Check the existing reverse lookup domain,
|
Check the existing reverse lookup zone,
|
||||||
if not exists create a new one automatically
|
if not exists create a new one automatically
|
||||||
"""
|
"""
|
||||||
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
|
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
@ -448,7 +448,7 @@ class Domain(db.Model):
|
|||||||
result = self.add(domain_reverse_name, 'Master', 'DEFAULT', [], [])
|
result = self.add(domain_reverse_name, 'Master', 'DEFAULT', [], [])
|
||||||
self.update()
|
self.update()
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
history = History(msg='Add reverse lookup domain {0}'.format(
|
history = History(msg='Add reverse lookup zone {0}'.format(
|
||||||
domain_reverse_name),
|
domain_reverse_name),
|
||||||
detail=json.dumps({
|
detail=json.dumps({
|
||||||
'domain_type': 'Master',
|
'domain_type': 'Master',
|
||||||
@ -459,7 +459,7 @@ class Domain(db.Model):
|
|||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Adding reverse lookup domain failed'
|
'msg': 'Adding reverse lookup zone failed'
|
||||||
}
|
}
|
||||||
domain_user_ids = self.get_user()
|
domain_user_ids = self.get_user()
|
||||||
if len(domain_user_ids) > 0:
|
if len(domain_user_ids) > 0:
|
||||||
@ -469,13 +469,13 @@ class Domain(db.Model):
|
|||||||
'status':
|
'status':
|
||||||
'ok',
|
'ok',
|
||||||
'msg':
|
'msg':
|
||||||
'New reverse lookup domain created with granted privileges'
|
'New reverse lookup zone created with granted privileges'
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
'msg': 'New reverse lookup domain created without users'
|
'msg': 'New reverse lookup zone created without users'
|
||||||
}
|
}
|
||||||
return {'status': 'ok', 'msg': 'Reverse lookup domain already exists'}
|
return {'status': 'ok', 'msg': 'Reverse lookup zone already exists'}
|
||||||
|
|
||||||
def get_reverse_domain_name(self, reverse_host_address):
|
def get_reverse_domain_name(self, reverse_host_address):
|
||||||
c = 1
|
c = 1
|
||||||
@ -504,22 +504,22 @@ class Domain(db.Model):
|
|||||||
|
|
||||||
def delete(self, domain_name):
|
def delete(self, domain_name):
|
||||||
"""
|
"""
|
||||||
Delete a single domain name from powerdns
|
Delete a single zone name from powerdns
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.delete_domain_from_powerdns(domain_name)
|
self.delete_domain_from_powerdns(domain_name)
|
||||||
self.delete_domain_from_pdnsadmin(domain_name)
|
self.delete_domain_from_pdnsadmin(domain_name)
|
||||||
return {'status': 'ok', 'msg': 'Delete domain successfully'}
|
return {'status': 'ok', 'msg': 'Delete zone successfully'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot delete domain {0}'.format(domain_name))
|
'Cannot delete zone {0}'.format(domain_name))
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return {'status': 'error', 'msg': 'Cannot delete domain'}
|
return {'status': 'error', 'msg': 'Cannot delete zone'}
|
||||||
|
|
||||||
def delete_domain_from_powerdns(self, domain_name):
|
def delete_domain_from_powerdns(self, domain_name):
|
||||||
"""
|
"""
|
||||||
Delete a single domain name from powerdns
|
Delete a single zone name from powerdns
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
|
|
||||||
@ -531,12 +531,12 @@ class Domain(db.Model):
|
|||||||
method='DELETE',
|
method='DELETE',
|
||||||
verify=Setting().get('verify_ssl_connections'))
|
verify=Setting().get('verify_ssl_connections'))
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'Deleted domain successfully from PowerDNS: {0}'.format(
|
'Deleted zone successfully from PowerDNS: {0}'.format(
|
||||||
domain_name))
|
domain_name))
|
||||||
return {'status': 'ok', 'msg': 'Delete domain successfully'}
|
return {'status': 'ok', 'msg': 'Delete zone successfully'}
|
||||||
|
|
||||||
def delete_domain_from_pdnsadmin(self, domain_name, do_commit=True):
|
def delete_domain_from_pdnsadmin(self, domain_name, do_commit=True):
|
||||||
# Revoke permission before deleting domain
|
# Revoke permission before deleting zone
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
domain_user = DomainUser.query.filter(
|
domain_user = DomainUser.query.filter(
|
||||||
DomainUser.domain_id == domain.id)
|
DomainUser.domain_id == domain.id)
|
||||||
@ -548,7 +548,7 @@ class Domain(db.Model):
|
|||||||
domain_setting.delete()
|
domain_setting.delete()
|
||||||
domain.apikeys[:] = []
|
domain.apikeys[:] = []
|
||||||
|
|
||||||
# Remove history for domain
|
# Remove history for zone
|
||||||
if not Setting().get('preserve_history'):
|
if not Setting().get('preserve_history'):
|
||||||
domain_history = History.query.filter(
|
domain_history = History.query.filter(
|
||||||
History.domain_id == domain.id
|
History.domain_id == domain.id
|
||||||
@ -556,17 +556,17 @@ class Domain(db.Model):
|
|||||||
if domain_history:
|
if domain_history:
|
||||||
domain_history.delete()
|
domain_history.delete()
|
||||||
|
|
||||||
# then remove domain
|
# then remove zone
|
||||||
Domain.query.filter(Domain.name == domain_name).delete()
|
Domain.query.filter(Domain.name == domain_name).delete()
|
||||||
if do_commit:
|
if do_commit:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"Deleted domain successfully from pdnsADMIN: {}".format(
|
"Deleted zone successfully from pdnsADMIN: {}".format(
|
||||||
domain_name))
|
domain_name))
|
||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
"""
|
"""
|
||||||
Get users (id) who have access to this domain name
|
Get users (id) who have access to this zone name
|
||||||
"""
|
"""
|
||||||
user_ids = []
|
user_ids = []
|
||||||
query = db.session.query(
|
query = db.session.query(
|
||||||
@ -596,7 +596,7 @@ class Domain(db.Model):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot revoke user privileges on domain {0}. DETAIL: {1}'.
|
'Cannot revoke user privileges on zone {0}. DETAIL: {1}'.
|
||||||
format(self.name, e))
|
format(self.name, e))
|
||||||
current_app.logger.debug(print(traceback.format_exc()))
|
current_app.logger.debug(print(traceback.format_exc()))
|
||||||
|
|
||||||
@ -608,7 +608,7 @@ class Domain(db.Model):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot grant user privileges to domain {0}. DETAIL: {1}'.
|
'Cannot grant user privileges to zone {0}. DETAIL: {1}'.
|
||||||
format(self.name, e))
|
format(self.name, e))
|
||||||
current_app.logger.debug(print(traceback.format_exc()))
|
current_app.logger.debug(print(traceback.format_exc()))
|
||||||
|
|
||||||
@ -625,7 +625,7 @@ class Domain(db.Model):
|
|||||||
|
|
||||||
def add_user(self, user):
|
def add_user(self, user):
|
||||||
"""
|
"""
|
||||||
Add a single user to Domain by User
|
Add a single user to zone by User
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
du = DomainUser(self.id, user.id)
|
du = DomainUser(self.id, user.id)
|
||||||
@ -635,7 +635,7 @@ class Domain(db.Model):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot add user privileges on domain {0}. DETAIL: {1}'.
|
'Cannot add user privileges on zone {0}. DETAIL: {1}'.
|
||||||
format(self.name, e))
|
format(self.name, e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -667,11 +667,11 @@ class Domain(db.Model):
|
|||||||
'There was something wrong, please contact administrator'
|
'There was something wrong, please contact administrator'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {'status': 'error', 'msg': 'This domain does not exist'}
|
return {'status': 'error', 'msg': 'This zone does not exist'}
|
||||||
|
|
||||||
def get_domain_dnssec(self, domain_name):
|
def get_domain_dnssec(self, domain_name):
|
||||||
"""
|
"""
|
||||||
Get domain DNSSEC information
|
Get zone DNSSEC information
|
||||||
"""
|
"""
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
if domain:
|
if domain:
|
||||||
@ -689,13 +689,13 @@ class Domain(db.Model):
|
|||||||
if 'error' in jdata:
|
if 'error' in jdata:
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'DNSSEC is not enabled for this domain'
|
'msg': 'DNSSEC is not enabled for this zone'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {'status': 'ok', 'dnssec': jdata}
|
return {'status': 'ok', 'dnssec': jdata}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot get domain dnssec. DETAIL: {0}'.format(e))
|
'Cannot get zone dnssec. DETAIL: {0}'.format(e))
|
||||||
return {
|
return {
|
||||||
'status':
|
'status':
|
||||||
'error',
|
'error',
|
||||||
@ -703,11 +703,11 @@ class Domain(db.Model):
|
|||||||
'There was something wrong, please contact administrator'
|
'There was something wrong, please contact administrator'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {'status': 'error', 'msg': 'This domain does not exist'}
|
return {'status': 'error', 'msg': 'This zone does not exist'}
|
||||||
|
|
||||||
def enable_domain_dnssec(self, domain_name):
|
def enable_domain_dnssec(self, domain_name):
|
||||||
"""
|
"""
|
||||||
Enable domain DNSSEC
|
Enable zone DNSSEC
|
||||||
"""
|
"""
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
if domain:
|
if domain:
|
||||||
@ -728,7 +728,7 @@ class Domain(db.Model):
|
|||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg':
|
'msg':
|
||||||
'API-RECTIFY could not be enabled for this domain',
|
'API-RECTIFY could not be enabled for this zone',
|
||||||
'jdata': jdata
|
'jdata': jdata
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,7 +749,7 @@ class Domain(db.Model):
|
|||||||
'status':
|
'status':
|
||||||
'error',
|
'error',
|
||||||
'msg':
|
'msg':
|
||||||
'Cannot enable DNSSEC for this domain. Error: {0}'.
|
'Cannot enable DNSSEC for this zone. Error: {0}'.
|
||||||
format(jdata['error']),
|
format(jdata['error']),
|
||||||
'jdata':
|
'jdata':
|
||||||
jdata
|
jdata
|
||||||
@ -769,7 +769,7 @@ class Domain(db.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return {'status': 'error', 'msg': 'This domain does not exist'}
|
return {'status': 'error', 'msg': 'This zone does not exist'}
|
||||||
|
|
||||||
def delete_dnssec_key(self, domain_name, key_id):
|
def delete_dnssec_key(self, domain_name, key_id):
|
||||||
"""
|
"""
|
||||||
@ -794,13 +794,13 @@ class Domain(db.Model):
|
|||||||
'status':
|
'status':
|
||||||
'error',
|
'error',
|
||||||
'msg':
|
'msg':
|
||||||
'Cannot disable DNSSEC for this domain. Error: {0}'.
|
'Cannot disable DNSSEC for this zone. Error: {0}'.
|
||||||
format(jdata['error']),
|
format(jdata['error']),
|
||||||
'jdata':
|
'jdata':
|
||||||
jdata
|
jdata
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disable API-RECTIFY for domain, AFTER deactivating DNSSEC
|
# Disable API-RECTIFY for zone, AFTER deactivating DNSSEC
|
||||||
post_data = {"api_rectify": False}
|
post_data = {"api_rectify": False}
|
||||||
jdata = utils.fetch_json(
|
jdata = utils.fetch_json(
|
||||||
urljoin(
|
urljoin(
|
||||||
@ -815,7 +815,7 @@ class Domain(db.Model):
|
|||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg':
|
'msg':
|
||||||
'API-RECTIFY could not be disabled for this domain',
|
'API-RECTIFY could not be disabled for this zone',
|
||||||
'jdata': jdata
|
'jdata': jdata
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,22 +834,22 @@ class Domain(db.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return {'status': 'error', 'msg': 'This domain does not exist'}
|
return {'status': 'error', 'msg': 'This zone does not exist'}
|
||||||
|
|
||||||
def assoc_account(self, account_id, update=True):
|
def assoc_account(self, account_id, update=True):
|
||||||
"""
|
"""
|
||||||
Associate domain with a domain, specified by account id
|
Associate account with a zone, specified by account id
|
||||||
"""
|
"""
|
||||||
domain_name = self.name
|
domain_name = self.name
|
||||||
|
|
||||||
# Sanity check - domain name
|
# Sanity check - domain name
|
||||||
if domain_name == "":
|
if domain_name == "":
|
||||||
return {'status': False, 'msg': 'No domain name specified'}
|
return {'status': False, 'msg': 'No zone name specified'}
|
||||||
|
|
||||||
# read domain and check that it exists
|
# read domain and check that it exists
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
if not domain:
|
if not domain:
|
||||||
return {'status': False, 'msg': 'Domain does not exist'}
|
return {'status': False, 'msg': 'Zone does not exist'}
|
||||||
|
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
||||||
|
|
||||||
@ -875,9 +875,9 @@ class Domain(db.Model):
|
|||||||
else:
|
else:
|
||||||
if update:
|
if update:
|
||||||
self.update()
|
self.update()
|
||||||
msg_str = 'Account changed for domain {0} successfully'
|
msg_str = 'Account changed for zone {0} successfully'
|
||||||
current_app.logger.info(msg_str.format(domain_name))
|
current_app.logger.info(msg_str.format(domain_name))
|
||||||
history = History(msg='Update domain {0} associate account {1}'.format(domain.name, 'none' if account_name == '' else account_name),
|
history = History(msg='Update zone {0} associate account {1}'.format(domain.name, 'none' if account_name == '' else account_name),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'assoc_account': 'None' if account_name == '' else account_name,
|
'assoc_account': 'None' if account_name == '' else account_name,
|
||||||
'dissoc_account': 'None' if account_name_old == '' else account_name_old
|
'dissoc_account': 'None' if account_name_old == '' else account_name_old
|
||||||
@ -889,16 +889,16 @@ class Domain(db.Model):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.debug(e)
|
current_app.logger.debug(e)
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
msg_str = 'Cannot change account for domain {0}'
|
msg_str = 'Cannot change account for zone {0}'
|
||||||
current_app.logger.error(msg_str.format(domain_name))
|
current_app.logger.error(msg_str.format(domain_name))
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Cannot change account for this domain.'
|
'msg': 'Cannot change account for this zone.'
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_account(self):
|
def get_account(self):
|
||||||
"""
|
"""
|
||||||
Get current account associated with this domain
|
Get current account associated with this zone
|
||||||
"""
|
"""
|
||||||
domain = Domain.query.filter(Domain.name == self.name).first()
|
domain = Domain.query.filter(Domain.name == self.name).first()
|
||||||
|
|
||||||
@ -907,7 +907,7 @@ class Domain(db.Model):
|
|||||||
def is_valid_access(self, user_id):
|
def is_valid_access(self, user_id):
|
||||||
"""
|
"""
|
||||||
Check if the user is allowed to access this
|
Check if the user is allowed to access this
|
||||||
domain name
|
zone name
|
||||||
"""
|
"""
|
||||||
return db.session.query(Domain) \
|
return db.session.query(Domain) \
|
||||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||||
@ -919,8 +919,8 @@ class Domain(db.Model):
|
|||||||
AccountUser.user_id == user_id
|
AccountUser.user_id == user_id
|
||||||
)).filter(Domain.id == self.id).first()
|
)).filter(Domain.id == self.id).first()
|
||||||
|
|
||||||
# Return None if this domain does not exist as record,
|
# Return None if this zone does not exist as record,
|
||||||
# Return the parent domain that hold the record if exist
|
# Return the parent zone that hold the record if exist
|
||||||
def is_overriding(self, domain_name):
|
def is_overriding(self, domain_name):
|
||||||
upper_domain_name = '.'.join(domain_name.split('.')[1:])
|
upper_domain_name = '.'.join(domain_name.split('.')[1:])
|
||||||
while upper_domain_name != '':
|
while upper_domain_name != '':
|
||||||
@ -929,7 +929,7 @@ class Domain(db.Model):
|
|||||||
if 'rrsets' in upper_domain:
|
if 'rrsets' in upper_domain:
|
||||||
for r in upper_domain['rrsets']:
|
for r in upper_domain['rrsets']:
|
||||||
if domain_name.rstrip('.') in r['name'].rstrip('.'):
|
if domain_name.rstrip('.') in r['name'].rstrip('.'):
|
||||||
current_app.logger.error('Domain already exists as a record: {} under domain: {}'.format(r['name'].rstrip('.'), upper_domain_name))
|
current_app.logger.error('Zone already exists as a record: {} under zone: {}'.format(r['name'].rstrip('.'), upper_domain_name))
|
||||||
return upper_domain_name
|
return upper_domain_name
|
||||||
upper_domain_name = '.'.join(upper_domain_name.split('.')[1:])
|
upper_domain_name = '.'.join(upper_domain_name.split('.')[1:])
|
||||||
return None
|
return None
|
||||||
|
@ -45,11 +45,11 @@ class DomainTemplate(db.Model):
|
|||||||
return {'status': 'ok', 'msg': 'Template has been created'}
|
return {'status': 'ok', 'msg': 'Template has been created'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Can not update domain template table. Error: {0}'.format(e))
|
'Can not update zone template table. Error: {0}'.format(e))
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Can not update domain template table'
|
'msg': 'Can not update zone template table'
|
||||||
}
|
}
|
||||||
|
|
||||||
def delete_template(self):
|
def delete_template(self):
|
||||||
@ -60,6 +60,6 @@ class DomainTemplate(db.Model):
|
|||||||
return {'status': 'ok', 'msg': 'Template has been deleted'}
|
return {'status': 'ok', 'msg': 'Template has been deleted'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Can not delete domain template. Error: {0}'.format(e))
|
'Can not delete zone template. Error: {0}'.format(e))
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return {'status': 'error', 'msg': 'Can not delete domain template'}
|
return {'status': 'error', 'msg': 'Can not delete zone template'}
|
||||||
|
@ -39,9 +39,9 @@ class DomainTemplateRecord(db.Model):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Can not update domain template table. Error: {0}'.format(e))
|
'Can not update zone template table. Error: {0}'.format(e))
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'msg': 'Can not update domain template table'
|
'msg': 'Can not update zone template table'
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class Record(object):
|
|||||||
|
|
||||||
def get_rrsets(self, domain):
|
def get_rrsets(self, domain):
|
||||||
"""
|
"""
|
||||||
Query domain's rrsets via PDNS API
|
Query zone's rrsets via PDNS API
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY}
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
||||||
try:
|
try:
|
||||||
@ -59,7 +59,7 @@ class Record(object):
|
|||||||
verify=Setting().get('verify_ssl_connections'))
|
verify=Setting().get('verify_ssl_connections'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot fetch domain's record data from remote powerdns api. DETAIL: {0}"
|
"Cannot fetch zone's record data from remote powerdns api. DETAIL: {0}"
|
||||||
.format(e))
|
.format(e))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class Record(object):
|
|||||||
|
|
||||||
def add(self, domain_name, rrset):
|
def add(self, domain_name, rrset):
|
||||||
"""
|
"""
|
||||||
Add a record to a domain (Used by auto_ptr and DynDNS)
|
Add a record to a zone (Used by auto_ptr and DynDNS)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
domain_name(str): The zone name
|
domain_name(str): The zone name
|
||||||
@ -115,7 +115,7 @@ class Record(object):
|
|||||||
return {'status': 'ok', 'msg': 'Record was added successfully'}
|
return {'status': 'ok', 'msg': 'Record was added successfully'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot add record to domain {}. Error: {}".format(
|
"Cannot add record to zone {}. Error: {}".format(
|
||||||
domain_name, e))
|
domain_name, e))
|
||||||
current_app.logger.debug("Submitted record rrset: \n{}".format(
|
current_app.logger.debug("Submitted record rrset: \n{}".format(
|
||||||
utils.pretty_json(rrset)))
|
utils.pretty_json(rrset)))
|
||||||
@ -172,7 +172,7 @@ class Record(object):
|
|||||||
record['record_name'] = utils.to_idna(record["record_name"], "encode")
|
record['record_name'] = utils.to_idna(record["record_name"], "encode")
|
||||||
#TODO: error handling
|
#TODO: error handling
|
||||||
# If the record is an alias (CNAME), we will also make sure that
|
# If the record is an alias (CNAME), we will also make sure that
|
||||||
# the target domain is properly converted to punycode (IDN)
|
# the target zone is properly converted to punycode (IDN)
|
||||||
if record['record_type'] == 'CNAME' or record['record_type'] == 'SOA':
|
if record['record_type'] == 'CNAME' or record['record_type'] == 'SOA':
|
||||||
record['record_data'] = utils.to_idna(record['record_data'], 'encode')
|
record['record_data'] = utils.to_idna(record['record_data'], 'encode')
|
||||||
#TODO: error handling
|
#TODO: error handling
|
||||||
@ -343,7 +343,7 @@ class Record(object):
|
|||||||
|
|
||||||
def apply(self, domain_name, submitted_records):
|
def apply(self, domain_name, submitted_records):
|
||||||
"""
|
"""
|
||||||
Apply record changes to a domain. This function
|
Apply record changes to a zone. This function
|
||||||
will make 1 call to the PDNS API to DELETE and
|
will make 1 call to the PDNS API to DELETE and
|
||||||
REPLACE records (rrsets)
|
REPLACE records (rrsets)
|
||||||
"""
|
"""
|
||||||
@ -377,7 +377,7 @@ class Record(object):
|
|||||||
return {'status': 'ok', 'msg': 'Record was applied successfully', 'data': (new_rrsets, del_rrsets)}
|
return {'status': 'ok', 'msg': 'Record was applied successfully', 'data': (new_rrsets, del_rrsets)}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot apply record changes to domain {0}. Error: {1}".format(
|
"Cannot apply record changes to zone {0}. Error: {1}".format(
|
||||||
domain_name, e))
|
domain_name, e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return {
|
return {
|
||||||
@ -480,7 +480,7 @@ class Record(object):
|
|||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot update auto-ptr record changes to domain {0}. Error: {1}"
|
"Cannot update auto-ptr record changes to zone {0}. Error: {1}"
|
||||||
.format(domain_name, e))
|
.format(domain_name, e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return {
|
return {
|
||||||
@ -492,7 +492,7 @@ class Record(object):
|
|||||||
|
|
||||||
def delete(self, domain):
|
def delete(self, domain):
|
||||||
"""
|
"""
|
||||||
Delete a record from domain
|
Delete a record from zone
|
||||||
"""
|
"""
|
||||||
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
||||||
data = {
|
data = {
|
||||||
@ -517,7 +517,7 @@ class Record(object):
|
|||||||
return {'status': 'ok', 'msg': 'Record was removed successfully'}
|
return {'status': 'ok', 'msg': 'Record was removed successfully'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot remove record {0}/{1}/{2} from domain {3}. DETAIL: {4}"
|
"Cannot remove record {0}/{1}/{2} from zone {3}. DETAIL: {4}"
|
||||||
.format(self.name, self.type, self.data, domain, e))
|
.format(self.name, self.type, self.data, domain, e))
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@ -540,7 +540,7 @@ class Record(object):
|
|||||||
|
|
||||||
def exists(self, domain):
|
def exists(self, domain):
|
||||||
"""
|
"""
|
||||||
Check if record is present within domain records, and if it's present set self to found record
|
Check if record is present within zone records, and if it's present set self to found record
|
||||||
"""
|
"""
|
||||||
rrsets = self.get_rrsets(domain)
|
rrsets = self.get_rrsets(domain)
|
||||||
for r in rrsets:
|
for r in rrsets:
|
||||||
@ -588,7 +588,7 @@ class Record(object):
|
|||||||
return {'status': 'ok', 'msg': 'Record was updated successfully'}
|
return {'status': 'ok', 'msg': 'Record was updated successfully'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".
|
"Cannot add record {0}/{1}/{2} to zone {3}. DETAIL: {4}".
|
||||||
format(self.name, self.type, self.data, domain, e))
|
format(self.name, self.type, self.data, domain, e))
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@ -614,11 +614,11 @@ class Record(object):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {
|
return {
|
||||||
'status': True,
|
'status': True,
|
||||||
'msg': 'Synced local serial for domain name {0}'.format(domain)
|
'msg': 'Synced local serial for zone name {0}'.format(domain)
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'status': False,
|
'status': False,
|
||||||
'msg':
|
'msg':
|
||||||
'Could not find domain name {0} in local db'.format(domain)
|
'Could not find zone name {0} in local db'.format(domain)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,131 @@ class Setting(db.Model):
|
|||||||
name = db.Column(db.String(64), unique=True, index=True)
|
name = db.Column(db.String(64), unique=True, index=True)
|
||||||
value = db.Column(db.Text())
|
value = db.Column(db.Text())
|
||||||
|
|
||||||
|
types = {
|
||||||
|
'maintenance': bool,
|
||||||
|
'fullscreen_layout': bool,
|
||||||
|
'record_helper': bool,
|
||||||
|
'login_ldap_first': bool,
|
||||||
|
'default_record_table_size': int,
|
||||||
|
'default_domain_table_size': int,
|
||||||
|
'auto_ptr': bool,
|
||||||
|
'record_quick_edit': bool,
|
||||||
|
'pretty_ipv6_ptr': bool,
|
||||||
|
'dnssec_admins_only': bool,
|
||||||
|
'allow_user_create_domain': bool,
|
||||||
|
'allow_user_remove_domain': bool,
|
||||||
|
'allow_user_view_history': bool,
|
||||||
|
'custom_history_header': str,
|
||||||
|
'delete_sso_accounts': bool,
|
||||||
|
'bg_domain_updates': bool,
|
||||||
|
'enable_api_rr_history': bool,
|
||||||
|
'preserve_history': bool,
|
||||||
|
'site_name': str,
|
||||||
|
'site_url': str,
|
||||||
|
'session_timeout': int,
|
||||||
|
'warn_session_timeout': bool,
|
||||||
|
'pdns_api_url': str,
|
||||||
|
'pdns_api_key': str,
|
||||||
|
'pdns_api_timeout': int,
|
||||||
|
'pdns_version': str,
|
||||||
|
'verify_ssl_connections': bool,
|
||||||
|
'verify_user_email': bool,
|
||||||
|
'enforce_api_ttl': bool,
|
||||||
|
'ttl_options': str,
|
||||||
|
'otp_field_enabled': bool,
|
||||||
|
'custom_css': str,
|
||||||
|
'otp_force': bool,
|
||||||
|
'max_history_records': int,
|
||||||
|
'deny_domain_override': bool,
|
||||||
|
'account_name_extra_chars': bool,
|
||||||
|
'gravatar_enabled': bool,
|
||||||
|
'forward_records_allow_edit': dict,
|
||||||
|
'reverse_records_allow_edit': dict,
|
||||||
|
'local_db_enabled': bool,
|
||||||
|
'signup_enabled': bool,
|
||||||
|
'pwd_enforce_characters': bool,
|
||||||
|
'pwd_min_len': int,
|
||||||
|
'pwd_min_lowercase': int,
|
||||||
|
'pwd_min_uppercase': int,
|
||||||
|
'pwd_min_digits': int,
|
||||||
|
'pwd_min_special': int,
|
||||||
|
'pwd_enforce_complexity': bool,
|
||||||
|
'pwd_min_complexity': int,
|
||||||
|
'ldap_enabled': bool,
|
||||||
|
'ldap_type': str,
|
||||||
|
'ldap_uri': str,
|
||||||
|
'ldap_base_dn': str,
|
||||||
|
'ldap_admin_username': str,
|
||||||
|
'ldap_admin_password': str,
|
||||||
|
'ldap_domain': str,
|
||||||
|
'ldap_filter_basic': str,
|
||||||
|
'ldap_filter_username': str,
|
||||||
|
'ldap_filter_group': str,
|
||||||
|
'ldap_filter_groupname': str,
|
||||||
|
'ldap_sg_enabled': bool,
|
||||||
|
'ldap_admin_group': str,
|
||||||
|
'ldap_operator_group': str,
|
||||||
|
'ldap_user_group': str,
|
||||||
|
'autoprovisioning': bool,
|
||||||
|
'autoprovisioning_attribute': str,
|
||||||
|
'urn_value': str,
|
||||||
|
'purge': bool,
|
||||||
|
'google_oauth_enabled': bool,
|
||||||
|
'google_oauth_client_id': str,
|
||||||
|
'google_oauth_client_secret': str,
|
||||||
|
'google_oauth_scope': str,
|
||||||
|
'google_base_url': str,
|
||||||
|
'google_oauth_auto_configure': bool,
|
||||||
|
'google_oauth_metadata_url': str,
|
||||||
|
'google_token_url': str,
|
||||||
|
'google_authorize_url': str,
|
||||||
|
'github_oauth_enabled': bool,
|
||||||
|
'github_oauth_key': str,
|
||||||
|
'github_oauth_secret': str,
|
||||||
|
'github_oauth_scope': str,
|
||||||
|
'github_oauth_api_url': str,
|
||||||
|
'github_oauth_auto_configure': bool,
|
||||||
|
'github_oauth_metadata_url': str,
|
||||||
|
'github_oauth_token_url': str,
|
||||||
|
'github_oauth_authorize_url': str,
|
||||||
|
'azure_oauth_enabled': bool,
|
||||||
|
'azure_oauth_key': str,
|
||||||
|
'azure_oauth_secret': str,
|
||||||
|
'azure_oauth_scope': str,
|
||||||
|
'azure_oauth_api_url': str,
|
||||||
|
'azure_oauth_auto_configure': bool,
|
||||||
|
'azure_oauth_metadata_url': str,
|
||||||
|
'azure_oauth_token_url': str,
|
||||||
|
'azure_oauth_authorize_url': str,
|
||||||
|
'azure_sg_enabled': bool,
|
||||||
|
'azure_admin_group': str,
|
||||||
|
'azure_operator_group': str,
|
||||||
|
'azure_user_group': str,
|
||||||
|
'azure_group_accounts_enabled': bool,
|
||||||
|
'azure_group_accounts_name': str,
|
||||||
|
'azure_group_accounts_name_re': str,
|
||||||
|
'azure_group_accounts_description': str,
|
||||||
|
'azure_group_accounts_description_re': str,
|
||||||
|
'oidc_oauth_enabled': bool,
|
||||||
|
'oidc_oauth_key': str,
|
||||||
|
'oidc_oauth_secret': str,
|
||||||
|
'oidc_oauth_scope': str,
|
||||||
|
'oidc_oauth_api_url': str,
|
||||||
|
'oidc_oauth_auto_configure': bool,
|
||||||
|
'oidc_oauth_metadata_url': str,
|
||||||
|
'oidc_oauth_token_url': str,
|
||||||
|
'oidc_oauth_authorize_url': str,
|
||||||
|
'oidc_oauth_logout_url': str,
|
||||||
|
'oidc_oauth_username': str,
|
||||||
|
'oidc_oauth_email': str,
|
||||||
|
'oidc_oauth_firstname': str,
|
||||||
|
'oidc_oauth_last_name': str,
|
||||||
|
'oidc_oauth_account_name_property': str,
|
||||||
|
'oidc_oauth_account_description_property': str,
|
||||||
|
}
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
|
# General Settings
|
||||||
'maintenance': False,
|
'maintenance': False,
|
||||||
'fullscreen_layout': True,
|
'fullscreen_layout': True,
|
||||||
'record_helper': True,
|
'record_helper': True,
|
||||||
@ -28,6 +152,7 @@ class Setting(db.Model):
|
|||||||
'allow_user_create_domain': False,
|
'allow_user_create_domain': False,
|
||||||
'allow_user_remove_domain': False,
|
'allow_user_remove_domain': False,
|
||||||
'allow_user_view_history': False,
|
'allow_user_view_history': False,
|
||||||
|
'custom_history_header': '',
|
||||||
'delete_sso_accounts': False,
|
'delete_sso_accounts': False,
|
||||||
'bg_domain_updates': False,
|
'bg_domain_updates': False,
|
||||||
'enable_api_rr_history': True,
|
'enable_api_rr_history': True,
|
||||||
@ -41,53 +166,82 @@ class Setting(db.Model):
|
|||||||
'pdns_api_timeout': 30,
|
'pdns_api_timeout': 30,
|
||||||
'pdns_version': '4.1.1',
|
'pdns_version': '4.1.1',
|
||||||
'verify_ssl_connections': True,
|
'verify_ssl_connections': True,
|
||||||
|
'verify_user_email': False,
|
||||||
|
'enforce_api_ttl': False,
|
||||||
|
'ttl_options': '1 minute,5 minutes,30 minutes,60 minutes,24 hours',
|
||||||
|
'otp_field_enabled': True,
|
||||||
|
'custom_css': '',
|
||||||
|
'otp_force': False,
|
||||||
|
'max_history_records': 1000,
|
||||||
|
'deny_domain_override': False,
|
||||||
|
'account_name_extra_chars': False,
|
||||||
|
'gravatar_enabled': False,
|
||||||
|
|
||||||
|
# Local Authentication Settings
|
||||||
'local_db_enabled': True,
|
'local_db_enabled': True,
|
||||||
'signup_enabled': True,
|
'signup_enabled': True,
|
||||||
'autoprovisioning': False,
|
'pwd_enforce_characters': False,
|
||||||
'urn_value':'',
|
'pwd_min_len': 10,
|
||||||
'autoprovisioning_attribute': '',
|
'pwd_min_lowercase': 3,
|
||||||
'purge': False,
|
'pwd_min_uppercase': 2,
|
||||||
'verify_user_email': False,
|
'pwd_min_digits': 2,
|
||||||
|
'pwd_min_special': 1,
|
||||||
|
'pwd_enforce_complexity': False,
|
||||||
|
'pwd_min_complexity': 11,
|
||||||
|
|
||||||
|
# LDAP Authentication Settings
|
||||||
'ldap_enabled': False,
|
'ldap_enabled': False,
|
||||||
'ldap_type': 'ldap',
|
'ldap_type': 'ldap',
|
||||||
'ldap_uri': '',
|
'ldap_uri': '',
|
||||||
'ldap_base_dn': '',
|
'ldap_base_dn': '',
|
||||||
'ldap_admin_username': '',
|
'ldap_admin_username': '',
|
||||||
'ldap_admin_password': '',
|
'ldap_admin_password': '',
|
||||||
|
'ldap_domain': '',
|
||||||
'ldap_filter_basic': '',
|
'ldap_filter_basic': '',
|
||||||
'ldap_filter_group': '',
|
|
||||||
'ldap_filter_username': '',
|
'ldap_filter_username': '',
|
||||||
|
'ldap_filter_group': '',
|
||||||
'ldap_filter_groupname': '',
|
'ldap_filter_groupname': '',
|
||||||
'ldap_sg_enabled': False,
|
'ldap_sg_enabled': False,
|
||||||
'ldap_admin_group': '',
|
'ldap_admin_group': '',
|
||||||
'ldap_operator_group': '',
|
'ldap_operator_group': '',
|
||||||
'ldap_user_group': '',
|
'ldap_user_group': '',
|
||||||
'ldap_domain': '',
|
'autoprovisioning': False,
|
||||||
|
'autoprovisioning_attribute': '',
|
||||||
|
'urn_value': '',
|
||||||
|
'purge': False,
|
||||||
|
|
||||||
|
# Google OAuth2 Settings
|
||||||
|
'google_oauth_enabled': False,
|
||||||
|
'google_oauth_client_id': '',
|
||||||
|
'google_oauth_client_secret': '',
|
||||||
|
'google_oauth_scope': 'openid email profile',
|
||||||
|
'google_base_url': 'https://www.googleapis.com/oauth2/v3/',
|
||||||
|
'google_oauth_auto_configure': True,
|
||||||
|
'google_oauth_metadata_url': 'https://accounts.google.com/.well-known/openid-configuration',
|
||||||
|
'google_token_url': 'https://oauth2.googleapis.com/token',
|
||||||
|
'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||||
|
|
||||||
|
# GitHub OAuth2 Settings
|
||||||
'github_oauth_enabled': False,
|
'github_oauth_enabled': False,
|
||||||
'github_oauth_key': '',
|
'github_oauth_key': '',
|
||||||
'github_oauth_secret': '',
|
'github_oauth_secret': '',
|
||||||
'github_oauth_scope': 'email',
|
'github_oauth_scope': 'email',
|
||||||
'github_oauth_api_url': 'https://api.github.com/user',
|
'github_oauth_api_url': 'https://api.github.com/user',
|
||||||
'github_oauth_token_url':
|
'github_oauth_auto_configure': False,
|
||||||
'https://github.com/login/oauth/access_token',
|
'github_oauth_metadata_url': '',
|
||||||
'github_oauth_authorize_url':
|
'github_oauth_token_url': 'https://github.com/login/oauth/access_token',
|
||||||
'https://github.com/login/oauth/authorize',
|
'github_oauth_authorize_url': 'https://github.com/login/oauth/authorize',
|
||||||
'google_oauth_enabled': False,
|
|
||||||
'google_oauth_client_id': '',
|
# Azure OAuth2 Settings
|
||||||
'google_oauth_client_secret': '',
|
|
||||||
'google_token_url': 'https://oauth2.googleapis.com/token',
|
|
||||||
'google_oauth_scope': 'openid email profile',
|
|
||||||
'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
||||||
'google_base_url': 'https://www.googleapis.com/oauth2/v3/',
|
|
||||||
'azure_oauth_enabled': False,
|
'azure_oauth_enabled': False,
|
||||||
'azure_oauth_key': '',
|
'azure_oauth_key': '',
|
||||||
'azure_oauth_secret': '',
|
'azure_oauth_secret': '',
|
||||||
'azure_oauth_scope': 'User.Read openid email profile',
|
'azure_oauth_scope': 'User.Read openid email profile',
|
||||||
'azure_oauth_api_url': 'https://graph.microsoft.com/v1.0/',
|
'azure_oauth_api_url': 'https://graph.microsoft.com/v1.0/',
|
||||||
'azure_oauth_token_url':
|
'azure_oauth_auto_configure': True,
|
||||||
'https://login.microsoftonline.com/[tenancy]/oauth2/v2.0/token',
|
'azure_oauth_metadata_url': '',
|
||||||
'azure_oauth_authorize_url':
|
'azure_oauth_token_url': '',
|
||||||
'https://login.microsoftonline.com/[tenancy]/oauth2/v2.0/authorize',
|
'azure_oauth_authorize_url': '',
|
||||||
'azure_sg_enabled': False,
|
'azure_sg_enabled': False,
|
||||||
'azure_admin_group': '',
|
'azure_admin_group': '',
|
||||||
'azure_operator_group': '',
|
'azure_operator_group': '',
|
||||||
@ -97,22 +251,26 @@ class Setting(db.Model):
|
|||||||
'azure_group_accounts_name_re': '',
|
'azure_group_accounts_name_re': '',
|
||||||
'azure_group_accounts_description': 'description',
|
'azure_group_accounts_description': 'description',
|
||||||
'azure_group_accounts_description_re': '',
|
'azure_group_accounts_description_re': '',
|
||||||
|
|
||||||
|
# OIDC OAuth2 Settings
|
||||||
'oidc_oauth_enabled': False,
|
'oidc_oauth_enabled': False,
|
||||||
'oidc_oauth_key': '',
|
'oidc_oauth_key': '',
|
||||||
'oidc_oauth_secret': '',
|
'oidc_oauth_secret': '',
|
||||||
'oidc_oauth_scope': 'email',
|
'oidc_oauth_scope': 'email',
|
||||||
'oidc_oauth_api_url': '',
|
'oidc_oauth_api_url': '',
|
||||||
|
'oidc_oauth_auto_configure': True,
|
||||||
|
'oidc_oauth_metadata_url': '',
|
||||||
'oidc_oauth_token_url': '',
|
'oidc_oauth_token_url': '',
|
||||||
'oidc_oauth_authorize_url': '',
|
'oidc_oauth_authorize_url': '',
|
||||||
'oidc_oauth_metadata_url': '',
|
|
||||||
'oidc_oauth_logout_url': '',
|
'oidc_oauth_logout_url': '',
|
||||||
'oidc_oauth_username': 'preferred_username',
|
'oidc_oauth_username': 'preferred_username',
|
||||||
|
'oidc_oauth_email': 'email',
|
||||||
'oidc_oauth_firstname': 'given_name',
|
'oidc_oauth_firstname': 'given_name',
|
||||||
'oidc_oauth_last_name': 'family_name',
|
'oidc_oauth_last_name': 'family_name',
|
||||||
'oidc_oauth_email': 'email',
|
|
||||||
'oidc_oauth_account_name_property': '',
|
'oidc_oauth_account_name_property': '',
|
||||||
'oidc_oauth_account_description_property': '',
|
'oidc_oauth_account_description_property': '',
|
||||||
'enforce_api_ttl': False,
|
|
||||||
|
# Zone Record Settings
|
||||||
'forward_records_allow_edit': {
|
'forward_records_allow_edit': {
|
||||||
'A': True,
|
'A': True,
|
||||||
'AAAA': True,
|
'AAAA': True,
|
||||||
@ -189,14 +347,103 @@ class Setting(db.Model):
|
|||||||
'TXT': True,
|
'TXT': True,
|
||||||
'URI': False
|
'URI': False
|
||||||
},
|
},
|
||||||
'ttl_options': '1 minute,5 minutes,30 minutes,60 minutes,24 hours',
|
}
|
||||||
'otp_field_enabled': True,
|
|
||||||
'custom_css': '',
|
groups = {
|
||||||
'otp_force': False,
|
'authentication': [
|
||||||
'max_history_records': 1000,
|
# Local Authentication Settings
|
||||||
'deny_domain_override': False,
|
'local_db_enabled',
|
||||||
'account_name_extra_chars': False,
|
'signup_enabled',
|
||||||
'gravatar_enabled': False,
|
'pwd_enforce_characters',
|
||||||
|
'pwd_min_len',
|
||||||
|
'pwd_min_lowercase',
|
||||||
|
'pwd_min_uppercase',
|
||||||
|
'pwd_min_digits',
|
||||||
|
'pwd_min_special',
|
||||||
|
'pwd_enforce_complexity',
|
||||||
|
'pwd_min_complexity',
|
||||||
|
|
||||||
|
# LDAP Authentication Settings
|
||||||
|
'ldap_enabled',
|
||||||
|
'ldap_type',
|
||||||
|
'ldap_uri',
|
||||||
|
'ldap_base_dn',
|
||||||
|
'ldap_admin_username',
|
||||||
|
'ldap_admin_password',
|
||||||
|
'ldap_domain',
|
||||||
|
'ldap_filter_basic',
|
||||||
|
'ldap_filter_username',
|
||||||
|
'ldap_filter_group',
|
||||||
|
'ldap_filter_groupname',
|
||||||
|
'ldap_sg_enabled',
|
||||||
|
'ldap_admin_group',
|
||||||
|
'ldap_operator_group',
|
||||||
|
'ldap_user_group',
|
||||||
|
'autoprovisioning',
|
||||||
|
'autoprovisioning_attribute',
|
||||||
|
'urn_value',
|
||||||
|
'purge',
|
||||||
|
|
||||||
|
# Google OAuth2 Settings
|
||||||
|
'google_oauth_enabled',
|
||||||
|
'google_oauth_client_id',
|
||||||
|
'google_oauth_client_secret',
|
||||||
|
'google_oauth_scope',
|
||||||
|
'google_base_url',
|
||||||
|
'google_oauth_auto_configure',
|
||||||
|
'google_oauth_metadata_url',
|
||||||
|
'google_token_url',
|
||||||
|
'google_authorize_url',
|
||||||
|
|
||||||
|
# GitHub OAuth2 Settings
|
||||||
|
'github_oauth_enabled',
|
||||||
|
'github_oauth_key',
|
||||||
|
'github_oauth_secret',
|
||||||
|
'github_oauth_scope',
|
||||||
|
'github_oauth_api_url',
|
||||||
|
'github_oauth_auto_configure',
|
||||||
|
'github_oauth_metadata_url',
|
||||||
|
'github_oauth_token_url',
|
||||||
|
'github_oauth_authorize_url',
|
||||||
|
|
||||||
|
# Azure OAuth2 Settings
|
||||||
|
'azure_oauth_enabled',
|
||||||
|
'azure_oauth_key',
|
||||||
|
'azure_oauth_secret',
|
||||||
|
'azure_oauth_scope',
|
||||||
|
'azure_oauth_api_url',
|
||||||
|
'azure_oauth_auto_configure',
|
||||||
|
'azure_oauth_metadata_url',
|
||||||
|
'azure_oauth_token_url',
|
||||||
|
'azure_oauth_authorize_url',
|
||||||
|
'azure_sg_enabled',
|
||||||
|
'azure_admin_group',
|
||||||
|
'azure_operator_group',
|
||||||
|
'azure_user_group',
|
||||||
|
'azure_group_accounts_enabled',
|
||||||
|
'azure_group_accounts_name',
|
||||||
|
'azure_group_accounts_name_re',
|
||||||
|
'azure_group_accounts_description',
|
||||||
|
'azure_group_accounts_description_re',
|
||||||
|
|
||||||
|
# OIDC OAuth2 Settings
|
||||||
|
'oidc_oauth_enabled',
|
||||||
|
'oidc_oauth_key',
|
||||||
|
'oidc_oauth_secret',
|
||||||
|
'oidc_oauth_scope',
|
||||||
|
'oidc_oauth_api_url',
|
||||||
|
'oidc_oauth_auto_configure',
|
||||||
|
'oidc_oauth_metadata_url',
|
||||||
|
'oidc_oauth_token_url',
|
||||||
|
'oidc_oauth_authorize_url',
|
||||||
|
'oidc_oauth_logout_url',
|
||||||
|
'oidc_oauth_username',
|
||||||
|
'oidc_oauth_email',
|
||||||
|
'oidc_oauth_firstname',
|
||||||
|
'oidc_oauth_last_name',
|
||||||
|
'oidc_oauth_account_name_property',
|
||||||
|
'oidc_oauth_account_description_property',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, id=None, name=None, value=None):
|
def __init__(self, id=None, name=None, value=None):
|
||||||
@ -210,6 +457,34 @@ class Setting(db.Model):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
def convert_type(self, name, value):
|
||||||
|
import json
|
||||||
|
if name in self.types:
|
||||||
|
var_type = self.types[name]
|
||||||
|
|
||||||
|
# Handle boolean values
|
||||||
|
if var_type == bool:
|
||||||
|
if value == 'True' or value == 'true' or value == '1' or value == True:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Handle float values
|
||||||
|
if var_type == float:
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
# Handle integer values
|
||||||
|
if var_type == int:
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
if var_type == dict or var_type == list:
|
||||||
|
return json.loads(value)
|
||||||
|
|
||||||
|
if var_type == str:
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
def set_maintenance(self, mode):
|
def set_maintenance(self, mode):
|
||||||
maintenance = Setting.query.filter(
|
maintenance = Setting.query.filter(
|
||||||
Setting.name == 'maintenance').first()
|
Setting.name == 'maintenance').first()
|
||||||
@ -262,7 +537,7 @@ class Setting(db.Model):
|
|||||||
current_setting = Setting(name=setting, value=None)
|
current_setting = Setting(name=setting, value=None)
|
||||||
db.session.add(current_setting)
|
db.session.add(current_setting)
|
||||||
|
|
||||||
value = str(value)
|
value = str(self.convert_type(setting, value))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_setting.value = value
|
current_setting.value = value
|
||||||
@ -286,14 +561,26 @@ class Setting(db.Model):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
if hasattr(result, 'value'):
|
if hasattr(result, 'value'):
|
||||||
result = result.value
|
result = result.value
|
||||||
return strtobool(result) if result in [
|
|
||||||
'True', 'False'
|
return self.convert_type(setting, result)
|
||||||
] else result
|
|
||||||
else:
|
else:
|
||||||
return self.defaults[setting]
|
return self.defaults[setting]
|
||||||
else:
|
else:
|
||||||
current_app.logger.error('Unknown setting queried: {0}'.format(setting))
|
current_app.logger.error('Unknown setting queried: {0}'.format(setting))
|
||||||
|
|
||||||
|
def get_group(self, group):
|
||||||
|
if not isinstance(group, list):
|
||||||
|
group = self.groups[group]
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
records = self.query.all()
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
if record.name in group:
|
||||||
|
result[record.name] = self.convert_type(record.name, record.value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def get_records_allow_to_edit(self):
|
def get_records_allow_to_edit(self):
|
||||||
return list(
|
return list(
|
||||||
set(self.get_forward_records_allow_to_edit() +
|
set(self.get_forward_records_allow_to_edit() +
|
||||||
|
@ -5,6 +5,7 @@ import bcrypt
|
|||||||
import pyotp
|
import pyotp
|
||||||
import ldap
|
import ldap
|
||||||
import ldap.filter
|
import ldap.filter
|
||||||
|
from collections import OrderedDict
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import AnonymousUserMixin
|
from flask_login import AnonymousUserMixin
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
@ -90,8 +91,8 @@ class User(db.Model):
|
|||||||
return '<User {0}>'.format(self.username)
|
return '<User {0}>'.format(self.username)
|
||||||
|
|
||||||
def get_totp_uri(self):
|
def get_totp_uri(self):
|
||||||
return "otpauth://totp/PowerDNS-Admin:{0}?secret={1}&issuer=PowerDNS-Admin".format(
|
return "otpauth://totp/{0}:{1}?secret={2}&issuer=PowerDNS-Admin".format(
|
||||||
self.username, self.otp_secret)
|
Setting().get('site_name'), self.username, self.otp_secret)
|
||||||
|
|
||||||
def verify_totp(self, token):
|
def verify_totp(self, token):
|
||||||
totp = pyotp.TOTP(self.otp_secret)
|
totp = pyotp.TOTP(self.otp_secret)
|
||||||
@ -254,82 +255,82 @@ class User(db.Model):
|
|||||||
if LDAP_TYPE == 'ldap':
|
if LDAP_TYPE == 'ldap':
|
||||||
groupSearchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_GROUPNAME, ldap_username, LDAP_FILTER_GROUP)
|
groupSearchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_GROUPNAME, ldap_username, LDAP_FILTER_GROUP)
|
||||||
current_app.logger.debug('Ldap groupSearchFilter {0}'.format(groupSearchFilter))
|
current_app.logger.debug('Ldap groupSearchFilter {0}'.format(groupSearchFilter))
|
||||||
if (self.ldap_search(groupSearchFilter,
|
if (LDAP_ADMIN_GROUP and self.ldap_search(groupSearchFilter, LDAP_ADMIN_GROUP)):
|
||||||
LDAP_ADMIN_GROUP)):
|
|
||||||
role_name = 'Administrator'
|
role_name = 'Administrator'
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'
|
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'
|
||||||
.format(self.username,
|
.format(self.username, LDAP_ADMIN_GROUP))
|
||||||
LDAP_ADMIN_GROUP))
|
elif (LDAP_OPERATOR_GROUP and self.ldap_search(groupSearchFilter, LDAP_OPERATOR_GROUP)):
|
||||||
elif (self.ldap_search(groupSearchFilter,
|
|
||||||
LDAP_OPERATOR_GROUP)):
|
|
||||||
role_name = 'Operator'
|
role_name = 'Operator'
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'
|
'User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'
|
||||||
.format(self.username,
|
.format(self.username, LDAP_OPERATOR_GROUP))
|
||||||
LDAP_OPERATOR_GROUP))
|
elif (LDAP_USER_GROUP and self.ldap_search(groupSearchFilter, LDAP_USER_GROUP)):
|
||||||
elif (self.ldap_search(groupSearchFilter,
|
|
||||||
LDAP_USER_GROUP)):
|
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'
|
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'
|
||||||
.format(self.username,
|
.format(self.username, LDAP_USER_GROUP))
|
||||||
LDAP_USER_GROUP))
|
|
||||||
else:
|
else:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'
|
'User {0} is not part of any security groups that allow access to PowerDNS-Admin'
|
||||||
.format(self.username,
|
|
||||||
LDAP_ADMIN_GROUP,
|
|
||||||
LDAP_OPERATOR_GROUP,
|
|
||||||
LDAP_USER_GROUP))
|
|
||||||
return False
|
|
||||||
elif LDAP_TYPE == 'ad':
|
|
||||||
ldap_admin_group_filter, ldap_operator_group, ldap_user_group = "", "", ""
|
|
||||||
if LDAP_ADMIN_GROUP:
|
|
||||||
ldap_admin_group_filter = "(memberOf:1.2.840.113556.1.4.1941:={0})".format(LDAP_ADMIN_GROUP)
|
|
||||||
if LDAP_OPERATOR_GROUP:
|
|
||||||
ldap_operator_group = "(memberOf:1.2.840.113556.1.4.1941:={0})".format(LDAP_OPERATOR_GROUP)
|
|
||||||
if LDAP_USER_GROUP:
|
|
||||||
ldap_user_group = "(memberOf:1.2.840.113556.1.4.1941:={0})".format(LDAP_USER_GROUP)
|
|
||||||
searchFilter = "(&({0}={1})(|{2}{3}{4}))".format(LDAP_FILTER_USERNAME, self.username,
|
|
||||||
ldap_admin_group_filter,
|
|
||||||
ldap_operator_group, ldap_user_group)
|
|
||||||
ldap_result = self.ldap_search(searchFilter, LDAP_BASE_DN)
|
|
||||||
user_ad_member_of = ldap_result[0][0][1].get(
|
|
||||||
'memberOf')
|
|
||||||
|
|
||||||
if not user_ad_member_of:
|
|
||||||
current_app.logger.error(
|
|
||||||
'User {0} does not belong to any group while LDAP_GROUP_SECURITY_ENABLED is ON'
|
|
||||||
.format(self.username))
|
.format(self.username))
|
||||||
return False
|
return False
|
||||||
|
elif LDAP_TYPE == 'ad':
|
||||||
|
ldap_group_security_roles = OrderedDict(
|
||||||
|
Administrator=LDAP_ADMIN_GROUP,
|
||||||
|
Operator=LDAP_OPERATOR_GROUP,
|
||||||
|
User=LDAP_USER_GROUP,
|
||||||
|
)
|
||||||
|
user_dn = ldap_result[0][0][0]
|
||||||
|
sf_groups = ""
|
||||||
|
|
||||||
user_ad_member_of = [g.decode("utf-8") for g in user_ad_member_of]
|
for group in ldap_group_security_roles.values():
|
||||||
|
if not group:
|
||||||
|
continue
|
||||||
|
|
||||||
if (LDAP_ADMIN_GROUP in user_ad_member_of):
|
sf_groups += f"(distinguishedName={group})"
|
||||||
role_name = 'Administrator'
|
|
||||||
current_app.logger.info(
|
sf_member_user = f"(member:1.2.840.113556.1.4.1941:={user_dn})"
|
||||||
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'
|
search_filter = f"(&(|{sf_groups}){sf_member_user})"
|
||||||
.format(self.username,
|
current_app.logger.debug(f"LDAP groupSearchFilter '{search_filter}'")
|
||||||
LDAP_ADMIN_GROUP))
|
|
||||||
elif (LDAP_OPERATOR_GROUP in user_ad_member_of):
|
ldap_user_groups = [
|
||||||
role_name = 'Operator'
|
group[0][0]
|
||||||
current_app.logger.info(
|
for group in self.ldap_search(
|
||||||
'User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'
|
search_filter,
|
||||||
.format(self.username,
|
LDAP_BASE_DN
|
||||||
LDAP_OPERATOR_GROUP))
|
)
|
||||||
elif (LDAP_USER_GROUP in user_ad_member_of):
|
]
|
||||||
current_app.logger.info(
|
|
||||||
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'
|
if not ldap_user_groups:
|
||||||
.format(self.username,
|
|
||||||
LDAP_USER_GROUP))
|
|
||||||
else:
|
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'
|
f"User '{self.username}' "
|
||||||
.format(self.username,
|
"does not belong to any group "
|
||||||
LDAP_ADMIN_GROUP,
|
"while LDAP_GROUP_SECURITY_ENABLED is ON"
|
||||||
LDAP_OPERATOR_GROUP,
|
)
|
||||||
LDAP_USER_GROUP))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
current_app.logger.debug(
|
||||||
|
"LDAP User security groups "
|
||||||
|
f"for user '{self.username}': "
|
||||||
|
" ".join(ldap_user_groups)
|
||||||
|
)
|
||||||
|
|
||||||
|
for role, ldap_group in ldap_group_security_roles.items():
|
||||||
|
# Continue when groups is not defined or
|
||||||
|
# user is'nt member of LDAP group
|
||||||
|
if not ldap_group or not ldap_group in ldap_user_groups:
|
||||||
|
continue
|
||||||
|
|
||||||
|
role_name = role
|
||||||
|
current_app.logger.info(
|
||||||
|
f"User '{self.username}' member of "
|
||||||
|
f"the '{ldap_group}' group that allows "
|
||||||
|
f"'{role}' access to to PowerDNS-Admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stop loop on first found
|
||||||
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
current_app.logger.error('Invalid LDAP type')
|
current_app.logger.error('Invalid LDAP type')
|
||||||
return False
|
return False
|
||||||
@ -527,7 +528,7 @@ class User(db.Model):
|
|||||||
|
|
||||||
def get_domains(self):
|
def get_domains(self):
|
||||||
"""
|
"""
|
||||||
Get list of domains which the user is granted to have
|
Get list of zones which the user is granted to have
|
||||||
access.
|
access.
|
||||||
|
|
||||||
Note: This doesn't include the permission granting from Account
|
Note: This doesn't include the permission granting from Account
|
||||||
@ -680,7 +681,7 @@ class User(db.Model):
|
|||||||
|
|
||||||
def addMissingDomain(self, autoprovision_domain, current_domains):
|
def addMissingDomain(self, autoprovision_domain, current_domains):
|
||||||
"""
|
"""
|
||||||
Add domain gathered by autoprovisioning to the current domains list of a user
|
Add domain gathered by autoprovisioning to the current zones list of a user
|
||||||
"""
|
"""
|
||||||
from ..models.domain import Domain
|
from ..models.domain import Domain
|
||||||
user = db.session.query(User).filter(User.username == self.username).first()
|
user = db.session.query(User).filter(User.username == self.username).first()
|
||||||
|
@ -838,10 +838,10 @@ class DetailedHistory():
|
|||||||
|
|
||||||
detail_dict = json.loads(history.detail)
|
detail_dict = json.loads(history.detail)
|
||||||
|
|
||||||
if 'domain_type' in detail_dict and 'account_id' in detail_dict: # this is a domain creation
|
if 'domain_type' in detail_dict and 'account_id' in detail_dict: # this is a zone creation
|
||||||
self.detailed_msg = render_template_string("""
|
self.detailed_msg = render_template_string("""
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Domain Type:</td><td>{{ domaintype }}</td></tr>
|
<tr><td>Zone Type:</td><td>{{ domaintype }}</td></tr>
|
||||||
<tr><td>Account:</td><td>{{ account }}</td></tr>
|
<tr><td>Account:</td><td>{{ account }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
@ -881,7 +881,7 @@ class DetailedHistory():
|
|||||||
authenticator=detail_dict['authenticator'],
|
authenticator=detail_dict['authenticator'],
|
||||||
ip_address=detail_dict['ip_address'])
|
ip_address=detail_dict['ip_address'])
|
||||||
|
|
||||||
elif 'add_rrsets' in detail_dict: # this is a domain record change
|
elif 'add_rrsets' in detail_dict: # this is a zone record change
|
||||||
# changes_set = []
|
# changes_set = []
|
||||||
self.detailed_msg = ""
|
self.detailed_msg = ""
|
||||||
# extract_changelogs_from_a_history_entry(changes_set, history, 0)
|
# extract_changelogs_from_a_history_entry(changes_set, history, 0)
|
||||||
@ -897,11 +897,12 @@ class DetailedHistory():
|
|||||||
description=DetailedHistory.get_key_val(detail_dict,
|
description=DetailedHistory.get_key_val(detail_dict,
|
||||||
"description"))
|
"description"))
|
||||||
|
|
||||||
elif 'Change domain' in history.msg and 'access control' in history.msg: # added or removed a user from a domain
|
elif any(msg in history.msg for msg in ['Change zone',
|
||||||
|
'Change domain']) and 'access control' in history.msg: # added or removed a user from a zone
|
||||||
users_with_access = DetailedHistory.get_key_val(detail_dict, "user_has_access")
|
users_with_access = DetailedHistory.get_key_val(detail_dict, "user_has_access")
|
||||||
self.detailed_msg = render_template_string("""
|
self.detailed_msg = render_template_string("""
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Users with access to this domain</td><td>{{ users_with_access }}</td></tr>
|
<tr><td>Users with access to this zone</td><td>{{ users_with_access }}</td></tr>
|
||||||
<tr><td>Number of users:</td><td>{{ users_with_access | length }}</td><tr>
|
<tr><td>Number of users:</td><td>{{ users_with_access | length }}</td><tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
@ -913,7 +914,7 @@ class DetailedHistory():
|
|||||||
<tr><td>Key: </td><td>{{ keyname }}</td></tr>
|
<tr><td>Key: </td><td>{{ keyname }}</td></tr>
|
||||||
<tr><td>Role:</td><td>{{ rolename }}</td></tr>
|
<tr><td>Role:</td><td>{{ rolename }}</td></tr>
|
||||||
<tr><td>Description:</td><td>{{ description }}</td></tr>
|
<tr><td>Description:</td><td>{{ description }}</td></tr>
|
||||||
<tr><td>Accessible domains with this API key:</td><td>{{ linked_domains }}</td></tr>
|
<tr><td>Accessible zones with this API key:</td><td>{{ linked_domains }}</td></tr>
|
||||||
<tr><td>Accessible accounts with this API key:</td><td>{{ linked_accounts }}</td></tr>
|
<tr><td>Accessible accounts with this API key:</td><td>{{ linked_accounts }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
@ -932,7 +933,7 @@ class DetailedHistory():
|
|||||||
<tr><td>Key: </td><td>{{ keyname }}</td></tr>
|
<tr><td>Key: </td><td>{{ keyname }}</td></tr>
|
||||||
<tr><td>Role:</td><td>{{ rolename }}</td></tr>
|
<tr><td>Role:</td><td>{{ rolename }}</td></tr>
|
||||||
<tr><td>Description:</td><td>{{ description }}</td></tr>
|
<tr><td>Description:</td><td>{{ description }}</td></tr>
|
||||||
<tr><td>Accessible domains with this API key:</td><td>{{ linked_domains }}</td></tr>
|
<tr><td>Accessible zones with this API key:</td><td>{{ linked_domains }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
keyname=DetailedHistory.get_key_val(detail_dict, "key"),
|
keyname=DetailedHistory.get_key_val(detail_dict, "key"),
|
||||||
@ -942,11 +943,11 @@ class DetailedHistory():
|
|||||||
linked_domains=DetailedHistory.get_key_val(detail_dict,
|
linked_domains=DetailedHistory.get_key_val(detail_dict,
|
||||||
"domains"))
|
"domains"))
|
||||||
|
|
||||||
elif 'Update type for domain' in history.msg:
|
elif any(msg in history.msg for msg in ['Update type for zone', 'Update type for domain']):
|
||||||
self.detailed_msg = render_template_string("""
|
self.detailed_msg = render_template_string("""
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Domain: </td><td>{{ domain }}</td></tr>
|
<tr><td>Zone: </td><td>{{ domain }}</td></tr>
|
||||||
<tr><td>Domain type:</td><td>{{ domain_type }}</td></tr>
|
<tr><td>Zone type:</td><td>{{ domain_type }}</td></tr>
|
||||||
<tr><td>Masters:</td><td>{{ masters }}</td></tr>
|
<tr><td>Masters:</td><td>{{ masters }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
@ -957,8 +958,8 @@ class DetailedHistory():
|
|||||||
elif 'reverse' in history.msg:
|
elif 'reverse' in history.msg:
|
||||||
self.detailed_msg = render_template_string("""
|
self.detailed_msg = render_template_string("""
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Domain Type: </td><td>{{ domain_type }}</td></tr>
|
<tr><td>Zone Type: </td><td>{{ domain_type }}</td></tr>
|
||||||
<tr><td>Domain Master IPs:</td><td>{{ domain_master_ips }}</td></tr>
|
<tr><td>Zone Master IPs:</td><td>{{ domain_master_ips }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""",
|
""",
|
||||||
domain_type=DetailedHistory.get_key_val(detail_dict,
|
domain_type=DetailedHistory.get_key_val(detail_dict,
|
||||||
@ -977,7 +978,8 @@ class DetailedHistory():
|
|||||||
'status'),
|
'status'),
|
||||||
history_msg=DetailedHistory.get_key_val(detail_dict, 'msg'))
|
history_msg=DetailedHistory.get_key_val(detail_dict, 'msg'))
|
||||||
|
|
||||||
elif 'Update domain' in history.msg and 'associate account' in history.msg: # When an account gets associated or dissociate with domains
|
elif any(msg in history.msg for msg in ['Update zone',
|
||||||
|
'Update domain']) and 'associate account' in history.msg: # When an account gets associated or dissociate with zones
|
||||||
self.detailed_msg = render_template_string('''
|
self.detailed_msg = render_template_string('''
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Associate: </td><td>{{ history_assoc_account }}</td></tr>
|
<tr><td>Associate: </td><td>{{ history_assoc_account }}</td></tr>
|
||||||
@ -1164,7 +1166,7 @@ def history_table(): # ajax call data
|
|||||||
else:
|
else:
|
||||||
# if the user isn't an administrator or operator,
|
# if the user isn't an administrator or operator,
|
||||||
# allow_user_view_history must be enabled to get here,
|
# allow_user_view_history must be enabled to get here,
|
||||||
# so include history for the domains for the user
|
# so include history for the zones for the user
|
||||||
base_query = db.session.query(History) \
|
base_query = db.session.query(History) \
|
||||||
.join(Domain, History.domain_id == Domain.id) \
|
.join(Domain, History.domain_id == Domain.id) \
|
||||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||||
@ -1233,9 +1235,14 @@ def history_table(): # ajax call data
|
|||||||
db.or_(
|
db.or_(
|
||||||
History.msg.like("%domain " + domain_name) if domain_name != "*" else History.msg.like(
|
History.msg.like("%domain " + domain_name) if domain_name != "*" else History.msg.like(
|
||||||
"%domain%"),
|
"%domain%"),
|
||||||
|
History.msg.like("%zone " + domain_name) if domain_name != "*" else History.msg.like(
|
||||||
|
"%zone%"),
|
||||||
History.msg.like(
|
History.msg.like(
|
||||||
"%domain " + domain_name + " access control") if domain_name != "*" else History.msg.like(
|
"%domain " + domain_name + " access control") if domain_name != "*" else History.msg.like(
|
||||||
"%domain%access control")
|
"%domain%access control"),
|
||||||
|
History.msg.like(
|
||||||
|
"%zone " + domain_name + " access control") if domain_name != "*" else History.msg.like(
|
||||||
|
"%zone%access control")
|
||||||
),
|
),
|
||||||
History.created_on <= max_date if max_date != None else True,
|
History.created_on <= max_date if max_date != None else True,
|
||||||
History.created_on >= min_date if min_date != None else True,
|
History.created_on >= min_date if min_date != None else True,
|
||||||
@ -1247,8 +1254,12 @@ def history_table(): # ajax call data
|
|||||||
histories = base_query \
|
histories = base_query \
|
||||||
.filter(
|
.filter(
|
||||||
db.and_(
|
db.and_(
|
||||||
|
db.or_(
|
||||||
History.msg.like("Apply record changes to domain " + domain_name) if domain_name != "*" \
|
History.msg.like("Apply record changes to domain " + domain_name) if domain_name != "*" \
|
||||||
else History.msg.like("Apply record changes to domain%"),
|
else History.msg.like("Apply record changes to domain%"),
|
||||||
|
History.msg.like("Apply record changes to zone " + domain_name) if domain_name != "*" \
|
||||||
|
else History.msg.like("Apply record changes to zone%"),
|
||||||
|
),
|
||||||
History.created_on <= max_date if max_date != None else True,
|
History.created_on <= max_date if max_date != None else True,
|
||||||
History.created_on >= min_date if min_date != None else True,
|
History.created_on >= min_date if min_date != None else True,
|
||||||
History.created_by == changed_by if changed_by != None else True
|
History.created_by == changed_by if changed_by != None else True
|
||||||
@ -1391,6 +1402,7 @@ def setting_basic():
|
|||||||
'default_domain_table_size',
|
'default_domain_table_size',
|
||||||
'default_record_table_size',
|
'default_record_table_size',
|
||||||
'delete_sso_accounts',
|
'delete_sso_accounts',
|
||||||
|
'custom_history_header',
|
||||||
'deny_domain_override',
|
'deny_domain_override',
|
||||||
'dnssec_admins_only',
|
'dnssec_admins_only',
|
||||||
'enable_api_rr_history',
|
'enable_api_rr_history',
|
||||||
@ -1532,253 +1544,34 @@ def has_an_auth_method(local_db_enabled=None,
|
|||||||
oidc_oauth_enabled = Setting().get('oidc_oauth_enabled')
|
oidc_oauth_enabled = Setting().get('oidc_oauth_enabled')
|
||||||
if azure_oauth_enabled is None:
|
if azure_oauth_enabled is None:
|
||||||
azure_oauth_enabled = Setting().get('azure_oauth_enabled')
|
azure_oauth_enabled = Setting().get('azure_oauth_enabled')
|
||||||
return local_db_enabled or ldap_enabled or google_oauth_enabled or github_oauth_enabled or oidc_oauth_enabled or azure_oauth_enabled
|
return local_db_enabled or ldap_enabled or google_oauth_enabled or github_oauth_enabled or oidc_oauth_enabled \
|
||||||
|
or azure_oauth_enabled
|
||||||
|
|
||||||
|
|
||||||
@admin_bp.route('/setting/authentication', methods=['GET', 'POST'])
|
@admin_bp.route('/setting/authentication', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_role_required
|
@admin_role_required
|
||||||
def setting_authentication():
|
def setting_authentication():
|
||||||
if request.method == 'GET':
|
|
||||||
return render_template('admin_setting_authentication.html')
|
return render_template('admin_setting_authentication.html')
|
||||||
elif request.method == 'POST':
|
|
||||||
conf_type = request.form.get('config_tab')
|
|
||||||
result = None
|
|
||||||
|
|
||||||
if conf_type == 'general':
|
|
||||||
local_db_enabled = True if request.form.get(
|
|
||||||
'local_db_enabled') else False
|
|
||||||
signup_enabled = True if request.form.get(
|
|
||||||
'signup_enabled', ) else False
|
|
||||||
|
|
||||||
if not has_an_auth_method(local_db_enabled=local_db_enabled):
|
@admin_bp.route('/setting/authentication/api', methods=['POST'])
|
||||||
result = {
|
@login_required
|
||||||
'status':
|
@admin_role_required
|
||||||
False,
|
def setting_authentication_api():
|
||||||
'msg':
|
result = {'status': 1, 'messages': [], 'data': {}}
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set('local_db_enabled', local_db_enabled)
|
|
||||||
Setting().set('signup_enabled', signup_enabled)
|
|
||||||
result = {'status': True, 'msg': 'Saved successfully'}
|
|
||||||
elif conf_type == 'ldap':
|
|
||||||
ldap_enabled = True if request.form.get('ldap_enabled') else False
|
|
||||||
|
|
||||||
if not has_an_auth_method(ldap_enabled=ldap_enabled):
|
if request.form.get('commit') == '1':
|
||||||
result = {
|
model = Setting()
|
||||||
'status':
|
data = json.loads(request.form.get('data'))
|
||||||
False,
|
|
||||||
'msg':
|
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set('ldap_enabled', ldap_enabled)
|
|
||||||
Setting().set('ldap_type', request.form.get('ldap_type'))
|
|
||||||
Setting().set('ldap_uri', request.form.get('ldap_uri'))
|
|
||||||
Setting().set('ldap_base_dn', request.form.get('ldap_base_dn'))
|
|
||||||
Setting().set('ldap_admin_username',
|
|
||||||
request.form.get('ldap_admin_username'))
|
|
||||||
Setting().set('ldap_admin_password',
|
|
||||||
request.form.get('ldap_admin_password'))
|
|
||||||
Setting().set('ldap_filter_basic',
|
|
||||||
request.form.get('ldap_filter_basic'))
|
|
||||||
Setting().set('ldap_filter_group',
|
|
||||||
request.form.get('ldap_filter_group'))
|
|
||||||
Setting().set('ldap_filter_username',
|
|
||||||
request.form.get('ldap_filter_username'))
|
|
||||||
Setting().set('ldap_filter_groupname',
|
|
||||||
request.form.get('ldap_filter_groupname'))
|
|
||||||
Setting().set(
|
|
||||||
'ldap_sg_enabled', True
|
|
||||||
if request.form.get('ldap_sg_enabled') == 'ON' else False)
|
|
||||||
Setting().set('ldap_admin_group',
|
|
||||||
request.form.get('ldap_admin_group'))
|
|
||||||
Setting().set('ldap_operator_group',
|
|
||||||
request.form.get('ldap_operator_group'))
|
|
||||||
Setting().set('ldap_user_group',
|
|
||||||
request.form.get('ldap_user_group'))
|
|
||||||
Setting().set('ldap_domain', request.form.get('ldap_domain'))
|
|
||||||
Setting().set(
|
|
||||||
'autoprovisioning', True
|
|
||||||
if request.form.get('autoprovisioning') == 'ON' else False)
|
|
||||||
Setting().set('autoprovisioning_attribute',
|
|
||||||
request.form.get('autoprovisioning_attribute'))
|
|
||||||
|
|
||||||
if request.form.get('autoprovisioning') == 'ON':
|
for key, value in data.items():
|
||||||
if validateURN(request.form.get('urn_value')):
|
if key in model.groups['authentication']:
|
||||||
Setting().set('urn_value',
|
model.set(key, value)
|
||||||
request.form.get('urn_value'))
|
|
||||||
else:
|
|
||||||
return render_template('admin_setting_authentication.html',
|
|
||||||
error="Invalid urn")
|
|
||||||
else:
|
|
||||||
Setting().set('urn_value',
|
|
||||||
request.form.get('urn_value'))
|
|
||||||
|
|
||||||
Setting().set('purge', True
|
result['data'] = Setting().get_group('authentication')
|
||||||
if request.form.get('purge') == 'ON' else False)
|
|
||||||
|
|
||||||
result = {'status': True, 'msg': 'Saved successfully'}
|
return result
|
||||||
elif conf_type == 'google':
|
|
||||||
google_oauth_enabled = True if request.form.get(
|
|
||||||
'google_oauth_enabled') else False
|
|
||||||
if not has_an_auth_method(google_oauth_enabled=google_oauth_enabled):
|
|
||||||
result = {
|
|
||||||
'status':
|
|
||||||
False,
|
|
||||||
'msg':
|
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set('google_oauth_enabled', google_oauth_enabled)
|
|
||||||
Setting().set('google_oauth_client_id',
|
|
||||||
request.form.get('google_oauth_client_id'))
|
|
||||||
Setting().set('google_oauth_client_secret',
|
|
||||||
request.form.get('google_oauth_client_secret'))
|
|
||||||
Setting().set('google_token_url',
|
|
||||||
request.form.get('google_token_url'))
|
|
||||||
Setting().set('google_oauth_scope',
|
|
||||||
request.form.get('google_oauth_scope'))
|
|
||||||
Setting().set('google_authorize_url',
|
|
||||||
request.form.get('google_authorize_url'))
|
|
||||||
Setting().set('google_base_url',
|
|
||||||
request.form.get('google_base_url'))
|
|
||||||
result = {
|
|
||||||
'status': True,
|
|
||||||
'msg':
|
|
||||||
'Saved successfully. Please reload PDA to take effect.'
|
|
||||||
}
|
|
||||||
elif conf_type == 'github':
|
|
||||||
github_oauth_enabled = True if request.form.get(
|
|
||||||
'github_oauth_enabled') else False
|
|
||||||
if not has_an_auth_method(github_oauth_enabled=github_oauth_enabled):
|
|
||||||
result = {
|
|
||||||
'status':
|
|
||||||
False,
|
|
||||||
'msg':
|
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set('github_oauth_enabled', github_oauth_enabled)
|
|
||||||
Setting().set('github_oauth_key',
|
|
||||||
request.form.get('github_oauth_key'))
|
|
||||||
Setting().set('github_oauth_secret',
|
|
||||||
request.form.get('github_oauth_secret'))
|
|
||||||
Setting().set('github_oauth_scope',
|
|
||||||
request.form.get('github_oauth_scope'))
|
|
||||||
Setting().set('github_oauth_api_url',
|
|
||||||
request.form.get('github_oauth_api_url'))
|
|
||||||
Setting().set('github_oauth_token_url',
|
|
||||||
request.form.get('github_oauth_token_url'))
|
|
||||||
Setting().set('github_oauth_authorize_url',
|
|
||||||
request.form.get('github_oauth_authorize_url'))
|
|
||||||
result = {
|
|
||||||
'status': True,
|
|
||||||
'msg':
|
|
||||||
'Saved successfully. Please reload PDA to take effect.'
|
|
||||||
}
|
|
||||||
elif conf_type == 'azure':
|
|
||||||
azure_oauth_enabled = True if request.form.get(
|
|
||||||
'azure_oauth_enabled') else False
|
|
||||||
if not has_an_auth_method(azure_oauth_enabled=azure_oauth_enabled):
|
|
||||||
result = {
|
|
||||||
'status':
|
|
||||||
False,
|
|
||||||
'msg':
|
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set('azure_oauth_enabled', azure_oauth_enabled)
|
|
||||||
Setting().set('azure_oauth_key',
|
|
||||||
request.form.get('azure_oauth_key'))
|
|
||||||
Setting().set('azure_oauth_secret',
|
|
||||||
request.form.get('azure_oauth_secret'))
|
|
||||||
Setting().set('azure_oauth_scope',
|
|
||||||
request.form.get('azure_oauth_scope'))
|
|
||||||
Setting().set('azure_oauth_api_url',
|
|
||||||
request.form.get('azure_oauth_api_url'))
|
|
||||||
Setting().set('azure_oauth_token_url',
|
|
||||||
request.form.get('azure_oauth_token_url'))
|
|
||||||
Setting().set('azure_oauth_authorize_url',
|
|
||||||
request.form.get('azure_oauth_authorize_url'))
|
|
||||||
Setting().set(
|
|
||||||
'azure_sg_enabled', True
|
|
||||||
if request.form.get('azure_sg_enabled') == 'ON' else False)
|
|
||||||
Setting().set('azure_admin_group',
|
|
||||||
request.form.get('azure_admin_group'))
|
|
||||||
Setting().set('azure_operator_group',
|
|
||||||
request.form.get('azure_operator_group'))
|
|
||||||
Setting().set('azure_user_group',
|
|
||||||
request.form.get('azure_user_group'))
|
|
||||||
Setting().set(
|
|
||||||
'azure_group_accounts_enabled', True
|
|
||||||
if request.form.get('azure_group_accounts_enabled') == 'ON' else False)
|
|
||||||
Setting().set('azure_group_accounts_name',
|
|
||||||
request.form.get('azure_group_accounts_name'))
|
|
||||||
Setting().set('azure_group_accounts_name_re',
|
|
||||||
request.form.get('azure_group_accounts_name_re'))
|
|
||||||
Setting().set('azure_group_accounts_description',
|
|
||||||
request.form.get('azure_group_accounts_description'))
|
|
||||||
Setting().set('azure_group_accounts_description_re',
|
|
||||||
request.form.get('azure_group_accounts_description_re'))
|
|
||||||
result = {
|
|
||||||
'status': True,
|
|
||||||
'msg':
|
|
||||||
'Saved successfully. Please reload PDA to take effect.'
|
|
||||||
}
|
|
||||||
elif conf_type == 'oidc':
|
|
||||||
oidc_oauth_enabled = True if request.form.get(
|
|
||||||
'oidc_oauth_enabled') else False
|
|
||||||
if not has_an_auth_method(oidc_oauth_enabled=oidc_oauth_enabled):
|
|
||||||
result = {
|
|
||||||
'status':
|
|
||||||
False,
|
|
||||||
'msg':
|
|
||||||
'Must have at least one authentication method enabled.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
Setting().set(
|
|
||||||
'oidc_oauth_enabled',
|
|
||||||
True if request.form.get('oidc_oauth_enabled') else False)
|
|
||||||
Setting().set('oidc_oauth_key',
|
|
||||||
request.form.get('oidc_oauth_key'))
|
|
||||||
Setting().set('oidc_oauth_secret',
|
|
||||||
request.form.get('oidc_oauth_secret'))
|
|
||||||
Setting().set('oidc_oauth_scope',
|
|
||||||
request.form.get('oidc_oauth_scope'))
|
|
||||||
Setting().set('oidc_oauth_api_url',
|
|
||||||
request.form.get('oidc_oauth_api_url'))
|
|
||||||
Setting().set('oidc_oauth_token_url',
|
|
||||||
request.form.get('oidc_oauth_token_url'))
|
|
||||||
Setting().set('oidc_oauth_authorize_url',
|
|
||||||
request.form.get('oidc_oauth_authorize_url'))
|
|
||||||
Setting().set('oidc_oauth_metadata_url',
|
|
||||||
request.form.get('oidc_oauth_metadata_url'))
|
|
||||||
Setting().set('oidc_oauth_logout_url',
|
|
||||||
request.form.get('oidc_oauth_logout_url'))
|
|
||||||
Setting().set('oidc_oauth_username',
|
|
||||||
request.form.get('oidc_oauth_username'))
|
|
||||||
Setting().set('oidc_oauth_firstname',
|
|
||||||
request.form.get('oidc_oauth_firstname'))
|
|
||||||
Setting().set('oidc_oauth_last_name',
|
|
||||||
request.form.get('oidc_oauth_last_name'))
|
|
||||||
Setting().set('oidc_oauth_email',
|
|
||||||
request.form.get('oidc_oauth_email'))
|
|
||||||
Setting().set('oidc_oauth_account_name_property',
|
|
||||||
request.form.get('oidc_oauth_account_name_property'))
|
|
||||||
Setting().set('oidc_oauth_account_description_property',
|
|
||||||
request.form.get('oidc_oauth_account_description_property'))
|
|
||||||
result = {
|
|
||||||
'status': True,
|
|
||||||
'msg':
|
|
||||||
'Saved successfully. Please reload PDA to take effect.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return abort(400)
|
|
||||||
|
|
||||||
return render_template('admin_setting_authentication.html',
|
|
||||||
result=result)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_bp.route('/templates', methods=['GET', 'POST'])
|
@admin_bp.route('/templates', methods=['GET', 'POST'])
|
||||||
@ -1815,7 +1608,7 @@ def create_template():
|
|||||||
t = DomainTemplate(name=name, description=description)
|
t = DomainTemplate(name=name, description=description)
|
||||||
result = t.create()
|
result = t.create()
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
history = History(msg='Add domain template {0}'.format(name),
|
history = History(msg='Add zone template {0}'.format(name),
|
||||||
detail=json.dumps({
|
detail=json.dumps({
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description
|
'description': description
|
||||||
@ -1828,7 +1621,7 @@ def create_template():
|
|||||||
return redirect(url_for('admin.create_template'))
|
return redirect(url_for('admin.create_template'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot create domain template. Error: {0}'.format(e))
|
'Cannot create zone template. Error: {0}'.format(e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
@ -1862,7 +1655,7 @@ def create_template_from_zone():
|
|||||||
t = DomainTemplate(name=name, description=description)
|
t = DomainTemplate(name=name, description=description)
|
||||||
result = t.create()
|
result = t.create()
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
history = History(msg='Add domain template {0}'.format(name),
|
history = History(msg='Add zone template {0}'.format(name),
|
||||||
detail=json.dumps({
|
detail=json.dumps({
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description
|
'description': description
|
||||||
@ -1870,7 +1663,7 @@ def create_template_from_zone():
|
|||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
|
|
||||||
# After creating the domain in Domain Template in the,
|
# After creating the zone in Zone Template in the,
|
||||||
# local DB. We add records into it Record Template.
|
# local DB. We add records into it Record Template.
|
||||||
records = []
|
records = []
|
||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
@ -1899,7 +1692,7 @@ def create_template_from_zone():
|
|||||||
'msg': result['msg']
|
'msg': result['msg']
|
||||||
}), 200)
|
}), 200)
|
||||||
else:
|
else:
|
||||||
# Revert the domain template (remove it)
|
# Revert the zone template (remove it)
|
||||||
# ff we cannot add records.
|
# ff we cannot add records.
|
||||||
t.delete_template()
|
t.delete_template()
|
||||||
return make_response(
|
return make_response(
|
||||||
@ -1956,7 +1749,7 @@ def edit_template(template):
|
|||||||
ttl_options=ttl_options)
|
ttl_options=ttl_options)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot open domain template page. DETAIL: {0}'.format(e))
|
'Cannot open zone template page. DETAIL: {0}'.format(e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
abort(500)
|
abort(500)
|
||||||
return redirect(url_for('admin.templates'))
|
return redirect(url_for('admin.templates'))
|
||||||
@ -1994,7 +1787,7 @@ def apply_records(template):
|
|||||||
jdata.pop('_csrf_token',
|
jdata.pop('_csrf_token',
|
||||||
None) # don't store csrf token in the history.
|
None) # don't store csrf token in the history.
|
||||||
history = History(
|
history = History(
|
||||||
msg='Apply domain template record changes to domain template {0}'
|
msg='Apply zone template record changes to zone template {0}'
|
||||||
.format(template),
|
.format(template),
|
||||||
detail=json.dumps(jdata),
|
detail=json.dumps(jdata),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
@ -2025,7 +1818,7 @@ def delete_template(template):
|
|||||||
result = t.delete_template()
|
result = t.delete_template()
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
history = History(
|
history = History(
|
||||||
msg='Deleted domain template {0}'.format(template),
|
msg='Deleted zone template {0}'.format(template),
|
||||||
detail=json.dumps({'name': template}),
|
detail=json.dumps({'name': template}),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
@ -2129,3 +1922,10 @@ def validateURN(value):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def safe_cast(val, to_type, default=None):
|
||||||
|
try:
|
||||||
|
return to_type(val)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
@ -48,6 +48,12 @@ user_detailed_schema = UserDetailedSchema()
|
|||||||
account_schema = AccountSchema(many=True)
|
account_schema = AccountSchema(many=True)
|
||||||
account_single_schema = AccountSchema()
|
account_single_schema = AccountSchema()
|
||||||
|
|
||||||
|
def is_custom_header_api():
|
||||||
|
custom_header_setting = Setting().get('custom_history_header')
|
||||||
|
if custom_header_setting != '' and custom_header_setting in request.headers:
|
||||||
|
return request.headers[custom_header_setting]
|
||||||
|
else:
|
||||||
|
return g.apikey.description
|
||||||
|
|
||||||
def get_user_domains():
|
def get_user_domains():
|
||||||
domains = db.session.query(Domain) \
|
domains = db.session.query(Domain) \
|
||||||
@ -205,7 +211,7 @@ def api_login_create_zone():
|
|||||||
accept='application/json; q=1',
|
accept='application/json; q=1',
|
||||||
verify=Setting().get('verify_ssl_connections'))
|
verify=Setting().get('verify_ssl_connections'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error("Cannot create domain. Error: {}".format(e))
|
current_app.logger.error("Cannot create zone. Error: {}".format(e))
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
if resp.status_code == 201:
|
if resp.status_code == 201:
|
||||||
@ -216,7 +222,7 @@ def api_login_create_zone():
|
|||||||
domain.update()
|
domain.update()
|
||||||
domain_id = domain.get_id_by_name(data['name'].rstrip('.'))
|
domain_id = domain.get_id_by_name(data['name'].rstrip('.'))
|
||||||
|
|
||||||
history = History(msg='Add domain {0}'.format(
|
history = History(msg='Add zone {0}'.format(
|
||||||
data['name'].rstrip('.')),
|
data['name'].rstrip('.')),
|
||||||
detail=json.dumps(data),
|
detail=json.dumps(data),
|
||||||
created_by=current_user.username,
|
created_by=current_user.username,
|
||||||
@ -224,7 +230,7 @@ def api_login_create_zone():
|
|||||||
history.add()
|
history.add()
|
||||||
|
|
||||||
if current_user.role.name not in ['Administrator', 'Operator']:
|
if current_user.role.name not in ['Administrator', 'Operator']:
|
||||||
current_app.logger.debug("User is ordinary user, assigning created domain")
|
current_app.logger.debug("User is ordinary user, assigning created zone")
|
||||||
domain = Domain(name=data['name'].rstrip('.'))
|
domain = Domain(name=data['name'].rstrip('.'))
|
||||||
domain.update()
|
domain.update()
|
||||||
domain.grant_privileges([current_user.id])
|
domain.grant_privileges([current_user.id])
|
||||||
@ -290,7 +296,7 @@ def api_login_delete_zone(domain_name):
|
|||||||
domain_id = domain.get_id_by_name(domain_name)
|
domain_id = domain.get_id_by_name(domain_name)
|
||||||
domain.update()
|
domain.update()
|
||||||
|
|
||||||
history = History(msg='Delete domain {0}'.format(
|
history = History(msg='Delete zone {0}'.format(
|
||||||
utils.pretty_domain_name(domain_name)),
|
utils.pretty_domain_name(domain_name)),
|
||||||
detail='',
|
detail='',
|
||||||
created_by=current_user.username,
|
created_by=current_user.username,
|
||||||
@ -341,13 +347,13 @@ def api_generate_apikey():
|
|||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
if role_name == 'User' and len(domains) == 0 and len(accounts) == 0:
|
if role_name == 'User' and len(domains) == 0 and len(accounts) == 0:
|
||||||
current_app.logger.error("Apikey with User role must have domains or accounts")
|
current_app.logger.error("Apikey with User role must have zones or accounts")
|
||||||
raise ApiKeyNotUsable()
|
raise ApiKeyNotUsable()
|
||||||
|
|
||||||
if role_name == 'User' and len(domains) > 0:
|
if role_name == 'User' and len(domains) > 0:
|
||||||
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
||||||
if len(domain_obj_list) == 0:
|
if len(domain_obj_list) == 0:
|
||||||
msg = "One of supplied domains does not exist"
|
msg = "One of supplied zones does not exist"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainNotExists(message=msg)
|
raise DomainNotExists(message=msg)
|
||||||
|
|
||||||
@ -377,13 +383,13 @@ def api_generate_apikey():
|
|||||||
domain_list = [item.name for item in domain_obj_list]
|
domain_list = [item.name for item in domain_obj_list]
|
||||||
user_domain_list = [item.name for item in user_domain_obj_list]
|
user_domain_list = [item.name for item in user_domain_obj_list]
|
||||||
|
|
||||||
current_app.logger.debug("Input domain list: {0}".format(domain_list))
|
current_app.logger.debug("Input zone list: {0}".format(domain_list))
|
||||||
current_app.logger.debug("User domain list: {0}".format(user_domain_list))
|
current_app.logger.debug("User zone list: {0}".format(user_domain_list))
|
||||||
|
|
||||||
inter = set(domain_list).intersection(set(user_domain_list))
|
inter = set(domain_list).intersection(set(user_domain_list))
|
||||||
|
|
||||||
if not (len(inter) == len(domain_list)):
|
if not (len(inter) == len(domain_list)):
|
||||||
msg = "You don't have access to one of domains"
|
msg = "You don't have access to one of zones"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainAccessForbidden(message=msg)
|
raise DomainAccessForbidden(message=msg)
|
||||||
|
|
||||||
@ -411,7 +417,7 @@ def api_get_apikeys(domain_name):
|
|||||||
|
|
||||||
if current_user.role.name not in ['Administrator', 'Operator']:
|
if current_user.role.name not in ['Administrator', 'Operator']:
|
||||||
if domain_name:
|
if domain_name:
|
||||||
msg = "Check if domain {0} exists and is allowed for user.".format(
|
msg = "Check if zone {0} exists and is allowed for user.".format(
|
||||||
domain_name)
|
domain_name)
|
||||||
current_app.logger.debug(msg)
|
current_app.logger.debug(msg)
|
||||||
apikeys = get_user_apikeys(domain_name)
|
apikeys = get_user_apikeys(domain_name)
|
||||||
@ -421,7 +427,7 @@ def api_get_apikeys(domain_name):
|
|||||||
|
|
||||||
current_app.logger.debug(apikey_schema.dump(apikeys))
|
current_app.logger.debug(apikey_schema.dump(apikeys))
|
||||||
else:
|
else:
|
||||||
msg_str = "Getting all allowed domains for user {0}"
|
msg_str = "Getting all allowed zones for user {0}"
|
||||||
msg = msg_str.format(current_user.username)
|
msg = msg_str.format(current_user.username)
|
||||||
current_app.logger.debug(msg)
|
current_app.logger.debug(msg)
|
||||||
|
|
||||||
@ -432,7 +438,7 @@ def api_get_apikeys(domain_name):
|
|||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
abort(500)
|
abort(500)
|
||||||
else:
|
else:
|
||||||
current_app.logger.debug("Getting all domains for administrative user")
|
current_app.logger.debug("Getting all zones for administrative user")
|
||||||
try:
|
try:
|
||||||
apikeys = ApiKey.query.all()
|
apikeys = ApiKey.query.all()
|
||||||
current_app.logger.debug(apikey_schema.dump(apikeys))
|
current_app.logger.debug(apikey_schema.dump(apikeys))
|
||||||
@ -482,7 +488,7 @@ def api_delete_apikey(apikey_id):
|
|||||||
inter = set(apikey_domains_list).intersection(set(user_domains_list))
|
inter = set(apikey_domains_list).intersection(set(user_domains_list))
|
||||||
|
|
||||||
if not (len(inter) == len(apikey_domains_list)):
|
if not (len(inter) == len(apikey_domains_list)):
|
||||||
msg = "You don't have access to some domains apikey belongs to"
|
msg = "You don't have access to some zones apikey belongs to"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainAccessForbidden(message=msg)
|
raise DomainAccessForbidden(message=msg)
|
||||||
|
|
||||||
@ -552,7 +558,7 @@ def api_update_apikey(apikey_id):
|
|||||||
if domains is not None:
|
if domains is not None:
|
||||||
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
||||||
if len(domain_obj_list) != len(domains):
|
if len(domain_obj_list) != len(domains):
|
||||||
msg = "One of supplied domains does not exist"
|
msg = "One of supplied zones does not exist"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainNotExists(message=msg)
|
raise DomainNotExists(message=msg)
|
||||||
|
|
||||||
@ -572,12 +578,12 @@ def api_update_apikey(apikey_id):
|
|||||||
target_accounts = current_accounts
|
target_accounts = current_accounts
|
||||||
|
|
||||||
if len(target_domains) == 0 and len(target_accounts) == 0:
|
if len(target_domains) == 0 and len(target_accounts) == 0:
|
||||||
current_app.logger.error("Apikey with User role must have domains or accounts")
|
current_app.logger.error("Apikey with User role must have zones or accounts")
|
||||||
raise ApiKeyNotUsable()
|
raise ApiKeyNotUsable()
|
||||||
|
|
||||||
if domains is not None and set(domains) == set(current_domains):
|
if domains is not None and set(domains) == set(current_domains):
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
"Domains are the same, apikey domains won't be updated")
|
"Zones are the same, apikey zones won't be updated")
|
||||||
domains = None
|
domains = None
|
||||||
|
|
||||||
if accounts is not None and set(accounts) == set(current_accounts):
|
if accounts is not None and set(accounts) == set(current_accounts):
|
||||||
@ -604,19 +610,19 @@ def api_update_apikey(apikey_id):
|
|||||||
domain_list = [item.name for item in domain_obj_list]
|
domain_list = [item.name for item in domain_obj_list]
|
||||||
user_domain_list = [item.name for item in user_domain_obj_list]
|
user_domain_list = [item.name for item in user_domain_obj_list]
|
||||||
|
|
||||||
current_app.logger.debug("Input domain list: {0}".format(domain_list))
|
current_app.logger.debug("Input zone list: {0}".format(domain_list))
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
"User domain list: {0}".format(user_domain_list))
|
"User zone list: {0}".format(user_domain_list))
|
||||||
|
|
||||||
inter = set(domain_list).intersection(set(user_domain_list))
|
inter = set(domain_list).intersection(set(user_domain_list))
|
||||||
|
|
||||||
if not (len(inter) == len(domain_list)):
|
if not (len(inter) == len(domain_list)):
|
||||||
msg = "You don't have access to one of domains"
|
msg = "You don't have access to one of zones"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainAccessForbidden(message=msg)
|
raise DomainAccessForbidden(message=msg)
|
||||||
|
|
||||||
if apikey_id not in apikeys_ids:
|
if apikey_id not in apikeys_ids:
|
||||||
msg = 'Apikey does not belong to domain to which user has access'
|
msg = 'Apikey does not belong to zone to which user has access'
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainAccessForbidden()
|
raise DomainAccessForbidden()
|
||||||
|
|
||||||
@ -960,9 +966,9 @@ def api_delete_account(account_id):
|
|||||||
# Remove account association from domains first
|
# Remove account association from domains first
|
||||||
if len(account.domains) > 0:
|
if len(account.domains) > 0:
|
||||||
for domain in account.domains:
|
for domain in account.domains:
|
||||||
current_app.logger.info(f"Disassociating domain {domain.name} with {account.name}")
|
current_app.logger.info(f"Disassociating zone {domain.name} with {account.name}")
|
||||||
Domain(name=domain.name).assoc_account(None, update=False)
|
Domain(name=domain.name).assoc_account(None, update=False)
|
||||||
current_app.logger.info("Syncing all domains")
|
current_app.logger.info("Syncing all zones")
|
||||||
Domain().update()
|
Domain().update()
|
||||||
|
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
@ -1104,31 +1110,32 @@ def api_zone_forward(server_id, zone_id):
|
|||||||
domain = Domain()
|
domain = Domain()
|
||||||
domain.update()
|
domain.update()
|
||||||
status = resp.status_code
|
status = resp.status_code
|
||||||
|
created_by_value=is_custom_header_api()
|
||||||
if 200 <= status < 300:
|
if 200 <= status < 300:
|
||||||
current_app.logger.debug("Request to powerdns API successful")
|
current_app.logger.debug("Request to powerdns API successful")
|
||||||
if Setting().get('enable_api_rr_history'):
|
if Setting().get('enable_api_rr_history'):
|
||||||
if request.method in ['POST', 'PATCH']:
|
if request.method in ['POST', 'PATCH']:
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
history = History(
|
history = History(
|
||||||
msg='Apply record changes to domain {0}'.format(zone_id.rstrip('.')),
|
msg='Apply record changes to zone {0}'.format(zone_id.rstrip('.')),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'domain': zone_id.rstrip('.'),
|
'domain': zone_id.rstrip('.'),
|
||||||
'add_rrsets': list(filter(lambda r: r['changetype'] == "REPLACE", data['rrsets'])),
|
'add_rrsets': list(filter(lambda r: r['changetype'] == "REPLACE", data['rrsets'])),
|
||||||
'del_rrsets': list(filter(lambda r: r['changetype'] == "DELETE", data['rrsets']))
|
'del_rrsets': list(filter(lambda r: r['changetype'] == "DELETE", data['rrsets']))
|
||||||
}),
|
}),
|
||||||
created_by=g.apikey.description,
|
created_by=created_by_value,
|
||||||
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
||||||
history.add()
|
history.add()
|
||||||
elif request.method == 'DELETE':
|
elif request.method == 'DELETE':
|
||||||
history = History(msg='Deleted zone {0}'.format(zone_id.rstrip('.')),
|
history = History(msg='Deleted zone {0}'.format(zone_id.rstrip('.')),
|
||||||
detail='',
|
detail='',
|
||||||
created_by=g.apikey.description,
|
created_by=created_by_value,
|
||||||
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
||||||
history.add()
|
history.add()
|
||||||
elif request.method != 'GET':
|
elif request.method != 'GET':
|
||||||
history = History(msg='Updated zone {0}'.format(zone_id.rstrip('.')),
|
history = History(msg='Updated zone {0}'.format(zone_id.rstrip('.')),
|
||||||
detail='',
|
detail='',
|
||||||
created_by=g.apikey.description,
|
created_by=created_by_value,
|
||||||
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
domain_id=Domain().get_id_by_name(zone_id.rstrip('.')))
|
||||||
history.add()
|
history.add()
|
||||||
return resp.content, resp.status_code, resp.headers.items()
|
return resp.content, resp.status_code, resp.headers.items()
|
||||||
@ -1152,21 +1159,22 @@ def api_create_zone(server_id):
|
|||||||
|
|
||||||
if resp.status_code == 201:
|
if resp.status_code == 201:
|
||||||
current_app.logger.debug("Request to powerdns API successful")
|
current_app.logger.debug("Request to powerdns API successful")
|
||||||
|
created_by_value=is_custom_header_api()
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
|
|
||||||
if g.apikey.role.name not in ['Administrator', 'Operator']:
|
if g.apikey.role.name not in ['Administrator', 'Operator']:
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
"Apikey is user key, assigning created domain")
|
"Apikey is user key, assigning created zone")
|
||||||
domain = Domain(name=data['name'].rstrip('.'))
|
domain = Domain(name=data['name'].rstrip('.'))
|
||||||
g.apikey.domains.append(domain)
|
g.apikey.domains.append(domain)
|
||||||
|
|
||||||
domain = Domain()
|
domain = Domain()
|
||||||
domain.update()
|
domain.update()
|
||||||
|
|
||||||
history = History(msg='Add domain {0}'.format(
|
history = History(msg='Add zone {0}'.format(
|
||||||
data['name'].rstrip('.')),
|
data['name'].rstrip('.')),
|
||||||
detail=json.dumps(data),
|
detail=json.dumps(data),
|
||||||
created_by=g.apikey.description,
|
created_by=created_by_value,
|
||||||
domain_id=domain.get_id_by_name(data['name'].rstrip('.')))
|
domain_id=domain.get_id_by_name(data['name'].rstrip('.')))
|
||||||
history.add()
|
history.add()
|
||||||
|
|
||||||
@ -1190,7 +1198,7 @@ def api_get_zones(server_id):
|
|||||||
|
|
||||||
accounts_domains = [d.name for a in g.apikey.accounts for d in a.domains]
|
accounts_domains = [d.name for a in g.apikey.accounts for d in a.domains]
|
||||||
allowed_domains = set(domain_list + accounts_domains)
|
allowed_domains = set(domain_list + accounts_domains)
|
||||||
current_app.logger.debug("Account domains: {}".format('/'.join(accounts_domains)))
|
current_app.logger.debug("Account zones: {}".format('/'.join(accounts_domains)))
|
||||||
content = json.dumps([i for i in json.loads(resp.content)
|
content = json.dumps([i for i in json.loads(resp.content)
|
||||||
if i['name'].rstrip('.') in allowed_domains])
|
if i['name'].rstrip('.') in allowed_domains])
|
||||||
return content, resp.status_code, resp.headers.items()
|
return content, resp.status_code, resp.headers.items()
|
||||||
@ -1228,14 +1236,14 @@ def health():
|
|||||||
domain_to_query = domain.query.first()
|
domain_to_query = domain.query.first()
|
||||||
|
|
||||||
if not domain_to_query:
|
if not domain_to_query:
|
||||||
current_app.logger.error("No domain found to query a health check")
|
current_app.logger.error("No zone found to query a health check")
|
||||||
return make_response("Unknown", 503)
|
return make_response("Unknown", 503)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
domain.get_domain_info(domain_to_query.name)
|
domain.get_domain_info(domain_to_query.name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Health Check - Failed to query authoritative server for domain {}".format(domain_to_query.name))
|
"Health Check - Failed to query authoritative server for zone {}".format(domain_to_query.name))
|
||||||
return make_response("Down", 503)
|
return make_response("Down", 503)
|
||||||
|
|
||||||
return make_response("Up", 200)
|
return make_response("Up", 200)
|
||||||
|
@ -60,15 +60,31 @@ def login_via_authorization_header_or_remote_user(request):
|
|||||||
# Try to login using Basic Authentication
|
# Try to login using Basic Authentication
|
||||||
auth_header = request.headers.get('Authorization')
|
auth_header = request.headers.get('Authorization')
|
||||||
if auth_header:
|
if auth_header:
|
||||||
|
|
||||||
|
if auth_header[:6] != "Basic ":
|
||||||
|
return None
|
||||||
|
|
||||||
auth_method = request.args.get('auth_method', 'LOCAL')
|
auth_method = request.args.get('auth_method', 'LOCAL')
|
||||||
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
|
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
|
||||||
auth_header = auth_header.replace('Basic ', '', 1)
|
|
||||||
|
# Remove "Basic " from the header value
|
||||||
|
auth_header = auth_header[6:]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
auth_header = str(base64.b64decode(auth_header), 'utf-8')
|
auth_header = str(base64.b64decode(auth_header), 'utf-8')
|
||||||
username, password = auth_header.split(":")
|
except (UnicodeDecodeError, TypeError) as e:
|
||||||
except TypeError as e:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# NK: We use auth_components here as we don't know if we'll have a :, we split it maximum 1 times to grab the
|
||||||
|
# username, the rest of the string would be the password.
|
||||||
|
auth_components = auth_header.split(':', maxsplit=1)
|
||||||
|
|
||||||
|
# If we don't have two auth components (username, password), we can return
|
||||||
|
if len(auth_components) != 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
(username, password) = auth_components
|
||||||
|
|
||||||
user = User(username=username,
|
user = User(username=username,
|
||||||
password=password,
|
password=password,
|
||||||
plain_text_password=password)
|
plain_text_password=password)
|
||||||
|
@ -141,7 +141,7 @@ def domains_custom(tab_id):
|
|||||||
filtered_count = domains.count()
|
filtered_count = domains.count()
|
||||||
|
|
||||||
start = int(request.args.get("start", 0))
|
start = int(request.args.get("start", 0))
|
||||||
length = min(int(request.args.get("length", 0)), 100)
|
length = min(int(request.args.get("length", 0)), max(100, int(Setting().get('default_domain_table_size'))))
|
||||||
|
|
||||||
if length != -1:
|
if length != -1:
|
||||||
domains = domains[start:start + length]
|
domains = domains[start:start + length]
|
||||||
@ -176,10 +176,10 @@ def dashboard():
|
|||||||
|
|
||||||
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
|
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
|
||||||
if not BG_DOMAIN_UPDATE:
|
if not BG_DOMAIN_UPDATE:
|
||||||
current_app.logger.info('Updating domains in foreground...')
|
current_app.logger.info('Updating zones in foreground...')
|
||||||
Domain().update()
|
Domain().update()
|
||||||
else:
|
else:
|
||||||
current_app.logger.info('Updating domains in background...')
|
current_app.logger.info('Updating zones in background...')
|
||||||
|
|
||||||
show_bg_domain_button = BG_DOMAIN_UPDATE
|
show_bg_domain_button = BG_DOMAIN_UPDATE
|
||||||
if BG_DOMAIN_UPDATE and current_user.role.name not in ['Administrator', 'Operator']:
|
if BG_DOMAIN_UPDATE and current_user.role.name not in ['Administrator', 'Operator']:
|
||||||
@ -196,7 +196,7 @@ def dashboard():
|
|||||||
@login_required
|
@login_required
|
||||||
@operator_role_required
|
@operator_role_required
|
||||||
def domains_updater():
|
def domains_updater():
|
||||||
current_app.logger.debug('Update domains in background')
|
current_app.logger.debug('Update zones in background')
|
||||||
d = Domain().update()
|
d = Domain().update()
|
||||||
|
|
||||||
response_data = {
|
response_data = {
|
||||||
|
@ -66,7 +66,7 @@ def domain(domain_name):
|
|||||||
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
||||||
|
|
||||||
# API server might be down, misconfigured
|
# API server might be down, misconfigured
|
||||||
if not rrsets and domain.type != 'Slave':
|
if not rrsets and domain.type != 'slave':
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
quick_edit = Setting().get('record_quick_edit')
|
quick_edit = Setting().get('record_quick_edit')
|
||||||
@ -178,7 +178,7 @@ def remove():
|
|||||||
if result['status'] == 'error':
|
if result['status'] == 'error':
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
history = History(msg='Delete domain {0}'.format(
|
history = History(msg='Delete zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
@ -206,7 +206,7 @@ def changelog(domain_name):
|
|||||||
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
||||||
|
|
||||||
# API server might be down, misconfigured
|
# API server might be down, misconfigured
|
||||||
if not rrsets and domain.type != 'Slave':
|
if not rrsets and domain.type != 'slave':
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
records_allow_to_edit = Setting().get_records_allow_to_edit()
|
records_allow_to_edit = Setting().get_records_allow_to_edit()
|
||||||
@ -294,7 +294,7 @@ def record_changelog(domain_name, record_name, record_type):
|
|||||||
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
current_app.logger.debug("Fetched rrsets: \n{}".format(pretty_json(rrsets)))
|
||||||
|
|
||||||
# API server might be down, misconfigured
|
# API server might be down, misconfigured
|
||||||
if not rrsets and domain.type != 'Slave':
|
if not rrsets and domain.type != 'slave':
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
# get all changelogs for this domain, in descening order
|
# get all changelogs for this domain, in descening order
|
||||||
@ -362,7 +362,7 @@ def add():
|
|||||||
if ' ' in domain_name or not domain_name or not domain_type:
|
if ' ' in domain_name or not domain_name or not domain_type:
|
||||||
return render_template(
|
return render_template(
|
||||||
'errors/400.html',
|
'errors/400.html',
|
||||||
msg="Please enter a valid domain name"), 400
|
msg="Please enter a valid zone name"), 400
|
||||||
|
|
||||||
if domain_name.endswith('.'):
|
if domain_name.endswith('.'):
|
||||||
domain_name = domain_name[:-1]
|
domain_name = domain_name[:-1]
|
||||||
@ -385,11 +385,11 @@ def add():
|
|||||||
try:
|
try:
|
||||||
domain_name = to_idna(domain_name, 'encode')
|
domain_name = to_idna(domain_name, 'encode')
|
||||||
except:
|
except:
|
||||||
current_app.logger.error("Cannot encode the domain name {}".format(domain_name))
|
current_app.logger.error("Cannot encode the zone name {}".format(domain_name))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return render_template(
|
return render_template(
|
||||||
'errors/400.html',
|
'errors/400.html',
|
||||||
msg="Please enter a valid domain name"), 400
|
msg="Please enter a valid zone name"), 400
|
||||||
|
|
||||||
if domain_type == 'slave':
|
if domain_type == 'slave':
|
||||||
if request.form.getlist('domain_master_address'):
|
if request.form.getlist('domain_master_address'):
|
||||||
@ -429,7 +429,7 @@ def add():
|
|||||||
else:
|
else:
|
||||||
accounts = current_user.get_accounts()
|
accounts = current_user.get_accounts()
|
||||||
|
|
||||||
msg = 'Domain already exists as a record under domain: {}'.format(upper_domain)
|
msg = 'Zone already exists as a record under zone: {}'.format(upper_domain)
|
||||||
|
|
||||||
return render_template('domain_add.html',
|
return render_template('domain_add.html',
|
||||||
domain_override_message=msg,
|
domain_override_message=msg,
|
||||||
@ -443,7 +443,7 @@ def add():
|
|||||||
account_name=account_name)
|
account_name=account_name)
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
domain_id = Domain().get_id_by_name(domain_name)
|
domain_id = Domain().get_id_by_name(domain_name)
|
||||||
history = History(msg='Add domain {0}'.format(
|
history = History(msg='Add zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'domain_type': domain_type,
|
'domain_type': domain_type,
|
||||||
@ -507,7 +507,7 @@ def add():
|
|||||||
return render_template('errors/400.html',
|
return render_template('errors/400.html',
|
||||||
msg=result['msg']), 400
|
msg=result['msg']), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Cannot add domain. Error: {0}'.format(e))
|
current_app.logger.error('Cannot add zone. Error: {0}'.format(e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ def delete(domain_name):
|
|||||||
if result['status'] == 'error':
|
if result['status'] == 'error':
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
history = History(msg='Delete domain {0}'.format(
|
history = History(msg='Delete zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
@ -560,13 +560,17 @@ def setting(domain_name):
|
|||||||
d = Domain(name=domain_name)
|
d = Domain(name=domain_name)
|
||||||
domain_user_ids = d.get_user()
|
domain_user_ids = d.get_user()
|
||||||
account = d.get_account()
|
account = d.get_account()
|
||||||
|
domain_info = d.get_domain_info(domain_name)
|
||||||
|
|
||||||
return render_template('domain_setting.html',
|
return render_template('domain_setting.html',
|
||||||
domain=domain,
|
domain=domain,
|
||||||
users=users,
|
users=users,
|
||||||
domain_user_ids=domain_user_ids,
|
domain_user_ids=domain_user_ids,
|
||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
domain_account=account)
|
domain_account=account,
|
||||||
|
zone_type=domain_info["kind"].lower(),
|
||||||
|
masters=','.join(domain_info["masters"]),
|
||||||
|
soa_edit_api=domain_info["soa_edit_api"].upper())
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# username in right column
|
# username in right column
|
||||||
@ -581,7 +585,7 @@ def setting(domain_name):
|
|||||||
d.grant_privileges(new_user_ids)
|
d.grant_privileges(new_user_ids)
|
||||||
|
|
||||||
history = History(
|
history = History(
|
||||||
msg='Change domain {0} access control'.format(
|
msg='Change zone {0} access control'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
detail=json.dumps({'user_has_access': new_user_list}),
|
detail=json.dumps({'user_has_access': new_user_list}),
|
||||||
created_by=current_user.username,
|
created_by=current_user.username,
|
||||||
@ -619,7 +623,7 @@ def change_type(domain_name):
|
|||||||
kind=domain_type,
|
kind=domain_type,
|
||||||
masters=domain_master_ips)
|
masters=domain_master_ips)
|
||||||
if status['status'] == 'ok':
|
if status['status'] == 'ok':
|
||||||
history = History(msg='Update type for domain {0}'.format(
|
history = History(msg='Update type for zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
detail=json.dumps({
|
detail=json.dumps({
|
||||||
"domain": domain_name,
|
"domain": domain_name,
|
||||||
@ -653,7 +657,7 @@ def change_soa_edit_api(domain_name):
|
|||||||
soa_edit_api=new_setting)
|
soa_edit_api=new_setting)
|
||||||
if status['status'] == 'ok':
|
if status['status'] == 'ok':
|
||||||
history = History(
|
history = History(
|
||||||
msg='Update soa_edit_api for domain {0}'.format(
|
msg='Update soa_edit_api for zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'domain': domain_name,
|
'domain': domain_name,
|
||||||
@ -697,7 +701,7 @@ def record_apply(domain_name):
|
|||||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||||
|
|
||||||
if domain:
|
if domain:
|
||||||
current_app.logger.debug('Current domain serial: {0}'.format(
|
current_app.logger.debug('Current zone serial: {0}'.format(
|
||||||
domain.serial))
|
domain.serial))
|
||||||
|
|
||||||
if int(submitted_serial) != domain.serial:
|
if int(submitted_serial) != domain.serial:
|
||||||
@ -714,14 +718,14 @@ def record_apply(domain_name):
|
|||||||
'status':
|
'status':
|
||||||
'error',
|
'error',
|
||||||
'msg':
|
'msg':
|
||||||
'Domain name {0} does not exist'.format(pretty_domain_name(domain_name))
|
'Zone name {0} does not exist'.format(pretty_domain_name(domain_name))
|
||||||
}), 404)
|
}), 404)
|
||||||
|
|
||||||
r = Record()
|
r = Record()
|
||||||
result = r.apply(domain_name, submitted_record)
|
result = r.apply(domain_name, submitted_record)
|
||||||
if result['status'] == 'ok':
|
if result['status'] == 'ok':
|
||||||
history = History(
|
history = History(
|
||||||
msg='Apply record changes to domain {0}'.format(pretty_domain_name(domain_name)),
|
msg='Apply record changes to zone {0}'.format(pretty_domain_name(domain_name)),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'domain': domain_name,
|
'domain': domain_name,
|
||||||
'add_rrsets': result['data'][0]['rrsets'],
|
'add_rrsets': result['data'][0]['rrsets'],
|
||||||
@ -733,7 +737,7 @@ def record_apply(domain_name):
|
|||||||
return make_response(jsonify(result), 200)
|
return make_response(jsonify(result), 200)
|
||||||
else:
|
else:
|
||||||
history = History(
|
history = History(
|
||||||
msg='Failed to apply record changes to domain {0}'.format(
|
msg='Failed to apply record changes to zone {0}'.format(
|
||||||
pretty_domain_name(domain_name)),
|
pretty_domain_name(domain_name)),
|
||||||
detail = json.dumps({
|
detail = json.dumps({
|
||||||
'domain': domain_name,
|
'domain': domain_name,
|
||||||
@ -760,7 +764,7 @@ def record_apply(domain_name):
|
|||||||
@can_access_domain
|
@can_access_domain
|
||||||
def record_update(domain_name):
|
def record_update(domain_name):
|
||||||
"""
|
"""
|
||||||
This route is used for domain work as Slave Zone only
|
This route is used for zone work as Slave Zone only
|
||||||
Pulling the records update from its Master
|
Pulling the records update from its Master
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -818,7 +822,7 @@ def dnssec_enable(domain_name):
|
|||||||
dnssec = domain.enable_domain_dnssec(domain_name)
|
dnssec = domain.enable_domain_dnssec(domain_name)
|
||||||
domain_object = Domain.query.filter(domain_name == Domain.name).first()
|
domain_object = Domain.query.filter(domain_name == Domain.name).first()
|
||||||
history = History(
|
history = History(
|
||||||
msg='DNSSEC was enabled for domain ' + domain_name ,
|
msg='DNSSEC was enabled for zone ' + domain_name ,
|
||||||
created_by=current_user.username,
|
created_by=current_user.username,
|
||||||
domain_id=domain_object.id)
|
domain_id=domain_object.id)
|
||||||
history.add()
|
history.add()
|
||||||
@ -837,7 +841,7 @@ def dnssec_disable(domain_name):
|
|||||||
domain.delete_dnssec_key(domain_name, key['id'])
|
domain.delete_dnssec_key(domain_name, key['id'])
|
||||||
domain_object = Domain.query.filter(domain_name == Domain.name).first()
|
domain_object = Domain.query.filter(domain_name == Domain.name).first()
|
||||||
history = History(
|
history = History(
|
||||||
msg='DNSSEC was disabled for domain ' + domain_name ,
|
msg='DNSSEC was disabled for zone ' + domain_name ,
|
||||||
created_by=current_user.username,
|
created_by=current_user.username,
|
||||||
domain_id=domain_object.id)
|
domain_id=domain_object.id)
|
||||||
history.add()
|
history.add()
|
||||||
@ -914,7 +918,7 @@ def admin_setdomainsetting(domain_name):
|
|||||||
}), 400)
|
}), 400)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
'Cannot change domain setting. Error: {0}'.format(e))
|
'Cannot change zone setting. Error: {0}'.format(e))
|
||||||
current_app.logger.debug(traceback.format_exc())
|
current_app.logger.debug(traceback.format_exc())
|
||||||
return make_response(
|
return make_response(
|
||||||
jsonify({
|
jsonify({
|
||||||
|
@ -5,6 +5,8 @@ import traceback
|
|||||||
import datetime
|
import datetime
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import base64
|
import base64
|
||||||
|
import string
|
||||||
|
from zxcvbn import zxcvbn
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
from yaml import Loader, load
|
from yaml import Loader, load
|
||||||
from flask import Blueprint, render_template, make_response, url_for, current_app, g, session, request, redirect, abort
|
from flask import Blueprint, render_template, make_response, url_for, current_app, g, session, request, redirect, abort
|
||||||
@ -43,6 +45,7 @@ index_bp = Blueprint('index',
|
|||||||
template_folder='templates',
|
template_folder='templates',
|
||||||
url_prefix='/')
|
url_prefix='/')
|
||||||
|
|
||||||
|
|
||||||
@index_bp.before_app_first_request
|
@index_bp.before_app_first_request
|
||||||
def register_modules():
|
def register_modules():
|
||||||
global google
|
global google
|
||||||
@ -96,7 +99,11 @@ def google_login():
|
|||||||
)
|
)
|
||||||
abort(400)
|
abort(400)
|
||||||
else:
|
else:
|
||||||
redirect_uri = url_for('google_authorized', _external=True)
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
redirect_uri = url_for('google_authorized', **params)
|
||||||
return google.authorize_redirect(redirect_uri)
|
return google.authorize_redirect(redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
@ -108,7 +115,11 @@ def github_login():
|
|||||||
)
|
)
|
||||||
abort(400)
|
abort(400)
|
||||||
else:
|
else:
|
||||||
redirect_uri = url_for('github_authorized', _external=True)
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
redirect_uri = url_for('github_authorized', **params)
|
||||||
return github.authorize_redirect(redirect_uri)
|
return github.authorize_redirect(redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
@ -120,9 +131,11 @@ def azure_login():
|
|||||||
)
|
)
|
||||||
abort(400)
|
abort(400)
|
||||||
else:
|
else:
|
||||||
redirect_uri = url_for('azure_authorized',
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
_external=True,
|
params = {'_external': True}
|
||||||
_scheme='https')
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
redirect_uri = url_for('azure_authorized', **params)
|
||||||
return azure.authorize_redirect(redirect_uri)
|
return azure.authorize_redirect(redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
@ -134,7 +147,11 @@ def oidc_login():
|
|||||||
)
|
)
|
||||||
abort(400)
|
abort(400)
|
||||||
else:
|
else:
|
||||||
redirect_uri = url_for('oidc_authorized', _external=True)
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
redirect_uri = url_for('oidc_authorized', **params)
|
||||||
return oidc.authorize_redirect(redirect_uri)
|
return oidc.authorize_redirect(redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
@ -147,18 +164,18 @@ def login():
|
|||||||
|
|
||||||
if 'google_token' in session:
|
if 'google_token' in session:
|
||||||
user_data = json.loads(google.get('userinfo').text)
|
user_data = json.loads(google.get('userinfo').text)
|
||||||
first_name = user_data['given_name']
|
google_first_name = user_data['given_name']
|
||||||
surname = user_data['family_name']
|
google_last_name = user_data['family_name']
|
||||||
email = user_data['email']
|
google_email = user_data['email']
|
||||||
user = User.query.filter_by(username=email).first()
|
user = User.query.filter_by(username=google_email).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
user = User.query.filter_by(email=email).first()
|
user = User.query.filter_by(email=google_email).first()
|
||||||
if not user:
|
if not user:
|
||||||
user = User(username=email,
|
user = User(username=google_email,
|
||||||
firstname=first_name,
|
firstname=google_first_name,
|
||||||
lastname=surname,
|
lastname=google_last_name,
|
||||||
plain_text_password=None,
|
plain_text_password=None,
|
||||||
email=email)
|
email=google_email)
|
||||||
|
|
||||||
result = user.create_local_user()
|
result = user.create_local_user()
|
||||||
if not result['status']:
|
if not result['status']:
|
||||||
@ -170,10 +187,18 @@ def login():
|
|||||||
return authenticate_user(user, 'Google OAuth')
|
return authenticate_user(user, 'Google OAuth')
|
||||||
|
|
||||||
if 'github_token' in session:
|
if 'github_token' in session:
|
||||||
me = json.loads(github.get('user').text)
|
user_data = json.loads(github.get('user').text)
|
||||||
github_username = me['login']
|
github_username = user_data['login']
|
||||||
github_name = me['name']
|
github_first_name = user_data['name']
|
||||||
github_email = me['email']
|
github_last_name = ''
|
||||||
|
github_email = user_data['email']
|
||||||
|
|
||||||
|
# If the user's full name from GitHub contains at least two words, use the first word as the first name and
|
||||||
|
# the rest as the last name.
|
||||||
|
github_name_parts = github_first_name.split(' ')
|
||||||
|
if len(github_name_parts) > 1:
|
||||||
|
github_first_name = github_name_parts[0]
|
||||||
|
github_last_name = ' '.join(github_name_parts[1:])
|
||||||
|
|
||||||
user = User.query.filter_by(username=github_username).first()
|
user = User.query.filter_by(username=github_username).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
@ -181,8 +206,8 @@ def login():
|
|||||||
if not user:
|
if not user:
|
||||||
user = User(username=github_username,
|
user = User(username=github_username,
|
||||||
plain_text_password=None,
|
plain_text_password=None,
|
||||||
firstname=github_name,
|
firstname=github_first_name,
|
||||||
lastname='',
|
lastname=github_last_name,
|
||||||
email=github_email)
|
email=github_email)
|
||||||
|
|
||||||
result = user.create_local_user()
|
result = user.create_local_user()
|
||||||
@ -197,7 +222,7 @@ def login():
|
|||||||
if 'azure_token' in session:
|
if 'azure_token' in session:
|
||||||
azure_info = azure.get('me?$select=displayName,givenName,id,mail,surname,userPrincipalName').text
|
azure_info = azure.get('me?$select=displayName,givenName,id,mail,surname,userPrincipalName').text
|
||||||
current_app.logger.info('Azure login returned: ' + azure_info)
|
current_app.logger.info('Azure login returned: ' + azure_info)
|
||||||
me = json.loads(azure_info)
|
user_data = json.loads(azure_info)
|
||||||
|
|
||||||
azure_info = azure.post('me/getMemberGroups',
|
azure_info = azure.post('me/getMemberGroups',
|
||||||
json={'securityEnabledOnly': False}).text
|
json={'securityEnabledOnly': False}).text
|
||||||
@ -209,15 +234,15 @@ def login():
|
|||||||
else:
|
else:
|
||||||
mygroups = []
|
mygroups = []
|
||||||
|
|
||||||
azure_username = me["userPrincipalName"]
|
azure_username = user_data["userPrincipalName"]
|
||||||
azure_givenname = me["givenName"]
|
azure_first_name = user_data["givenName"]
|
||||||
azure_familyname = me["surname"]
|
azure_last_name = user_data["surname"]
|
||||||
if "mail" in me:
|
if "mail" in user_data:
|
||||||
azure_email = me["mail"]
|
azure_email = user_data["mail"]
|
||||||
else:
|
else:
|
||||||
azure_email = ""
|
azure_email = ""
|
||||||
if not azure_email:
|
if not azure_email:
|
||||||
azure_email = me["userPrincipalName"]
|
azure_email = user_data["userPrincipalName"]
|
||||||
|
|
||||||
# Handle foreign principals such as guest users
|
# Handle foreign principals such as guest users
|
||||||
azure_email = re.sub(r"#.*$", "", azure_email)
|
azure_email = re.sub(r"#.*$", "", azure_email)
|
||||||
@ -227,8 +252,8 @@ def login():
|
|||||||
if not user:
|
if not user:
|
||||||
user = User(username=azure_username,
|
user = User(username=azure_username,
|
||||||
plain_text_password=None,
|
plain_text_password=None,
|
||||||
firstname=azure_givenname,
|
firstname=azure_first_name,
|
||||||
lastname=azure_familyname,
|
lastname=azure_last_name,
|
||||||
email=azure_email)
|
email=azure_email)
|
||||||
|
|
||||||
result = user.create_local_user()
|
result = user.create_local_user()
|
||||||
@ -367,23 +392,23 @@ def login():
|
|||||||
return authenticate_user(user, 'Azure OAuth')
|
return authenticate_user(user, 'Azure OAuth')
|
||||||
|
|
||||||
if 'oidc_token' in session:
|
if 'oidc_token' in session:
|
||||||
me = json.loads(oidc.get('userinfo').text)
|
user_data = json.loads(oidc.get('userinfo').text)
|
||||||
oidc_username = me[Setting().get('oidc_oauth_username')]
|
oidc_username = user_data[Setting().get('oidc_oauth_username')]
|
||||||
oidc_givenname = me[Setting().get('oidc_oauth_firstname')]
|
oidc_first_name = user_data[Setting().get('oidc_oauth_firstname')]
|
||||||
oidc_familyname = me[Setting().get('oidc_oauth_last_name')]
|
oidc_last_name = user_data[Setting().get('oidc_oauth_last_name')]
|
||||||
oidc_email = me[Setting().get('oidc_oauth_email')]
|
oidc_email = user_data[Setting().get('oidc_oauth_email')]
|
||||||
|
|
||||||
user = User.query.filter_by(username=oidc_username).first()
|
user = User.query.filter_by(username=oidc_username).first()
|
||||||
if not user:
|
if not user:
|
||||||
user = User(username=oidc_username,
|
user = User(username=oidc_username,
|
||||||
plain_text_password=None,
|
plain_text_password=None,
|
||||||
firstname=oidc_givenname,
|
firstname=oidc_first_name,
|
||||||
lastname=oidc_familyname,
|
lastname=oidc_last_name,
|
||||||
email=oidc_email)
|
email=oidc_email)
|
||||||
result = user.create_local_user()
|
result = user.create_local_user()
|
||||||
else:
|
else:
|
||||||
user.firstname = oidc_givenname
|
user.firstname = oidc_first_name
|
||||||
user.lastname = oidc_familyname
|
user.lastname = oidc_last_name
|
||||||
user.email = oidc_email
|
user.email = oidc_email
|
||||||
user.plain_text_password = None
|
user.plain_text_password = None
|
||||||
result = user.update_local_user()
|
result = user.update_local_user()
|
||||||
@ -393,17 +418,19 @@ def login():
|
|||||||
return redirect(url_for('index.login'))
|
return redirect(url_for('index.login'))
|
||||||
|
|
||||||
# This checks if the account_name_property and account_description property were included in settings.
|
# This checks if the account_name_property and account_description property were included in settings.
|
||||||
if Setting().get('oidc_oauth_account_name_property') and Setting().get('oidc_oauth_account_description_property'):
|
if Setting().get('oidc_oauth_account_name_property') and Setting().get(
|
||||||
|
'oidc_oauth_account_description_property'):
|
||||||
|
|
||||||
# Gets the name_property and description_property.
|
# Gets the name_property and description_property.
|
||||||
name_prop = Setting().get('oidc_oauth_account_name_property')
|
name_prop = Setting().get('oidc_oauth_account_name_property')
|
||||||
desc_prop = Setting().get('oidc_oauth_account_description_property')
|
desc_prop = Setting().get('oidc_oauth_account_description_property')
|
||||||
|
|
||||||
account_to_add = []
|
account_to_add = []
|
||||||
#If the name_property and desc_property exist in me (A variable that contains all the userinfo from the IdP).
|
# If the name_property and desc_property exist in me (A variable that contains all the userinfo from the
|
||||||
if name_prop in me and desc_prop in me:
|
# IdP).
|
||||||
accounts_name_prop = [me[name_prop]] if type(me[name_prop]) is not list else me[name_prop]
|
if name_prop in user_data and desc_prop in user_data:
|
||||||
accounts_desc_prop = [me[desc_prop]] if type(me[desc_prop]) is not list else me[desc_prop]
|
accounts_name_prop = [user_data[name_prop]] if type(user_data[name_prop]) is not list else user_data[name_prop]
|
||||||
|
accounts_desc_prop = [user_data[desc_prop]] if type(user_data[desc_prop]) is not list else user_data[desc_prop]
|
||||||
|
|
||||||
# Run on all groups the user is in by the index num.
|
# Run on all groups the user is in by the index num.
|
||||||
for i in range(len(accounts_name_prop)):
|
for i in range(len(accounts_name_prop)):
|
||||||
@ -499,7 +526,8 @@ def login():
|
|||||||
if checkForPDAEntries(Entitlements, urn_value):
|
if checkForPDAEntries(Entitlements, urn_value):
|
||||||
user.updateUser(Entitlements)
|
user.updateUser(Entitlements)
|
||||||
else:
|
else:
|
||||||
current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix')
|
current_app.logger.warning(
|
||||||
|
'Not a single powerdns-admin record was found, possibly a typo in the prefix')
|
||||||
if Setting().get('purge'):
|
if Setting().get('purge'):
|
||||||
user.set_role("User")
|
user.set_role("User")
|
||||||
user.revoke_privilege(True)
|
user.revoke_privilege(True)
|
||||||
@ -507,6 +535,7 @@ def login():
|
|||||||
|
|
||||||
return authenticate_user(user, auth_method, remember_me)
|
return authenticate_user(user, auth_method, remember_me)
|
||||||
|
|
||||||
|
|
||||||
def checkForPDAEntries(Entitlements, urn_value):
|
def checkForPDAEntries(Entitlements, urn_value):
|
||||||
"""
|
"""
|
||||||
Run through every record located in the ldap attribute given and determine if there are any valid powerdns-admin records
|
Run through every record located in the ldap attribute given and determine if there are any valid powerdns-admin records
|
||||||
@ -524,9 +553,10 @@ def clear_session():
|
|||||||
session.pop('user_id', None)
|
session.pop('user_id', None)
|
||||||
session.pop('github_token', None)
|
session.pop('github_token', None)
|
||||||
session.pop('google_token', None)
|
session.pop('google_token', None)
|
||||||
|
session.pop('azure_token', None)
|
||||||
|
session.pop('oidc_token', None)
|
||||||
session.pop('authentication_type', None)
|
session.pop('authentication_type', None)
|
||||||
session.pop('remote_user', None)
|
session.pop('remote_user', None)
|
||||||
session.clear()
|
|
||||||
logout_user()
|
logout_user()
|
||||||
|
|
||||||
|
|
||||||
@ -560,6 +590,7 @@ def signin_history(username, authenticator, success):
|
|||||||
}),
|
}),
|
||||||
created_by='System').add()
|
created_by='System').add()
|
||||||
|
|
||||||
|
|
||||||
# Get a list of Azure security groups the user is a member of
|
# Get a list of Azure security groups the user is a member of
|
||||||
def get_azure_groups(uri):
|
def get_azure_groups(uri):
|
||||||
azure_info = azure.get(uri).text
|
azure_info = azure.get(uri).text
|
||||||
@ -575,30 +606,33 @@ def get_azure_groups(uri):
|
|||||||
mygroups = []
|
mygroups = []
|
||||||
return mygroups
|
return mygroups
|
||||||
|
|
||||||
|
|
||||||
# Handle user login, write history and, if set, handle showing the register_otp QR code.
|
# Handle user login, write history and, if set, handle showing the register_otp QR code.
|
||||||
# if Setting for OTP on first login is enabled, and OTP field is also enabled,
|
# if Setting for OTP on first login is enabled, and OTP field is also enabled,
|
||||||
# but user isn't using it yet, enable OTP, get QR code and display it, logging the user out.
|
# but user isn't using it yet, enable OTP, get QR code and display it, logging the user out.
|
||||||
def authenticate_user(user, authenticator, remember=False):
|
def authenticate_user(user, authenticator, remember=False):
|
||||||
login_user(user, remember=remember)
|
login_user(user, remember=remember)
|
||||||
signin_history(user.username, authenticator, True)
|
signin_history(user.username, authenticator, True)
|
||||||
if Setting().get('otp_force') and Setting().get('otp_field_enabled') and not user.otp_secret and session['authentication_type'] not in ['OAuth']:
|
if Setting().get('otp_force') and Setting().get('otp_field_enabled') and not user.otp_secret \
|
||||||
|
and session['authentication_type'] not in ['OAuth']:
|
||||||
user.update_profile(enable_otp=True)
|
user.update_profile(enable_otp=True)
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
prepare_welcome_user(user_id)
|
prepare_welcome_user(user_id)
|
||||||
return redirect(url_for('index.welcome'))
|
return redirect(url_for('index.welcome'))
|
||||||
return redirect(url_for('index.login'))
|
return redirect(url_for('index.login'))
|
||||||
|
|
||||||
|
|
||||||
# Prepare user to enter /welcome screen, otherwise they won't have permission to do so
|
# Prepare user to enter /welcome screen, otherwise they won't have permission to do so
|
||||||
def prepare_welcome_user(user_id):
|
def prepare_welcome_user(user_id):
|
||||||
logout_user()
|
logout_user()
|
||||||
session['welcome_user_id'] = user_id
|
session['welcome_user_id'] = user_id
|
||||||
|
|
||||||
|
|
||||||
@index_bp.route('/logout')
|
@index_bp.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
if current_app.config.get(
|
if current_app.config.get(
|
||||||
'SAML_ENABLED'
|
'SAML_ENABLED'
|
||||||
) and 'samlSessionIndex' in session and current_app.config.get(
|
) and 'samlSessionIndex' in session and current_app.config.get('SAML_LOGOUT'):
|
||||||
'SAML_LOGOUT'):
|
|
||||||
req = saml.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = saml.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
if current_app.config.get('SAML_LOGOUT_URL'):
|
if current_app.config.get('SAML_LOGOUT_URL'):
|
||||||
@ -649,6 +683,94 @@ def logout():
|
|||||||
return redirect(redirect_uri)
|
return redirect(redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
|
def password_policy_check(user, password):
|
||||||
|
def check_policy(chars, user_password, setting):
|
||||||
|
setting_as_int = int(Setting().get(setting))
|
||||||
|
test_string = user_password
|
||||||
|
for c in chars:
|
||||||
|
test_string = test_string.replace(c, '')
|
||||||
|
return (setting_as_int, len(user_password) - len(test_string))
|
||||||
|
|
||||||
|
def matches_policy(item, policy_fails):
|
||||||
|
return "*" if item in policy_fails else ""
|
||||||
|
|
||||||
|
policy = []
|
||||||
|
policy_fails = {}
|
||||||
|
|
||||||
|
# If either policy is enabled check basics first ... this is obvious!
|
||||||
|
if Setting().get('pwd_enforce_characters') or Setting().get('pwd_enforce_complexity'):
|
||||||
|
# Cannot contain username
|
||||||
|
if user.username in password:
|
||||||
|
policy_fails["username"] = True
|
||||||
|
policy.append(f"{matches_policy('username', policy_fails)}cannot contain username")
|
||||||
|
|
||||||
|
# Cannot contain password
|
||||||
|
if user.firstname in password:
|
||||||
|
policy_fails["firstname"] = True
|
||||||
|
policy.append(f"{matches_policy('firstname', policy_fails)}cannot contain firstname")
|
||||||
|
|
||||||
|
# Cannot contain lastname
|
||||||
|
if user.lastname in password:
|
||||||
|
policy_fails["lastname"] = True
|
||||||
|
policy.append(f"{matches_policy('lastname', policy_fails)}cannot contain lastname")
|
||||||
|
|
||||||
|
# Cannot contain email
|
||||||
|
if user.email in password:
|
||||||
|
policy_fails["email"] = True
|
||||||
|
policy.append(f"{matches_policy('email', policy_fails)}cannot contain email")
|
||||||
|
|
||||||
|
# Check if we're enforcing character requirements
|
||||||
|
if Setting().get('pwd_enforce_characters'):
|
||||||
|
# Length
|
||||||
|
pwd_min_len_setting = int(Setting().get('pwd_min_len'))
|
||||||
|
pwd_len = len(password)
|
||||||
|
if pwd_len < pwd_min_len_setting:
|
||||||
|
policy_fails["length"] = True
|
||||||
|
policy.append(f"{matches_policy('length', policy_fails)}length={pwd_len}/{pwd_min_len_setting}")
|
||||||
|
# Digits
|
||||||
|
(pwd_min_digits_setting, pwd_digits) = check_policy(string.digits, password, 'pwd_min_digits')
|
||||||
|
if pwd_digits < pwd_min_digits_setting:
|
||||||
|
policy_fails["digits"] = True
|
||||||
|
policy.append(f"{matches_policy('digits', policy_fails)}digits={pwd_digits}/{pwd_min_digits_setting}")
|
||||||
|
# Lowercase
|
||||||
|
(pwd_min_lowercase_setting, pwd_lowercase) = check_policy(string.digits, password, 'pwd_min_lowercase')
|
||||||
|
if pwd_lowercase < pwd_min_lowercase_setting:
|
||||||
|
policy_fails["lowercase"] = True
|
||||||
|
policy.append(
|
||||||
|
f"{matches_policy('lowercase', policy_fails)}lowercase={pwd_lowercase}/{pwd_min_lowercase_setting}")
|
||||||
|
# Uppercase
|
||||||
|
(pwd_min_uppercase_setting, pwd_uppercase) = check_policy(string.digits, password, 'pwd_min_uppercase')
|
||||||
|
if pwd_uppercase < pwd_min_uppercase_setting:
|
||||||
|
policy_fails["uppercase"] = True
|
||||||
|
policy.append(
|
||||||
|
f"{matches_policy('uppercase', policy_fails)}uppercase={pwd_uppercase}/{pwd_min_uppercase_setting}")
|
||||||
|
# Special
|
||||||
|
(pwd_min_special_setting, pwd_special) = check_policy(string.digits, password, 'pwd_min_special')
|
||||||
|
if pwd_special < pwd_min_special_setting:
|
||||||
|
policy_fails["special"] = True
|
||||||
|
policy.append(f"{matches_policy('special', policy_fails)}special={pwd_special}/{pwd_min_special_setting}")
|
||||||
|
|
||||||
|
if Setting().get('pwd_enforce_complexity'):
|
||||||
|
# Complexity checking
|
||||||
|
zxcvbn_inputs = []
|
||||||
|
for input in (user.firstname, user.lastname, user.username, user.email):
|
||||||
|
if len(input):
|
||||||
|
zxcvbn_inputs.append(input)
|
||||||
|
|
||||||
|
result = zxcvbn(password, user_inputs=zxcvbn_inputs)
|
||||||
|
pwd_min_complexity_setting = int(Setting().get('pwd_min_complexity'))
|
||||||
|
pwd_complexity = result['guesses_log10']
|
||||||
|
if pwd_complexity < pwd_min_complexity_setting:
|
||||||
|
policy_fails["complexity"] = True
|
||||||
|
policy.append(
|
||||||
|
f"{matches_policy('complexity', policy_fails)}complexity={pwd_complexity:.0f}/{pwd_min_complexity_setting}")
|
||||||
|
|
||||||
|
policy_str = {"password": f"Fails policy: {', '.join(policy)}. Items prefixed with '*' failed."}
|
||||||
|
|
||||||
|
# NK: the first item in the tuple indicates a PASS, so, we check for any True's and negate that
|
||||||
|
return (not any(policy_fails.values()), policy_str)
|
||||||
|
|
||||||
|
|
||||||
@index_bp.route('/register', methods=['GET', 'POST'])
|
@index_bp.route('/register', methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
CAPTCHA_ENABLE = current_app.config.get('CAPTCHA_ENABLE')
|
CAPTCHA_ENABLE = current_app.config.get('CAPTCHA_ENABLE')
|
||||||
@ -688,7 +810,8 @@ def register():
|
|||||||
|
|
||||||
if not captcha.validate():
|
if not captcha.validate():
|
||||||
return render_template(
|
return render_template(
|
||||||
'register.html', error='Invalid CAPTCHA answer', error_messages=error_messages, captcha_enable=CAPTCHA_ENABLE)
|
'register.html', error='Invalid CAPTCHA answer', error_messages=error_messages,
|
||||||
|
captcha_enable=CAPTCHA_ENABLE)
|
||||||
|
|
||||||
if error_messages:
|
if error_messages:
|
||||||
return render_template('register.html', error_messages=error_messages, captcha_enable=CAPTCHA_ENABLE)
|
return render_template('register.html', error_messages=error_messages, captcha_enable=CAPTCHA_ENABLE)
|
||||||
@ -700,6 +823,10 @@ def register():
|
|||||||
email=email
|
email=email
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(password_policy_pass, password_policy) = password_policy_check(user, password)
|
||||||
|
if not password_policy_pass:
|
||||||
|
return render_template('register.html', error_messages=password_policy, captcha_enable=CAPTCHA_ENABLE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = user.create_local_user()
|
result = user.create_local_user()
|
||||||
if result and result['status']:
|
if result and result['status']:
|
||||||
@ -736,12 +863,15 @@ def welcome():
|
|||||||
if otp_token and otp_token.isdigit():
|
if otp_token and otp_token.isdigit():
|
||||||
good_token = user.verify_totp(otp_token)
|
good_token = user.verify_totp(otp_token)
|
||||||
if not good_token:
|
if not good_token:
|
||||||
return render_template('register_otp.html', qrcode_image=encoded_img_data.decode(), user=user, error="Invalid token")
|
return render_template('register_otp.html', qrcode_image=encoded_img_data.decode(), user=user,
|
||||||
|
error="Invalid token")
|
||||||
else:
|
else:
|
||||||
return render_template('register_otp.html', qrcode_image=encoded_img_data.decode(), user=user, error="Token required")
|
return render_template('register_otp.html', qrcode_image=encoded_img_data.decode(), user=user,
|
||||||
|
error="Token required")
|
||||||
session.pop('welcome_user_id')
|
session.pop('welcome_user_id')
|
||||||
return redirect(url_for('index.index'))
|
return redirect(url_for('index.index'))
|
||||||
|
|
||||||
|
|
||||||
@index_bp.route('/confirm/<token>', methods=['GET'])
|
@index_bp.route('/confirm/<token>', methods=['GET'])
|
||||||
def confirm_email(token):
|
def confirm_email(token):
|
||||||
email = confirm_token(token)
|
email = confirm_token(token)
|
||||||
@ -1146,6 +1276,7 @@ def uplift_to_admin(user):
|
|||||||
created_by='SAML Assertion')
|
created_by='SAML Assertion')
|
||||||
history.add()
|
history.add()
|
||||||
|
|
||||||
|
|
||||||
def uplift_to_operator(user):
|
def uplift_to_operator(user):
|
||||||
if user.role.name != 'Operator':
|
if user.role.name != 'Operator':
|
||||||
user.role_id = Role.query.filter_by(name='Operator').first().id
|
user.role_id = Role.query.filter_by(name='Operator').first().id
|
||||||
|
@ -9,6 +9,8 @@ from flask_login import current_user, login_required, login_manager
|
|||||||
|
|
||||||
from ..models.user import User, Anonymous
|
from ..models.user import User, Anonymous
|
||||||
from ..models.setting import Setting
|
from ..models.setting import Setting
|
||||||
|
from .index import password_policy_check
|
||||||
|
|
||||||
|
|
||||||
user_bp = Blueprint('user',
|
user_bp = Blueprint('user',
|
||||||
__name__,
|
__name__,
|
||||||
@ -79,12 +81,23 @@ def profile():
|
|||||||
.format(current_user.username)
|
.format(current_user.username)
|
||||||
}), 400)
|
}), 400)
|
||||||
|
|
||||||
|
(password_policy_pass, password_policy) = password_policy_check(current_user.get_user_info_by_username(), new_password)
|
||||||
|
if not password_policy_pass:
|
||||||
|
if request.data:
|
||||||
|
return make_response(
|
||||||
|
jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'msg': password_policy['password'],
|
||||||
|
}), 400)
|
||||||
|
return render_template('user_profile.html', error_messages=password_policy)
|
||||||
|
|
||||||
user = User(username=current_user.username,
|
user = User(username=current_user.username,
|
||||||
plain_text_password=new_password,
|
plain_text_password=new_password,
|
||||||
firstname=firstname,
|
firstname=firstname,
|
||||||
lastname=lastname,
|
lastname=lastname,
|
||||||
email=email,
|
email=email,
|
||||||
reload_info=False)
|
reload_info=False)
|
||||||
|
|
||||||
user.update_profile()
|
user.update_profile()
|
||||||
|
|
||||||
return render_template('user_profile.html')
|
return render_template('user_profile.html')
|
||||||
|
@ -15,28 +15,40 @@ def azure_oauth():
|
|||||||
session['azure_token'] = token
|
session['azure_token'] = token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
authlib_params = {
|
||||||
|
'client_id': Setting().get('azure_oauth_key'),
|
||||||
|
'client_secret': Setting().get('azure_oauth_secret'),
|
||||||
|
'api_base_url': Setting().get('azure_oauth_api_url'),
|
||||||
|
'request_token_url': None,
|
||||||
|
'client_kwargs': {'scope': Setting().get('azure_oauth_scope')},
|
||||||
|
'fetch_token': fetch_azure_token,
|
||||||
|
}
|
||||||
|
|
||||||
|
server_metadata_url = Setting().get('azure_oauth_metadata_url')
|
||||||
|
|
||||||
|
if isinstance(server_metadata_url, str) and len(server_metadata_url.strip()) > 0:
|
||||||
|
authlib_params['server_metadata_url'] = server_metadata_url
|
||||||
|
else:
|
||||||
|
authlib_params['access_token_url'] = Setting().get('azure_oauth_token_url')
|
||||||
|
authlib_params['authorize_url'] = Setting().get('azure_oauth_authorize_url')
|
||||||
|
|
||||||
azure = authlib_oauth_client.register(
|
azure = authlib_oauth_client.register(
|
||||||
'azure',
|
'azure',
|
||||||
client_id=Setting().get('azure_oauth_key'),
|
**authlib_params
|
||||||
client_secret=Setting().get('azure_oauth_secret'),
|
|
||||||
api_base_url=Setting().get('azure_oauth_api_url'),
|
|
||||||
request_token_url=None,
|
|
||||||
access_token_url=Setting().get('azure_oauth_token_url'),
|
|
||||||
authorize_url=Setting().get('azure_oauth_authorize_url'),
|
|
||||||
client_kwargs={'scope': Setting().get('azure_oauth_scope')},
|
|
||||||
fetch_token=fetch_azure_token,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@current_app.route('/azure/authorized')
|
@current_app.route('/azure/authorized')
|
||||||
def azure_authorized():
|
def azure_authorized():
|
||||||
session['azure_oauthredir'] = url_for('.azure_authorized',
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
_external=True,
|
params = {'_external': True}
|
||||||
_scheme='https')
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
session['azure_oauthredir'] = url_for('.azure_authorized', **params)
|
||||||
token = azure.authorize_access_token()
|
token = azure.authorize_access_token()
|
||||||
if token is None:
|
if token is None:
|
||||||
return 'Access denied: reason=%s error=%s' % (
|
return 'Access denied: reason=%s error=%s' % (
|
||||||
request.args['error'], request.args['error_description'])
|
request.args['error'], request.args['error_description'])
|
||||||
session['azure_token'] = (token)
|
session['azure_token'] = (token)
|
||||||
return redirect(url_for('index.login', _external=True, _scheme='https'))
|
return redirect(url_for('index.login', **params))
|
||||||
|
|
||||||
return azure
|
return azure
|
||||||
|
@ -15,28 +15,42 @@ def github_oauth():
|
|||||||
session['github_token'] = token
|
session['github_token'] = token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
authlib_params = {
|
||||||
|
'client_id': Setting().get('github_oauth_key'),
|
||||||
|
'client_secret': Setting().get('github_oauth_secret'),
|
||||||
|
'request_token_params': {'scope': Setting().get('github_oauth_scope')},
|
||||||
|
'api_base_url': Setting().get('github_oauth_api_url'),
|
||||||
|
'request_token_url': None,
|
||||||
|
'client_kwargs': {'scope': Setting().get('github_oauth_scope')},
|
||||||
|
'fetch_token': fetch_github_token,
|
||||||
|
'update_token': update_token
|
||||||
|
}
|
||||||
|
|
||||||
|
server_metadata_url = Setting().get('github_oauth_metadata_url')
|
||||||
|
|
||||||
|
if isinstance(server_metadata_url, str) and len(server_metadata_url.strip()) > 0:
|
||||||
|
authlib_params['server_metadata_url'] = server_metadata_url
|
||||||
|
else:
|
||||||
|
authlib_params['access_token_url'] = Setting().get('github_oauth_token_url')
|
||||||
|
authlib_params['authorize_url'] = Setting().get('github_oauth_authorize_url')
|
||||||
|
|
||||||
github = authlib_oauth_client.register(
|
github = authlib_oauth_client.register(
|
||||||
'github',
|
'github',
|
||||||
client_id=Setting().get('github_oauth_key'),
|
**authlib_params
|
||||||
client_secret=Setting().get('github_oauth_secret'),
|
)
|
||||||
request_token_params={'scope': Setting().get('github_oauth_scope')},
|
|
||||||
api_base_url=Setting().get('github_oauth_api_url'),
|
|
||||||
request_token_url=None,
|
|
||||||
access_token_url=Setting().get('github_oauth_token_url'),
|
|
||||||
authorize_url=Setting().get('github_oauth_authorize_url'),
|
|
||||||
client_kwargs={'scope': Setting().get('github_oauth_scope')},
|
|
||||||
fetch_token=fetch_github_token,
|
|
||||||
update_token=update_token)
|
|
||||||
|
|
||||||
@current_app.route('/github/authorized')
|
@current_app.route('/github/authorized')
|
||||||
def github_authorized():
|
def github_authorized():
|
||||||
session['github_oauthredir'] = url_for('.github_authorized',
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
_external=True)
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
session['github_oauthredir'] = url_for('.github_authorized', **params)
|
||||||
token = github.authorize_access_token()
|
token = github.authorize_access_token()
|
||||||
if token is None:
|
if token is None:
|
||||||
return 'Access denied: reason=%s error=%s' % (
|
return 'Access denied: reason=%s error=%s' % (
|
||||||
request.args['error'], request.args['error_description'])
|
request.args['error'], request.args['error_description'])
|
||||||
session['github_token'] = (token)
|
session['github_token'] = token
|
||||||
return redirect(url_for('index.login'))
|
return redirect(url_for('index.login', **params))
|
||||||
|
|
||||||
return github
|
return github
|
||||||
|
@ -15,30 +15,43 @@ def google_oauth():
|
|||||||
session['google_token'] = token
|
session['google_token'] = token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
authlib_params = {
|
||||||
|
'client_id': Setting().get('google_oauth_client_id'),
|
||||||
|
'client_secret': Setting().get('google_oauth_client_secret'),
|
||||||
|
'api_base_url': Setting().get('google_base_url'),
|
||||||
|
'request_token_url': None,
|
||||||
|
'client_kwargs': {'scope': Setting().get('google_oauth_scope')},
|
||||||
|
'fetch_token': fetch_google_token,
|
||||||
|
'update_token': update_token
|
||||||
|
}
|
||||||
|
|
||||||
|
server_metadata_url = Setting().get('google_oauth_metadata_url')
|
||||||
|
|
||||||
|
if isinstance(server_metadata_url, str) and len(server_metadata_url.strip()) > 0:
|
||||||
|
authlib_params['server_metadata_url'] = server_metadata_url
|
||||||
|
else:
|
||||||
|
authlib_params['access_token_url'] = Setting().get('google_token_url')
|
||||||
|
authlib_params['authorize_url'] = Setting().get('google_authorize_url')
|
||||||
|
|
||||||
google = authlib_oauth_client.register(
|
google = authlib_oauth_client.register(
|
||||||
'google',
|
'google',
|
||||||
client_id=Setting().get('google_oauth_client_id'),
|
**authlib_params
|
||||||
client_secret=Setting().get('google_oauth_client_secret'),
|
)
|
||||||
api_base_url=Setting().get('google_base_url'),
|
|
||||||
request_token_url=None,
|
|
||||||
access_token_url=Setting().get('google_token_url'),
|
|
||||||
authorize_url=Setting().get('google_authorize_url'),
|
|
||||||
client_kwargs={'scope': Setting().get('google_oauth_scope')},
|
|
||||||
fetch_token=fetch_google_token,
|
|
||||||
update_token=update_token)
|
|
||||||
|
|
||||||
@current_app.route('/google/authorized')
|
@current_app.route('/google/authorized')
|
||||||
def google_authorized():
|
def google_authorized():
|
||||||
session['google_oauthredir'] = url_for(
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
'.google_authorized', _external=True)
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
session['google_oauthredir'] = url_for('.google_authorized', **params)
|
||||||
token = google.authorize_access_token()
|
token = google.authorize_access_token()
|
||||||
if token is None:
|
if token is None:
|
||||||
return 'Access denied: reason=%s error=%s' % (
|
return 'Access denied: reason=%s error=%s' % (
|
||||||
request.args['error_reason'],
|
request.args['error_reason'],
|
||||||
request.args['error_description']
|
request.args['error_description']
|
||||||
)
|
)
|
||||||
session['google_token'] = (token)
|
session['google_token'] = token
|
||||||
return redirect(url_for('index.login'))
|
return redirect(url_for('index.login', **params))
|
||||||
|
|
||||||
return google
|
return google
|
||||||
|
|
||||||
|
@ -15,28 +15,41 @@ def oidc_oauth():
|
|||||||
session['oidc_token'] = token
|
session['oidc_token'] = token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
authlib_params = {
|
||||||
|
'client_id': Setting().get('oidc_oauth_key'),
|
||||||
|
'client_secret': Setting().get('oidc_oauth_secret'),
|
||||||
|
'api_base_url': Setting().get('oidc_oauth_api_url'),
|
||||||
|
'request_token_url': None,
|
||||||
|
'client_kwargs': {'scope': Setting().get('oidc_oauth_scope')},
|
||||||
|
'fetch_token': fetch_oidc_token,
|
||||||
|
'update_token': update_token
|
||||||
|
}
|
||||||
|
|
||||||
|
server_metadata_url = Setting().get('oidc_oauth_metadata_url')
|
||||||
|
|
||||||
|
if isinstance(server_metadata_url, str) and len(server_metadata_url.strip()) > 0:
|
||||||
|
authlib_params['server_metadata_url'] = server_metadata_url
|
||||||
|
else:
|
||||||
|
authlib_params['access_token_url'] = Setting().get('oidc_oauth_token_url')
|
||||||
|
authlib_params['authorize_url'] = Setting().get('oidc_oauth_authorize_url')
|
||||||
|
|
||||||
oidc = authlib_oauth_client.register(
|
oidc = authlib_oauth_client.register(
|
||||||
'oidc',
|
'oidc',
|
||||||
client_id=Setting().get('oidc_oauth_key'),
|
**authlib_params
|
||||||
client_secret=Setting().get('oidc_oauth_secret'),
|
)
|
||||||
api_base_url=Setting().get('oidc_oauth_api_url'),
|
|
||||||
request_token_url=None,
|
|
||||||
access_token_url=Setting().get('oidc_oauth_token_url'),
|
|
||||||
authorize_url=Setting().get('oidc_oauth_authorize_url'),
|
|
||||||
server_metadata_url=Setting().get('oidc_oauth_metadata_url'),
|
|
||||||
client_kwargs={'scope': Setting().get('oidc_oauth_scope')},
|
|
||||||
fetch_token=fetch_oidc_token,
|
|
||||||
update_token=update_token)
|
|
||||||
|
|
||||||
@current_app.route('/oidc/authorized')
|
@current_app.route('/oidc/authorized')
|
||||||
def oidc_authorized():
|
def oidc_authorized():
|
||||||
session['oidc_oauthredir'] = url_for('.oidc_authorized',
|
use_ssl = current_app.config.get('SERVER_EXTERNAL_SSL')
|
||||||
_external=True)
|
params = {'_external': True}
|
||||||
|
if isinstance(use_ssl, bool):
|
||||||
|
params['_scheme'] = 'https' if use_ssl else 'http'
|
||||||
|
session['oidc_oauthredir'] = url_for('.oidc_authorized', **params)
|
||||||
token = oidc.authorize_access_token()
|
token = oidc.authorize_access_token()
|
||||||
if token is None:
|
if token is None:
|
||||||
return 'Access denied: reason=%s error=%s' % (
|
return 'Access denied: reason=%s error=%s' % (
|
||||||
request.args['error'], request.args['error_description'])
|
request.args['error'], request.args['error_description'])
|
||||||
session['oidc_token'] = (token)
|
session['oidc_token'] = token
|
||||||
return redirect(url_for('index.login'))
|
return redirect(url_for('index.login', **params))
|
||||||
|
|
||||||
return oidc
|
return oidc
|
@ -0,0 +1,801 @@
|
|||||||
|
let AuthenticationSettingsModel = function (user_data, api_url, csrf_token, selector) {
|
||||||
|
let self = this;
|
||||||
|
let target = null;
|
||||||
|
self.api_url = api_url;
|
||||||
|
self.csrf_token = csrf_token;
|
||||||
|
self.selector = selector;
|
||||||
|
self.loading = false;
|
||||||
|
self.saving = false;
|
||||||
|
self.saved = false;
|
||||||
|
self.save_failed = false;
|
||||||
|
self.messages = [];
|
||||||
|
self.messages_class = 'info';
|
||||||
|
self.tab_active = '';
|
||||||
|
self.tab_default = 'local';
|
||||||
|
|
||||||
|
let defaults = {
|
||||||
|
// Local Authentication Settings
|
||||||
|
local_db_enabled: true,
|
||||||
|
signup_enabled: true,
|
||||||
|
pwd_enforce_characters: 0,
|
||||||
|
pwd_min_len: 10,
|
||||||
|
pwd_min_lowercase: 3,
|
||||||
|
pwd_min_uppercase: 2,
|
||||||
|
pwd_min_digits: 2,
|
||||||
|
pwd_min_special: 1,
|
||||||
|
pwd_enforce_complexity: 0,
|
||||||
|
pwd_min_complexity: 11,
|
||||||
|
|
||||||
|
// LDAP Authentication Settings
|
||||||
|
ldap_enabled: false,
|
||||||
|
ldap_type: 'ldap',
|
||||||
|
ldap_uri: '',
|
||||||
|
ldap_base_dn: '',
|
||||||
|
ldap_admin_username: '',
|
||||||
|
ldap_admin_password: '',
|
||||||
|
ldap_domain: '',
|
||||||
|
ldap_filter_basic: '',
|
||||||
|
ldap_filter_username: '',
|
||||||
|
ldap_filter_group: '',
|
||||||
|
ldap_filter_groupname: '',
|
||||||
|
ldap_sg_enabled: false,
|
||||||
|
ldap_admin_group: '',
|
||||||
|
ldap_operator_group: '',
|
||||||
|
ldap_user_group: '',
|
||||||
|
autoprovisioning: false,
|
||||||
|
autoprovisioning_attribute: '',
|
||||||
|
urn_value: '',
|
||||||
|
purge: 0,
|
||||||
|
|
||||||
|
// Google OAuth2 Settings
|
||||||
|
google_oauth_enabled: false,
|
||||||
|
google_oauth_client_id: '',
|
||||||
|
google_oauth_client_secret: '',
|
||||||
|
google_oauth_scope: '',
|
||||||
|
google_base_url: '',
|
||||||
|
google_oauth_auto_configure: true,
|
||||||
|
google_oauth_metadata_url: '',
|
||||||
|
google_token_url: '',
|
||||||
|
google_authorize_url: '',
|
||||||
|
|
||||||
|
// GitHub OAuth2 Settings
|
||||||
|
github_oauth_enabled: false,
|
||||||
|
github_oauth_key: '',
|
||||||
|
github_oauth_secret: '',
|
||||||
|
github_oauth_scope: '',
|
||||||
|
github_oauth_api_url: '',
|
||||||
|
github_oauth_auto_configure: false,
|
||||||
|
github_oauth_metadata_url: '',
|
||||||
|
github_oauth_token_url: '',
|
||||||
|
github_oauth_authorize_url: '',
|
||||||
|
|
||||||
|
// Azure AD OAuth2 Settings
|
||||||
|
azure_oauth_enabled: false,
|
||||||
|
azure_oauth_key: '',
|
||||||
|
azure_oauth_secret: '',
|
||||||
|
azure_oauth_scope: '',
|
||||||
|
azure_oauth_api_url: '',
|
||||||
|
azure_oauth_auto_configure: true,
|
||||||
|
azure_oauth_metadata_url: '',
|
||||||
|
azure_oauth_token_url: '',
|
||||||
|
azure_oauth_authorize_url: '',
|
||||||
|
azure_sg_enabled: false,
|
||||||
|
azure_admin_group: '',
|
||||||
|
azure_operator_group: '',
|
||||||
|
azure_user_group: '',
|
||||||
|
azure_group_accounts_enabled: false,
|
||||||
|
azure_group_accounts_name: '',
|
||||||
|
azure_group_accounts_name_re: '',
|
||||||
|
azure_group_accounts_description: '',
|
||||||
|
azure_group_accounts_description_re: '',
|
||||||
|
|
||||||
|
// OIDC OAuth2 Settings
|
||||||
|
oidc_oauth_enabled: false,
|
||||||
|
oidc_oauth_key: '',
|
||||||
|
oidc_oauth_secret: '',
|
||||||
|
oidc_oauth_scope: '',
|
||||||
|
oidc_oauth_api_url: '',
|
||||||
|
oidc_oauth_auto_configure: true,
|
||||||
|
oidc_oauth_metadata_url: '',
|
||||||
|
oidc_oauth_token_url: '',
|
||||||
|
oidc_oauth_authorize_url: '',
|
||||||
|
oidc_oauth_logout_url: '',
|
||||||
|
oidc_oauth_username: '',
|
||||||
|
oidc_oauth_email: '',
|
||||||
|
oidc_oauth_firstname: '',
|
||||||
|
oidc_oauth_last_name: '',
|
||||||
|
oidc_oauth_account_name_property: '',
|
||||||
|
oidc_oauth_account_description_property: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init = function (autoload) {
|
||||||
|
self.loading = ko.observable(self.loading);
|
||||||
|
self.saving = ko.observable(self.saving);
|
||||||
|
self.saved = ko.observable(self.saved);
|
||||||
|
self.save_failed = ko.observable(self.save_failed);
|
||||||
|
self.messages = ko.observableArray(self.messages);
|
||||||
|
self.messages_class = ko.observable(self.messages_class);
|
||||||
|
self.tab_active = ko.observable(self.tab_active);
|
||||||
|
self.tab_default = ko.observable(self.tab_default);
|
||||||
|
self.update(user_data);
|
||||||
|
|
||||||
|
let el = null;
|
||||||
|
if (typeof selector !== 'undefined') {
|
||||||
|
el = $(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el !== null && el.length > 0) {
|
||||||
|
target = el;
|
||||||
|
ko.applyBindings(self, el[0]);
|
||||||
|
} else {
|
||||||
|
ko.applyBindings(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasHash()) {
|
||||||
|
self.activateTab(self.getHash());
|
||||||
|
} else {
|
||||||
|
self.activateDefaultTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setupListeners();
|
||||||
|
self.setupValidation();
|
||||||
|
|
||||||
|
if (autoload) {
|
||||||
|
self.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.load = function () {
|
||||||
|
self.loading(true);
|
||||||
|
$.ajax({
|
||||||
|
url: self.api_url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {_csrf_token: csrf_token},
|
||||||
|
dataType: 'json',
|
||||||
|
success: self.onDataLoaded
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.save = function () {
|
||||||
|
if (!target.valid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.saving(true);
|
||||||
|
$.ajax({
|
||||||
|
url: self.api_url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {_csrf_token: csrf_token, commit: 1, data: ko.toJSON(self)},
|
||||||
|
dataType: 'json',
|
||||||
|
success: self.onDataSaved
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update = function (instance) {
|
||||||
|
for (const [key, value] of Object.entries($.extend(defaults, instance))) {
|
||||||
|
if (ko.isObservable(self[key])) {
|
||||||
|
self[key](value);
|
||||||
|
} else {
|
||||||
|
self[key] = ko.observable(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setupListeners = function () {
|
||||||
|
if ('onhashchange' in window) {
|
||||||
|
$(window).bind('hashchange', self.onHashChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.destroyListeners = function () {
|
||||||
|
if ('onhashchange' in window) {
|
||||||
|
$(window).unbind('hashchange', self.onHashChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setupValidation = function () {
|
||||||
|
let uuidRegExp = /^([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})|[0-9]+$/i;
|
||||||
|
|
||||||
|
let footerErrorElements = [
|
||||||
|
'input#local_db_enabled',
|
||||||
|
];
|
||||||
|
|
||||||
|
let errorCheckSelectors = [
|
||||||
|
'input.error:not([disabled])',
|
||||||
|
'select.error:not([disabled])',
|
||||||
|
'textarea.error:not([disabled])',
|
||||||
|
];
|
||||||
|
|
||||||
|
let errorCheckQuery = errorCheckSelectors.join(',');
|
||||||
|
let tabs = target.find('.tab-content > *[data-tab]')
|
||||||
|
|
||||||
|
let onElementChanged = function (event) {
|
||||||
|
target.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth_enabled = function (value, element, params) {
|
||||||
|
let enabled = 0;
|
||||||
|
if (self.local_db_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.ldap_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.google_oauth_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.github_oauth_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.azure_oauth_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.oidc_oauth_enabled()) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
return enabled > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ldap_exclusive = function (value, element, params) {
|
||||||
|
let enabled = 0;
|
||||||
|
if (self.ldap_sg_enabled() === 1) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
if (self.autoprovisioning() === 1) {
|
||||||
|
enabled++;
|
||||||
|
}
|
||||||
|
return enabled < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = function (value, element, params) {
|
||||||
|
return uuidRegExp.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_enabled = function (element) {
|
||||||
|
return self.local_db_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let ldap_enabled = function (element) {
|
||||||
|
return self.ldap_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let google_oauth_enabled = function (element) {
|
||||||
|
return self.google_oauth_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let github_oauth_enabled = function (element) {
|
||||||
|
return self.github_oauth_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let azure_oauth_enabled = function (element) {
|
||||||
|
return self.azure_oauth_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let oidc_oauth_enabled = function (element) {
|
||||||
|
return self.oidc_oauth_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
let enforce_characters = function (element) {
|
||||||
|
return self.local_db_enabled() === 1 && self.pwd_enforce_characters() === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let enforce_complexity = function (element) {
|
||||||
|
return self.local_db_enabled() === 1 && self.pwd_enforce_complexity() === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ldap_type_openldap = function (element) {
|
||||||
|
return self.ldap_enabled() && self.ldap_type() === 'ldap';
|
||||||
|
};
|
||||||
|
|
||||||
|
let ldap_type_ad = function (element) {
|
||||||
|
return self.ldap_enabled() && self.ldap_type() === 'ad';
|
||||||
|
};
|
||||||
|
|
||||||
|
let ldap_sg_enabled = function (element) {
|
||||||
|
return self.ldap_enabled() === 1 && self.ldap_sg_enabled() === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ldap_ap_enabled = function (element) {
|
||||||
|
return self.ldap_enabled() === 1 && self.autoprovisioning() === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let azure_gs_enabled = function (element) {
|
||||||
|
return self.azure_oauth_enabled() === 1 && self.azure_sg_enabled() === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let azure_gas_enabled = function (element) {
|
||||||
|
return self.azure_oauth_enabled() && self.azure_group_accounts_enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
let google_oauth_auto_configure_enabled = function (element) {
|
||||||
|
return self.google_oauth_enabled() && self.google_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let google_oauth_auto_configure_disabled = function (element) {
|
||||||
|
return self.google_oauth_enabled() && !self.google_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let github_oauth_auto_configure_enabled = function (element) {
|
||||||
|
return self.github_oauth_enabled() && self.github_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let github_oauth_auto_configure_disabled = function (element) {
|
||||||
|
return self.github_oauth_enabled() && !self.github_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let azure_oauth_auto_configure_enabled = function (element) {
|
||||||
|
return self.azure_oauth_enabled() && self.azure_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let azure_oauth_auto_configure_disabled = function (element) {
|
||||||
|
return self.azure_oauth_enabled() && !self.azure_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let oidc_oauth_auto_configure_enabled = function (element) {
|
||||||
|
return self.oidc_oauth_enabled() && self.oidc_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let oidc_oauth_auto_configure_disabled = function (element) {
|
||||||
|
return self.oidc_oauth_enabled() && !self.oidc_oauth_auto_configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.validator.addMethod('auth_enabled', auth_enabled, 'At least one authentication method must be enabled.');
|
||||||
|
jQuery.validator.addMethod('ldap_exclusive', ldap_exclusive, 'The LDAP group security and role auto-provisioning features are mutually exclusive.');
|
||||||
|
jQuery.validator.addMethod('uuid', uuid, 'A valid UUID is required.');
|
||||||
|
|
||||||
|
target.validate({
|
||||||
|
ignore: '',
|
||||||
|
errorPlacement: function (error, element) {
|
||||||
|
let useFooter = false;
|
||||||
|
for (let i = 0; i < footerErrorElements.length; i++) {
|
||||||
|
if (element.is(footerErrorElements[i])) {
|
||||||
|
useFooter = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useFooter) {
|
||||||
|
target.find('.card-footer > .error').append(error);
|
||||||
|
} else if (element.is('input[type=radio]')) {
|
||||||
|
error.insertAfter(element.parents('div.radio'));
|
||||||
|
} else {
|
||||||
|
element.after(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showErrors: function (errorMap, errorList) {
|
||||||
|
this.defaultShowErrors();
|
||||||
|
tabs.each(function (index, tab) {
|
||||||
|
tab = $(tab);
|
||||||
|
let tabId = tab.data('tab');
|
||||||
|
let tabLink = target.find('.nav-tabs > li > a[data-tab="' + tabId + '"]');
|
||||||
|
if (tab.find(errorCheckQuery).length > 0) {
|
||||||
|
tabLink.addClass('error');
|
||||||
|
} else {
|
||||||
|
tabLink.removeClass('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
local_db_enabled: 'auth_enabled',
|
||||||
|
ldap_enabled: 'auth_enabled',
|
||||||
|
google_oauth_enabled: 'auth_enabled',
|
||||||
|
github_oauth_enabled: 'auth_enabled',
|
||||||
|
azure_oauth_enabled: 'auth_enabled',
|
||||||
|
oidc_oauth_enabled: 'auth_enabled',
|
||||||
|
pwd_min_len: {
|
||||||
|
required: enforce_characters,
|
||||||
|
digits: true,
|
||||||
|
min: 1,
|
||||||
|
max: 64,
|
||||||
|
},
|
||||||
|
pwd_min_lowercase: {
|
||||||
|
required: enforce_characters,
|
||||||
|
digits: true,
|
||||||
|
min: 0,
|
||||||
|
max: 64,
|
||||||
|
},
|
||||||
|
pwd_min_uppercase: {
|
||||||
|
required: enforce_characters,
|
||||||
|
digits: true,
|
||||||
|
min: 0,
|
||||||
|
max: 64,
|
||||||
|
},
|
||||||
|
pwd_min_digits: {
|
||||||
|
required: enforce_characters,
|
||||||
|
digits: true,
|
||||||
|
min: 0,
|
||||||
|
max: 64,
|
||||||
|
},
|
||||||
|
pwd_min_special: {
|
||||||
|
required: enforce_characters,
|
||||||
|
digits: true,
|
||||||
|
min: 0,
|
||||||
|
max: 64,
|
||||||
|
},
|
||||||
|
pwd_min_complexity: {
|
||||||
|
required: enforce_complexity,
|
||||||
|
digits: true,
|
||||||
|
min: 1,
|
||||||
|
max: 1000,
|
||||||
|
},
|
||||||
|
ldap_type: ldap_enabled,
|
||||||
|
ldap_uri: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
minlength: 11,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
ldap_base_dn: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
minlength: 4,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
ldap_admin_username: {
|
||||||
|
required: ldap_type_openldap,
|
||||||
|
minlength: 4,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
ldap_admin_password: {
|
||||||
|
required: ldap_type_openldap,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
ldap_domain: {
|
||||||
|
required: ldap_type_ad,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
ldap_filter_basic: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
minlength: 3,
|
||||||
|
maxlength: 1000,
|
||||||
|
},
|
||||||
|
ldap_filter_username: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
ldap_filter_group: {
|
||||||
|
required: ldap_type_openldap,
|
||||||
|
minlength: 3,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
ldap_filter_groupname: {
|
||||||
|
required: ldap_type_openldap,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
ldap_sg_enabled: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
ldap_exclusive: true,
|
||||||
|
},
|
||||||
|
ldap_admin_group: {
|
||||||
|
required: ldap_sg_enabled,
|
||||||
|
minlength: 3,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
ldap_operator_group: {
|
||||||
|
required: ldap_sg_enabled,
|
||||||
|
minlength: 3,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
ldap_user_group: {
|
||||||
|
required: ldap_sg_enabled,
|
||||||
|
minlength: 3,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
autoprovisioning: {
|
||||||
|
required: ldap_enabled,
|
||||||
|
ldap_exclusive: true,
|
||||||
|
},
|
||||||
|
autoprovisioning_attribute: {
|
||||||
|
required: ldap_ap_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
urn_value: {
|
||||||
|
required: ldap_ap_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 100,
|
||||||
|
},
|
||||||
|
purge: ldap_enabled,
|
||||||
|
google_oauth_client_id: {
|
||||||
|
required: google_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
google_oauth_client_secret: {
|
||||||
|
required: google_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
google_oauth_scope: {
|
||||||
|
required: google_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
google_base_url: {
|
||||||
|
required: google_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
google_oauth_metadata_url: {
|
||||||
|
required: google_oauth_auto_configure_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
google_token_url: {
|
||||||
|
required: google_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
google_authorize_url: {
|
||||||
|
required: google_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
github_oauth_key: {
|
||||||
|
required: github_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
github_oauth_secret: {
|
||||||
|
required: github_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
github_oauth_scope: {
|
||||||
|
required: github_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
github_oauth_api_url: {
|
||||||
|
required: github_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
github_oauth_metadata_url: {
|
||||||
|
required: github_oauth_auto_configure_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
github_oauth_token_url: {
|
||||||
|
required: github_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
github_oauth_authorize_url: {
|
||||||
|
required: github_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
azure_oauth_key: {
|
||||||
|
required: azure_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
uuid: true,
|
||||||
|
},
|
||||||
|
azure_oauth_secret: {
|
||||||
|
required: azure_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
azure_oauth_scope: {
|
||||||
|
required: azure_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
azure_oauth_api_url: {
|
||||||
|
required: azure_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
azure_oauth_metadata_url: {
|
||||||
|
required: azure_oauth_auto_configure_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
azure_oauth_token_url: {
|
||||||
|
required: azure_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
azure_oauth_authorize_url: {
|
||||||
|
required: azure_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
azure_sg_enabled: azure_oauth_enabled,
|
||||||
|
azure_admin_group: {
|
||||||
|
uuid: azure_gs_enabled,
|
||||||
|
},
|
||||||
|
azure_operator_group: {
|
||||||
|
uuid: azure_gs_enabled,
|
||||||
|
},
|
||||||
|
azure_user_group: {
|
||||||
|
uuid: azure_gs_enabled,
|
||||||
|
},
|
||||||
|
azure_group_accounts_enabled: azure_oauth_enabled,
|
||||||
|
azure_group_accounts_name: {
|
||||||
|
required: azure_gas_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
azure_group_accounts_name_re: {
|
||||||
|
required: azure_gas_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
azure_group_accounts_description: {
|
||||||
|
required: azure_gas_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
azure_group_accounts_description_re: {
|
||||||
|
required: azure_gas_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_key: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_secret: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_scope: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_api_url: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
oidc_oauth_metadata_url: {
|
||||||
|
required: oidc_oauth_auto_configure_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
oidc_oauth_token_url: {
|
||||||
|
required: oidc_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
oidc_oauth_authorize_url: {
|
||||||
|
required: oidc_oauth_auto_configure_disabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
oidc_oauth_logout_url: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
oidc_oauth_username: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_email: {
|
||||||
|
required: oidc_oauth_enabled,
|
||||||
|
minlength: 1,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_firstname: {
|
||||||
|
minlength: 0,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_last_name: {
|
||||||
|
minlength: 0,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_account_name_property: {
|
||||||
|
minlength: 0,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
oidc_oauth_account_description_property: {
|
||||||
|
minlength: 0,
|
||||||
|
maxlength: 255,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
ldap_sg_enabled: {
|
||||||
|
ldap_exclusive: 'The LDAP group security feature is mutually exclusive with the LDAP role auto-provisioning feature.',
|
||||||
|
},
|
||||||
|
autoprovisioning: {
|
||||||
|
ldap_exclusive: 'The LDAP role auto-provisioning feature is mutually exclusive with the LDAP group security feature.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
target.find('input, select, textarea, label').on('change,keyup,blur,click', onElementChanged);
|
||||||
|
target.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.activateTab = function (tab) {
|
||||||
|
$('[role="tablist"] a.nav-link').blur();
|
||||||
|
self.tab_active(tab);
|
||||||
|
window.location.hash = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.activateDefaultTab = function () {
|
||||||
|
self.activateTab(self.tab_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.getHash = function () {
|
||||||
|
return window.location.hash.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hasHash = function () {
|
||||||
|
return window.location.hash.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onDataLoaded = function (result) {
|
||||||
|
if (result.status == 0) {
|
||||||
|
self.messages_class('danger');
|
||||||
|
self.messages(result.messages);
|
||||||
|
self.loading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update(result.data);
|
||||||
|
self.messages_class('info');
|
||||||
|
self.messages(result.messages);
|
||||||
|
self.loading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onDataSaved = function (result) {
|
||||||
|
if (result.status == 0) {
|
||||||
|
self.saved(false);
|
||||||
|
self.save_failed(true);
|
||||||
|
self.messages_class('danger');
|
||||||
|
self.messages(result.messages);
|
||||||
|
self.saving(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update(result.data);
|
||||||
|
self.saved(true);
|
||||||
|
self.save_failed(false);
|
||||||
|
self.messages_class('info');
|
||||||
|
self.messages(result.messages);
|
||||||
|
self.saving(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onHashChange = function (event) {
|
||||||
|
let hash = window.location.hash.trim();
|
||||||
|
if (hash.length > 1) {
|
||||||
|
self.activateTab(hash.substring(1));
|
||||||
|
} else {
|
||||||
|
self.activateDefaultTab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onSaveClick = function (model, event) {
|
||||||
|
self.save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onTabClick = function (model, event) {
|
||||||
|
self.activateTab($(event.target).data('tab'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -96,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Users on the right have access to manage records in all domains
|
<p>Users on the right have access to manage records in all zones
|
||||||
associated with the account.
|
associated with the account.
|
||||||
</p>
|
</p>
|
||||||
<p>Click on users to move between columns.</p>
|
<p>Click on users to move between columns.</p>
|
||||||
@ -113,12 +113,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card-body -->
|
<!-- /.card-body -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Domains on the right are associated with the account. Red marked domain names are
|
<p>Zones on the right are associated with the account. Red marked zone names are
|
||||||
already associated with other accounts.
|
already associated with other accounts.
|
||||||
Moving already associated domains to this account will overwrite the previous
|
Moving already associated zones to this account will overwrite the previous
|
||||||
associated account.
|
associated account.
|
||||||
</p>
|
</p>
|
||||||
<p>Hover over the red domain names to show the associated account. Click on domains to
|
<p>Hover over the red zone names to show the associated account. Click on zones to
|
||||||
move between columns.</p>
|
move between columns.</p>
|
||||||
<div class="form-group col-2">
|
<div class="form-group col-2">
|
||||||
<select multiple="multiple" class="form-control" id="account_domains"
|
<select multiple="multiple" class="form-control" id="account_domains"
|
||||||
@ -168,12 +168,12 @@
|
|||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
An account allows grouping of domains belonging to a particular entity, such as a
|
An account allows grouping of zones belonging to a particular entity, such as a
|
||||||
customer or
|
customer or
|
||||||
department.
|
department.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
A domain can be assigned to an account upon domain creation or through the domain
|
A zone can be assigned to an account upon zone creation or through the zone
|
||||||
administration
|
administration
|
||||||
page.
|
page.
|
||||||
</p>
|
</p>
|
||||||
@ -242,6 +242,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
addMultiSelect("#account_multi_user", "Username")
|
addMultiSelect("#account_multi_user", "Username")
|
||||||
addMultiSelect("#account_domains", "Domain")
|
addMultiSelect("#account_domains", "Zone")
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
<div class="card-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
<p>This key will be linked to the accounts on the right,</p>
|
<p>This key will be linked to the accounts on the right,</p>
|
||||||
<p>thus granting access to domains owned by the selected accounts.</p>
|
<p>thus granting access to zones owned by the selected accounts.</p>
|
||||||
<p>Click on accounts to move between the columns.</p>
|
<p>Click on accounts to move between the columns.</p>
|
||||||
<div class="form-group col-2">
|
<div class="form-group col-2">
|
||||||
<select multiple="multiple" class="form-control" id="key_multi_account"
|
<select multiple="multiple" class="form-control" id="key_multi_account"
|
||||||
@ -87,12 +87,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card-body -->
|
<!-- /.card-body -->
|
||||||
<div class="card-header key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
<div class="card-header key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
<h3 class="card-title">Domain Access Control</h3>
|
<h3 class="card-title">Zone Access Control</h3>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
<div class="card-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
<p>This key will have access to the domains on the right.</p>
|
<p>This key will have access to the zones on the right.</p>
|
||||||
<p>Click on domains to move between the columns.</p>
|
<p>Click on zones to move between the columns.</p>
|
||||||
<div class="form-group col-2">
|
<div class="form-group col-2">
|
||||||
<select multiple="multiple" class="form-control" id="key_multi_domain"
|
<select multiple="multiple" class="form-control" id="key_multi_domain"
|
||||||
name="key_multi_domain">
|
name="key_multi_domain">
|
||||||
@ -131,7 +131,7 @@
|
|||||||
<p>Fill in all the fields in the form to the left.</p>
|
<p>Fill in all the fields in the form to the left.</p>
|
||||||
<p><strong>Role</strong> The role of the key.</p>
|
<p><strong>Role</strong> The role of the key.</p>
|
||||||
<p><strong>Description</strong> The key description.</p>
|
<p><strong>Description</strong> The key description.</p>
|
||||||
<p><strong>Access Control</strong> The domains or accounts which the key has access to.</p>
|
<p><strong>Access Control</strong> The zones or accounts which the key has access to.</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.card-body -->
|
<!-- /.card-body -->
|
||||||
</div>
|
</div>
|
||||||
@ -154,13 +154,13 @@
|
|||||||
var warn_modal = $("#modal_warning");
|
var warn_modal = $("#modal_warning");
|
||||||
|
|
||||||
if (selectedRole != "User" && selectedDomains > 0 && selectedAccounts > 0) {
|
if (selectedRole != "User" && selectedDomains > 0 && selectedAccounts > 0) {
|
||||||
var warning = "Administrator and Operators have access to all domains. Your domain an account selection won't be saved.";
|
var warning = "Administrator and Operators have access to all zones. Your zone an account selection won't be saved.";
|
||||||
e.preventDefault(e);
|
e.preventDefault(e);
|
||||||
warn_modal.modal('show');
|
warn_modal.modal('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRole == "User" && selectedDomains == 0 && selectedAccounts == 0) {
|
if (selectedRole == "User" && selectedDomains == 0 && selectedAccounts == 0) {
|
||||||
var warning = "User role must have at least one account or one domain bound. None selected.";
|
var warning = "User role must have at least one account or one zone bound. None selected.";
|
||||||
e.preventDefault(e);
|
e.preventDefault(e);
|
||||||
warn_modal.modal('show');
|
warn_modal.modal('show');
|
||||||
}
|
}
|
||||||
@ -203,8 +203,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#key_multi_domain").multiSelect({
|
$("#key_multi_domain").multiSelect({
|
||||||
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Zone Name'>",
|
||||||
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Zone Name'>",
|
||||||
afterInit: function (ms) {
|
afterInit: function (ms) {
|
||||||
var that = this,
|
var that = this,
|
||||||
$selectableSearch = that.$selectableUl.prev(),
|
$selectableSearch = that.$selectableUl.prev(),
|
||||||
|
@ -135,9 +135,9 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Fill in all the fields to the in the form to the left.</p>
|
<p>Fill in all the fields to the in the form to the left.</p>
|
||||||
{% if create %}
|
{% if create %}
|
||||||
<p><strong>Newly created users do not have access to any domains.</strong> You will need
|
<p><strong>Newly created users do not have access to any zones.</strong> You will need
|
||||||
to grant
|
to grant
|
||||||
access to the user once it is created via the domain management buttons on the
|
access to the user once it is created via the zone management buttons on the
|
||||||
dashboard.
|
dashboard.
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="callout callout-info">
|
<div class="callout callout-info">
|
||||||
<p>This tool can be used to search for domains, records, and comments via the PDNS
|
<p>This tool can be used to search for zones, records, and comments via the PDNS
|
||||||
API.</p>
|
API.</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.callout -->
|
<!-- /.callout -->
|
||||||
|
@ -39,8 +39,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body clearfix">
|
|
||||||
<form id="history-search-form" autocomplete="off">
|
<form id="history-search-form" autocomplete="off">
|
||||||
|
<div class="card-body clearfix">
|
||||||
<ul class="nav nav-tabs" id="custom-content-below-tab" role="tablist">
|
<ul class="nav nav-tabs" id="custom-content-below-tab" role="tablist">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="#tabs-act" data-toggle="pill" role="tab">
|
<a class="nav-link active" href="#tabs-act" data-toggle="pill" role="tab">
|
||||||
@ -49,7 +49,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#tabs-domain" data-toggle="pill" role="tab">
|
<a class="nav-link" href="#tabs-domain" data-toggle="pill" role="tab">
|
||||||
Search By Domain
|
Search By Zone
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@ -71,13 +71,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="tabs-domain">
|
<div class="tab-pane" id="tabs-domain">
|
||||||
<td>
|
<td>
|
||||||
<label>Domain Name</label>
|
<label>Zone Name</label>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="autocomplete" style="width:250px;">
|
<div class="autocomplete" style="width:250px;">
|
||||||
<input type="text" class="form-control" id="domain_name_filter"
|
<input type="text" class="form-control" id="domain_name_filter"
|
||||||
name="domain_name_filter"
|
name="domain_name_filter"
|
||||||
placeholder="Enter * to search for any domain" value="">
|
placeholder="Enter * to search for any zone" value="">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -144,7 +144,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -201,8 +200,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div id="table_from_ajax"></div>
|
<div id="table_from_ajax"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -447,7 +446,7 @@
|
|||||||
$('#auth_name_filter').val('');
|
$('#auth_name_filter').val('');
|
||||||
$('#user_name_filter').removeAttr('disabled');
|
$('#user_name_filter').removeAttr('disabled');
|
||||||
canSearch = false;
|
canSearch = false;
|
||||||
main_field = "Domain Name"
|
main_field = "Zone Name"
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#account_tab').click(function () {
|
$('#account_tab').click(function () {
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<th>Contact</th>
|
<th>Contact</th>
|
||||||
<th>Mail</th>
|
<th>Mail</th>
|
||||||
<th>Member</th>
|
<th>Member</th>
|
||||||
<th>Domain</th>
|
<th>Zone(s)</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Domains</th>
|
<th>Zones</th>
|
||||||
<th>Accounts</th>
|
<th>Accounts</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -143,7 +143,7 @@
|
|||||||
var modal = $("#modal_revoke");
|
var modal = $("#modal_revoke");
|
||||||
var username = $(this).prop('id');
|
var username = $(this).prop('id');
|
||||||
var info = "Are you sure you want to revoke all privileges for user " + username +
|
var info = "Are you sure you want to revoke all privileges for user " + username +
|
||||||
"? They will not able to access any domain.";
|
"? They will not able to access any zone.";
|
||||||
modal.find('.modal-body p').text(info);
|
modal.find('.modal-body p').text(info);
|
||||||
modal.find('#button_revoke_confirm').click(function () {
|
modal.find('#button_revoke_confirm').click(function () {
|
||||||
var postdata = {
|
var postdata = {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -101,7 +101,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if current_user.role.name in ['Administrator', 'Operator'] %}
|
|
||||||
<li class="nav-header">Administration</li>
|
<li class="nav-header">Administration</li>
|
||||||
<li class="{{ 'nav-item active' if active_page == 'admin_global_search' else 'nav-item' }}">
|
<li class="{{ 'nav-item active' if active_page == 'admin_global_search' else 'nav-item' }}">
|
||||||
<a href="{{ url_for('admin.global_search') }}" class="nav-link">
|
<a href="{{ url_for('admin.global_search') }}" class="nav-link">
|
||||||
@ -109,6 +108,15 @@
|
|||||||
<p>Global Search</p>
|
<p>Global Search</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||||
|
<li class="{{ 'nav-item active' if active_page == 'admin_history' else 'nav-item' }}">
|
||||||
|
<a href="{{ url_for('admin.history') }}" class="nav-link">
|
||||||
|
<i class="nav-icon fa-solid fa-timeline"></i>
|
||||||
|
<p>Activity</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.role.name in ['Administrator', 'Operator'] %}
|
||||||
<li class="{{ 'nav-item active' if active_page == 'server_statistics' else 'nav-item' }}">
|
<li class="{{ 'nav-item active' if active_page == 'server_statistics' else 'nav-item' }}">
|
||||||
<a href="{{ url_for('admin.server_statistics') }}" class="nav-link">
|
<a href="{{ url_for('admin.server_statistics') }}" class="nav-link">
|
||||||
<i class="nav-icon fa-solid fa-chart-simple"></i>
|
<i class="nav-icon fa-solid fa-chart-simple"></i>
|
||||||
@ -121,12 +129,6 @@
|
|||||||
<p>Server Configuration</p>
|
<p>Server Configuration</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="{{ 'nav-item active' if active_page == 'admin_history' else 'nav-item' }}">
|
|
||||||
<a href="{{ url_for('admin.history') }}" class="nav-link">
|
|
||||||
<i class="nav-icon fa-solid fa-timeline"></i>
|
|
||||||
<p>Activity</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="{{ 'nav-item active' if active_page == 'admin_domain_template' else 'nav-item' }}">
|
<li class="{{ 'nav-item active' if active_page == 'admin_domain_template' else 'nav-item' }}">
|
||||||
<a href="{{ url_for('admin.templates') }}" class="nav-link">
|
<a href="{{ url_for('admin.templates') }}" class="nav-link">
|
||||||
<i class="nav-icon fa-solid fa-clone"></i>
|
<i class="nav-icon fa-solid fa-clone"></i>
|
||||||
@ -189,14 +191,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% elif SETTING.get('allow_user_view_history') %}
|
|
||||||
<li class="nav-header">Administration</li>
|
|
||||||
<li class="{{ 'nav-item active' if active_page == 'admin_history' else 'nav-item' }}">
|
|
||||||
<a href="{{ url_for('admin.history') }}" class="nav-link">
|
|
||||||
<i class="nav-icon fa-solid fa-calendar-alt"></i>
|
|
||||||
<p>History</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -234,7 +228,7 @@
|
|||||||
<footer class="main-footer">
|
<footer class="main-footer">
|
||||||
<strong><a href="https://github.com/PowerDNS-Admin/PowerDNS-Admin">PowerDNS-Admin</a></strong> - A PowerDNS web
|
<strong><a href="https://github.com/PowerDNS-Admin/PowerDNS-Admin">PowerDNS-Admin</a></strong> - A PowerDNS web
|
||||||
interface with advanced features.
|
interface with advanced features.
|
||||||
<span class="float-right">Version 0.4.0</span>
|
<span class="float-right">Version 0.4.1</span>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<!-- ./wrapper -->
|
<!-- ./wrapper -->
|
||||||
|
@ -269,7 +269,7 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">Sync Domains from backend</h4>
|
<h4 class="modal-title">Sync Zones from backend</h4>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -76,11 +76,16 @@
|
|||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="radio_type" id="radio_type_secondary"
|
<input type="radio" name="radio_type" id="radio_type_secondary"
|
||||||
value="secondary">
|
value="slave">
|
||||||
Secondary
|
Secondary
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" style="display: none;" id="domain_master_address_div">
|
||||||
|
<input type="text" class="form-control" name="domain_master_address"
|
||||||
|
id="domain_master_address"
|
||||||
|
placeholder="Enter valid Primary Server IP addresses (separated by commas)">
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domain_template">Zone Template</label>
|
<label for="domain_template">Zone Template</label>
|
||||||
<select class="form-control" id="domain_template" name="domain_template">
|
<select class="form-control" id="domain_template" name="domain_template">
|
||||||
@ -90,11 +95,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="display: none;" id="domain_primary_address_div">
|
|
||||||
<input type="text" class="form-control" name="domain_primary_address"
|
|
||||||
id="domain_primary_address"
|
|
||||||
placeholder="Enter valid Primary Server IP addresses (separated by commas)">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>SOA-EDIT-API</label>
|
<label>SOA-EDIT-API</label>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
@ -162,7 +162,7 @@
|
|||||||
<dt>Account</dt>
|
<dt>Account</dt>
|
||||||
<dd>Specifies the PowerDNS account value to use for the zone.</dd>
|
<dd>Specifies the PowerDNS account value to use for the zone.</dd>
|
||||||
<dt>Zone Type</dt>
|
<dt>Zone Type</dt>
|
||||||
<dd>The type decides how the domain will be replicated across multiple DNS servers.
|
<dd>The type decides how the zone will be replicated across multiple DNS servers.
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>Native</strong> - The server will not perform any Primary or Secondary
|
<strong>Native</strong> - The server will not perform any Primary or Secondary
|
||||||
@ -186,7 +186,7 @@
|
|||||||
<dt>SOA-EDIT-API</dt>
|
<dt>SOA-EDIT-API</dt>
|
||||||
<dd>The SOA-EDIT-API setting defines how the SOA serial number will be updated after a
|
<dd>The SOA-EDIT-API setting defines how the SOA serial number will be updated after a
|
||||||
change is
|
change is
|
||||||
made to the domain.
|
made to the zone.
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>DEFAULT</strong> - Generate a soa serial of YYYYMMDD01. If the current serial
|
<strong>DEFAULT</strong> - Generate a soa serial of YYYYMMDD01. If the current serial
|
||||||
@ -228,10 +228,10 @@
|
|||||||
<script>
|
<script>
|
||||||
$("input[name=radio_type]").change(function () {
|
$("input[name=radio_type]").change(function () {
|
||||||
var type = $(this).val();
|
var type = $(this).val();
|
||||||
if (type == "secondary") {
|
if (type == "slave") {
|
||||||
$("#domain_primary_address_div").show();
|
$("#domain_master_address_div").show();
|
||||||
} else {
|
} else {
|
||||||
$("#domain_primary_address_div").hide();
|
$("#domain_master_address_div").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
{% if record_name and record_type %}
|
{% if record_name and record_type %}
|
||||||
Record changelog: <b>{{ record_name}}   {{ record_type }}</b>
|
Record changelog: <b>{{ record_name}}   {{ record_type }}</b>
|
||||||
{% else %}
|
{% else %}
|
||||||
Domain changelog: <b>{{ domain.name | pretty_domain_name }}</b>
|
Zone changelog: <b>{{ domain.name | pretty_domain_name }}</b>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<button type="button" class="btn btn-primary float-left button_show_records" id="{{ domain.name }}">
|
<button type="button" class="btn btn-primary float-left button_show_records" id="{{ domain.name }}">
|
||||||
<i class="fa-solid fa-arrow-left"></i>
|
<i class="fa-solid fa-arrow-left"></i>
|
||||||
Manage Domain {{ domain.name }}
|
Manage Zone {{ domain.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domainid">Zone</label>
|
<label for="domainid">Zone</label>
|
||||||
<select id=domainid class="form-control" style="width:15em;">
|
<select id=domainid class="form-control" style="width:15em;">
|
||||||
<option value="0">- Select Domain -</option>
|
<option value="0">- Select Zone -</option>
|
||||||
{% for domain in domainss %}
|
{% for domain in domainss %}
|
||||||
<option value="{{ domain.id }}">{{ domain.name }}</option>
|
<option value="{{ domain.id }}">{{ domain.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -83,7 +83,7 @@
|
|||||||
$(document.body).on("click", ".button_delete", function (e) {
|
$(document.body).on("click", ".button_delete", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if ($("#domainid").val() == 0) {
|
if ($("#domainid").val() == 0) {
|
||||||
showErrorModal("Please select domain to remove.");
|
showErrorModal("Please select zone to remove.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,11 +148,11 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p>Users on the right have access to manage the records in
|
<p>Users on the right have access to manage the records in
|
||||||
the {{ domain.name | pretty_domain_name }} domain.</p>
|
the {{ domain.name | pretty_domain_name }} zone.</p>
|
||||||
<p>Click on users to move from between columns.</p>
|
<p>Click on users to move from between columns.</p>
|
||||||
<p>
|
<p>
|
||||||
Users in <font style="color: red;">red</font> are Administrators
|
Users in <font style="color: red;">red</font> are Administrators
|
||||||
and already have access to <b>ALL</b> domains.
|
and already have access to <b>ALL</b> zones.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -197,7 +197,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>The type decides how the domain will be replicated across multiple DNS servers.</p>
|
<p>The type decides how the zone will be replicated across multiple DNS servers.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Native - PowerDNS will not perform any replication. Use this if you only have one
|
Native - PowerDNS will not perform any replication. Use this if you only have one
|
||||||
@ -214,19 +214,20 @@
|
|||||||
zone transfers (AXFRs) from other servers configured as primaries.
|
zone transfers (AXFRs) from other servers configured as primaries.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<b>New Domain Type Setting:</b>
|
<b>New Zone Type Setting:</b>
|
||||||
<form method="post" action="{{ url_for('domain.change_type', domain_name=domain.name) }}">
|
<form method="post" action="{{ url_for('domain.change_type', domain_name=domain.name) }}">
|
||||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||||
<select name="domain_type" class="form-control" style="width:15em;">
|
<select name="domain_type" class="form-control" style="width:15em;">
|
||||||
<option selected value="0">- Unchanged -</option>
|
<option value="0">- Unchanged -</option>
|
||||||
<option value="native">Native</option>
|
{% for type in ["native", "master", "slave"] %}
|
||||||
<option value="primary">Primary</option>
|
<option {% if zone_type == type %}selected{% endif %} value="{{ type }}">{{ type | format_zone_type }}</option>
|
||||||
<option value="secondary">Secondary</option>
|
{% endfor %}
|
||||||
</select><br/>
|
</select><br/>
|
||||||
<div class="form-group" style="display: none;" id="domain_primary_address_div">
|
<div class="form-group" style="{% if zone_type != 'slave' %}display: none;{% endif %}" id="domain_master_address_div">
|
||||||
<input type="text" class="form-control" name="domain_primary_address"
|
<input type="text" class="form-control" name="domain_master_address"
|
||||||
id="domain_primary_address"
|
id="domain_master_address"
|
||||||
placeholder="Enter valid Primary Server IP addresses (separated by commas)">
|
placeholder="Enter valid Primary Server IP addresses (separated by commas)"
|
||||||
|
value="{{ masters }}">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" title="Update Zone Type" class="btn btn-primary" id="change_type">
|
<button type="submit" title="Update Zone Type" class="btn btn-primary" id="change_type">
|
||||||
<i class="fa-solid fa-floppy-disk"></i> Update Zone Type
|
<i class="fa-solid fa-floppy-disk"></i> Update Zone Type
|
||||||
@ -251,7 +252,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>The SOA-EDIT-API setting defines how the SOA serial number will be updated after a change
|
<p>The SOA-EDIT-API setting defines how the SOA serial number will be updated after a change
|
||||||
is made
|
is made
|
||||||
to the domain.</p>
|
to the zone.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
DEFAULT - Generate a soa serial of YYYYMMDD01. If the current serial is lower than
|
DEFAULT - Generate a soa serial of YYYYMMDD01. If the current serial is lower than
|
||||||
@ -276,10 +277,9 @@
|
|||||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||||
<select name="soa_edit_api" class="form-control" style="width:15em;">
|
<select name="soa_edit_api" class="form-control" style="width:15em;">
|
||||||
<option selected value="0">- Unchanged -</option>
|
<option selected value="0">- Unchanged -</option>
|
||||||
<option>DEFAULT</option>
|
{% for edit_type in ["DEFAULT", "INCREASE", "EPOCH", "OFF"] %}
|
||||||
<option>INCREASE</option>
|
<option {% if soa_edit_api == edit_type %}selected{% endif %}>{{ edit_type }}</option>
|
||||||
<option>EPOCH</option>
|
{% endfor %}
|
||||||
<option>OFF</option>
|
|
||||||
</select><br/>
|
</select><br/>
|
||||||
<button type="submit" title="Update SOA-EDIT-API" class="btn btn-primary"
|
<button type="submit" title="Update SOA-EDIT-API" class="btn btn-primary"
|
||||||
id="change_soa_edit_api">
|
id="change_soa_edit_api">
|
||||||
@ -303,9 +303,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card-header -->
|
<!-- /.card-header -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>This function is used to remove a domain from PowerDNS-Admin <b>AND</b> PowerDNS. All
|
<p>This function is used to remove a zone from PowerDNS-Admin <b>AND</b> PowerDNS. All
|
||||||
records and
|
records and
|
||||||
user privileges associated with this domain will also be removed. This change cannot be
|
user privileges associated with this zone will also be removed. This change cannot be
|
||||||
reverted.</p>
|
reverted.</p>
|
||||||
<button type="button" title="Delete Zone" class="btn btn-danger float-left delete_domain"
|
<button type="button" title="Delete Zone" class="btn btn-danger float-left delete_domain"
|
||||||
id="{{ domain.name }}">
|
id="{{ domain.name }}">
|
||||||
@ -402,7 +402,7 @@
|
|||||||
applyChanges(postdata, $SCRIPT_ROOT + '/domain/' + domain + '/manage-setting', true);
|
applyChanges(postdata, $SCRIPT_ROOT + '/domain/' + domain + '/manage-setting', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle deletion of domain
|
// handle deletion of zone
|
||||||
$(document.body).on('click', '.delete_domain', function () {
|
$(document.body).on('click', '.delete_domain', function () {
|
||||||
var modal = $("#modal_delete_domain");
|
var modal = $("#modal_delete_domain");
|
||||||
var domain = $(this).prop('id');
|
var domain = $(this).prop('id');
|
||||||
@ -419,13 +419,13 @@
|
|||||||
modal.modal('show');
|
modal.modal('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
// domain primary address input handeling
|
// zone primary address input handeling
|
||||||
$("select[name=domain_type]").change(function () {
|
$("select[name=domain_type]").change(function () {
|
||||||
var type = $(this).val();
|
var type = $(this).val();
|
||||||
if (type == "secondary") {
|
if (type == "slave") {
|
||||||
$("#domain_primary_address_div").show();
|
$("#domain_master_address_div").show();
|
||||||
} else {
|
} else {
|
||||||
$("#domain_primary_address_div").hide();
|
$("#domain_master_address_div").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@
|
|||||||
<div class="nav-tabs-custom mb-2">
|
<div class="nav-tabs-custom mb-2">
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="#tabs-personal" data-toggle="tab">
|
<a class="nav-link {{ 'active' if not error_messages else '' }}" href="#tabs-personal" data-toggle="tab">
|
||||||
Personal Info
|
Personal Info
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if session['authentication_type'] == 'LOCAL' %}
|
{% if session['authentication_type'] == 'LOCAL' %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#tabs-password" data-toggle="tab">
|
<a class="nav-link {{ 'active' if 'password' in error_messages else '' }}" href="#tabs-password" data-toggle="tab">
|
||||||
Change Password
|
Change Password
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -57,7 +57,8 @@
|
|||||||
<!-- /.nav-tabs-custom -->
|
<!-- /.nav-tabs-custom -->
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane fade show active" id="tabs-personal">
|
<div class="tab-pane fade {{ 'show active' if not error_messages else '' }}"
|
||||||
|
id="tabs-personal">
|
||||||
<form role="form" method="post" action="{{ user_profile }}">
|
<form role="form" method="post" action="{{ user_profile }}">
|
||||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -91,7 +92,8 @@
|
|||||||
<!-- /.tab-pane -->
|
<!-- /.tab-pane -->
|
||||||
|
|
||||||
{% if session['authentication_type'] == 'LOCAL' %}
|
{% if session['authentication_type'] == 'LOCAL' %}
|
||||||
<div class="tab-pane fade" id="tabs-password">
|
<div class="tab-pane fade {{ 'show active' if 'password' in error_messages else '' }}"
|
||||||
|
id="tabs-password">
|
||||||
{% if not current_user.password %}
|
{% if not current_user.password %}
|
||||||
Your account password is managed via LDAP which isn't supported to
|
Your account password is managed via LDAP which isn't supported to
|
||||||
change here.
|
change here.
|
||||||
@ -101,8 +103,15 @@
|
|||||||
value="{{ csrf_token() }}">
|
value="{{ csrf_token() }}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">New Password</label>
|
<label for="password">New Password</label>
|
||||||
<input type="password" class="form-control" name="password"
|
<input type="password" class="form-control {{ 'is-invalid' if 'password' in error_messages else '' }}"
|
||||||
|
name="password"
|
||||||
id="newpassword">
|
id="newpassword">
|
||||||
|
{% if 'password' in error_messages %}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
{{ error_messages['password'] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="rpassword">Re-type New Password</label>
|
<label for="rpassword">Re-type New Password</label>
|
||||||
|
@ -17,7 +17,7 @@ bravado-core==5.17.1
|
|||||||
certifi==2022.12.7
|
certifi==2022.12.7
|
||||||
cffi==1.15.1
|
cffi==1.15.1
|
||||||
configobj==5.0.8
|
configobj==5.0.8
|
||||||
cryptography==36.0.2
|
cryptography==39.0.2 # fixes CVE-2023-0286, CVE-2023-23931
|
||||||
cssmin==0.2.0
|
cssmin==0.2.0
|
||||||
dnspython>=2.3.0
|
dnspython>=2.3.0
|
||||||
flask_session_captcha==1.3.0
|
flask_session_captcha==1.3.0
|
||||||
@ -25,7 +25,7 @@ gunicorn==20.1.0
|
|||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
jsonschema[format]>=2.5.1,<4.0.0 # until https://github.com/Yelp/bravado-core/pull/385
|
jsonschema[format]>=2.5.1,<4.0.0 # until https://github.com/Yelp/bravado-core/pull/385
|
||||||
lima==0.5
|
lima==0.5
|
||||||
lxml==4.6.5
|
--use-feature=no-binary-enable-wheel-cache lxml==4.9.0
|
||||||
mysqlclient==2.0.1
|
mysqlclient==2.0.1
|
||||||
passlib==1.7.4
|
passlib==1.7.4
|
||||||
#pyOpenSSL==22.1.0
|
#pyOpenSSL==22.1.0
|
||||||
@ -33,7 +33,7 @@ pyasn1==0.4.8
|
|||||||
pyotp==2.8.0
|
pyotp==2.8.0
|
||||||
pytest==7.2.1
|
pytest==7.2.1
|
||||||
python-ldap==3.4.3
|
python-ldap==3.4.3
|
||||||
python3-saml==1.14.0
|
python3-saml==1.15.0
|
||||||
pytimeparse==1.1.8
|
pytimeparse==1.1.8
|
||||||
pytz==2022.7.1
|
pytz==2022.7.1
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
@ -43,3 +43,6 @@ webcolors==1.12
|
|||||||
werkzeug==2.1.2
|
werkzeug==2.1.2
|
||||||
zipp==3.11.0
|
zipp==3.11.0
|
||||||
rcssmin==1.1.1
|
rcssmin==1.1.1
|
||||||
|
zxcvbn==4.4.28
|
||||||
|
psycopg2==2.9.5
|
||||||
|
setuptools==65.5.1 # fixes CVE-2022-40897
|
@ -29,6 +29,6 @@ with app.app_context():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
### Start the update process
|
### Start the update process
|
||||||
app.logger.info('Update domains from nameserver API')
|
app.logger.info('Update zones from nameserver API')
|
||||||
|
|
||||||
Domain().update()
|
Domain().update()
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4"
|
resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4"
|
||||||
integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==
|
integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==
|
||||||
|
|
||||||
"@fortawesome/fontawesome-free@6.3.0":
|
"@fortawesome/fontawesome-free@6.3.0", "@fortawesome/fontawesome-free@^5.15.4":
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.3.0.tgz#b5877182692a6f7a39d1108837bec24247ba4bd7"
|
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.3.0.tgz#b5877182692a6f7a39d1108837bec24247ba4bd7"
|
||||||
integrity sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==
|
integrity sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==
|
||||||
@ -1026,7 +1026,7 @@ jquery-ui-dist@^1.13.0, jquery-ui-dist@^1.13.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jquery ">=1.8.0 <4.0.0"
|
jquery ">=1.8.0 <4.0.0"
|
||||||
|
|
||||||
jquery-validation@^1.19.3:
|
jquery-validation@^1.19.3, jquery-validation@^1.19.5:
|
||||||
version "1.19.5"
|
version "1.19.5"
|
||||||
resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.5.tgz#557495b7cad79716897057c4447ad3cd76fda811"
|
resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.5.tgz#557495b7cad79716897057c4447ad3cd76fda811"
|
||||||
integrity sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==
|
integrity sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==
|
||||||
@ -1081,6 +1081,11 @@ jtimeout@^3.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jquery ">=1.7.1 <4.0.0"
|
jquery ">=1.7.1 <4.0.0"
|
||||||
|
|
||||||
|
knockout@^3.5.1:
|
||||||
|
version "3.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/knockout/-/knockout-3.5.1.tgz#62c81e81843bea2008fd23c575edd9ca978e75cf"
|
||||||
|
integrity sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q==
|
||||||
|
|
||||||
levn@~0.3.0:
|
levn@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
||||||
|
Loading…
Reference in New Issue
Block a user