Allow all application settings to be configured by environment variables (#1535)

This commit is contained in:
Matt Scott 2023-04-14 19:30:18 -04:00 committed by GitHub
commit 9193317d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 774 additions and 749 deletions

View File

@ -1,151 +1,2 @@
# import everything from environment variables
import os
import sys
import json
# Defaults for Docker image
BIND_ADDRESS = '0.0.0.0'
PORT = 80
SERVER_EXTERNAL_SSL = os.getenv('SERVER_EXTERNAL_SSL', True)
SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db'
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_HTTPONLY = True
SESSION_TYPE = 'sqlalchemy'
legal_envvars = (
'SECRET_KEY',
'OIDC_OAUTH_ENABLED',
'OIDC_OAUTH_KEY',
'OIDC_OAUTH_SECRET',
'OIDC_OAUTH_API_URL',
'OIDC_OAUTH_TOKEN_URL',
'OIDC_OAUTH_AUTHORIZE_URL',
'OIDC_OAUTH_METADATA_URL',
'OIDC_OAUTH_LOGOUT_URL',
'OIDC_OAUTH_SCOPE',
'OIDC_OAUTH_USERNAME',
'OIDC_OAUTH_FIRSTNAME',
'OIDC_OAUTH_LAST_NAME',
'OIDC_OAUTH_EMAIL',
'BIND_ADDRESS',
'PORT',
'SERVER_EXTERNAL_SSL',
'LOG_LEVEL',
'SALT',
'SQLALCHEMY_TRACK_MODIFICATIONS',
'SQLALCHEMY_DATABASE_URI',
'SQLALCHEMY_ENGINE_OPTIONS',
'MAIL_SERVER',
'MAIL_PORT',
'MAIL_DEBUG',
'MAIL_USE_TLS',
'MAIL_USE_SSL',
'MAIL_USERNAME',
'MAIL_PASSWORD',
'MAIL_DEFAULT_SENDER',
'SAML_ENABLED',
'SAML_DEBUG',
'SAML_PATH',
'SAML_METADATA_URL',
'SAML_METADATA_CACHE_LIFETIME',
'SAML_IDP_SSO_BINDING',
'SAML_IDP_ENTITY_ID',
'SAML_NAMEID_FORMAT',
'SAML_ATTRIBUTE_EMAIL',
'SAML_ATTRIBUTE_GIVENNAME',
'SAML_ATTRIBUTE_SURNAME',
'SAML_ATTRIBUTE_NAME',
'SAML_ATTRIBUTE_USERNAME',
'SAML_ATTRIBUTE_ADMIN',
'SAML_ATTRIBUTE_GROUP',
'SAML_GROUP_ADMIN_NAME',
'SAML_GROUP_TO_ACCOUNT_MAPPING',
'SAML_ATTRIBUTE_ACCOUNT',
'SAML_SP_ENTITY_ID',
'SAML_SP_CONTACT_NAME',
'SAML_SP_CONTACT_MAIL',
'SAML_SIGN_REQUEST',
'SAML_WANT_MESSAGE_SIGNED',
'SAML_LOGOUT',
'SAML_LOGOUT_URL',
'SAML_ASSERTION_ENCRYPTED',
'REMOTE_USER_LOGOUT_URL',
'REMOTE_USER_COOKIES',
'SIGNUP_ENABLED',
'LOCAL_DB_ENABLED',
'LDAP_ENABLED',
'SAML_CERT',
'SAML_KEY',
'SESSION_TYPE',
'SESSION_COOKIE_SECURE',
'CSRF_COOKIE_SECURE',
'CAPTCHA_ENABLE',
)
legal_envvars_int = ('PORT', 'MAIL_PORT', 'SAML_METADATA_CACHE_LIFETIME')
legal_envvars_bool = (
'SQLALCHEMY_TRACK_MODIFICATIONS',
'HSTS_ENABLED',
'MAIL_DEBUG',
'MAIL_USE_TLS',
'MAIL_USE_SSL',
'OIDC_OAUTH_ENABLED',
'SAML_ENABLED',
'SAML_DEBUG',
'SAML_SIGN_REQUEST',
'SAML_WANT_MESSAGE_SIGNED',
'SAML_LOGOUT',
'SAML_ASSERTION_ENCRYPTED',
'REMOTE_USER_ENABLED',
'SIGNUP_ENABLED',
'LOCAL_DB_ENABLED',
'LDAP_ENABLED',
'SESSION_COOKIE_SECURE',
'CSRF_COOKIE_SECURE',
'CAPTCHA_ENABLE',
'SERVER_EXTERNAL_SSL',
)
legal_envvars_dict = (
'SQLALCHEMY_ENGINE_OPTIONS',
)
def str2bool(v):
return v.lower() in ("true", "yes", "1")
def dictfromstr(v, ret):
try:
return json.loads(ret)
except Exception as e:
print('Cannot parse json {} for variable {}'.format(ret, v))
print(e)
raise ValueError
for v in legal_envvars:
ret = None
# _FILE suffix will allow to read value from file, useful for Docker containers.
# secrets feature
if v + '_FILE' in os.environ:
if v in os.environ:
raise AttributeError(
"Both {} and {} are set but are exclusive.".format(
v, v + '_FILE'))
with open(os.environ[v + '_FILE']) as f:
ret = f.read()
f.close()
elif v in os.environ:
ret = os.environ[v]
if ret is not None:
if v in legal_envvars_bool:
ret = str2bool(ret)
if v in legal_envvars_int:
ret = int(ret)
if v in legal_envvars_dict:
ret = dictfromstr(v, ret)
sys.modules[__name__].__dict__[v] = ret

View File

@ -4,11 +4,11 @@ from flask import Flask
from flask_mail import Mail
from werkzeug.middleware.proxy_fix import ProxyFix
from flask_session import Session
from .lib import utils
def create_app(config=None):
from powerdnsadmin.lib.settings import AppSettings
from . import models, routes, services
from .assets import assets
app = Flask(__name__)
@ -50,6 +50,9 @@ def create_app(config=None):
elif config.endswith('.py'):
app.config.from_pyfile(config)
# Load any settings defined with environment variables
AppSettings.load_environment(app)
# HSTS
if app.config.get('HSTS_ENABLED'):
from flask_sslify import SSLify
@ -84,7 +87,7 @@ def create_app(config=None):
app.jinja_env.filters['format_datetime_local'] = utils.format_datetime
app.jinja_env.filters['format_zone_type'] = utils.format_zone_type
# Register context proccessors
# Register context processors
from .models.setting import Setting
@app.context_processor

View File

@ -13,6 +13,7 @@ def admin_role_required(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name != 'Administrator':
@ -26,6 +27,7 @@ def operator_role_required(f):
"""
Grant access if user is in Operator role or higher
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in ['Administrator', 'Operator']:
@ -39,6 +41,7 @@ def history_access_required(f):
"""
Grant access if user is in Operator role or higher, or Users can view history
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in [
@ -57,6 +60,7 @@ def can_access_domain(f):
- user is in granted Account, or
- user is in granted Domain
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in ['Administrator', 'Operator']:
@ -83,6 +87,7 @@ def can_configure_dnssec(f):
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in [
@ -94,12 +99,14 @@ def can_configure_dnssec(f):
return decorated_function
def can_remove_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_remove_domain is on
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in [
@ -111,13 +118,13 @@ def can_remove_domain(f):
return decorated_function
def can_create_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_create_domain is on
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in [
@ -144,11 +151,12 @@ def api_basic_auth(f):
# Remove "Basic " from the header value
auth_header = auth_header[6:]
auth_components = []
try:
auth_header = str(base64.b64decode(auth_header), 'utf-8')
# 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.
# NK: We use auth_components here as we don't know if we'll have a colon,
# 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)
except (binascii.Error, UnicodeDecodeError) as e:
current_app.logger.error(
@ -211,6 +219,7 @@ def callback_if_request_body_contains_key(callback, http_methods=[], keys=[]):
If request body contains one or more of specified keys, call
:param callback
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -220,7 +229,9 @@ def callback_if_request_body_contains_key(callback, http_methods=[], keys=[]):
):
callback(*args, **kwargs)
return f(*args, **kwargs)
return decorated_function
return decorator
@ -255,7 +266,9 @@ def api_role_can(action, roles=None, allow_self=False):
"User {} with role {} does not have enough privileges to {}"
).format(current_user.username, current_user.role.name, action)
raise NotEnoughPrivileges(message=msg)
return decorated_function
return decorator
@ -265,6 +278,7 @@ def api_can_create_domain(f):
- user is in Operator role or higher, or
- allow_user_create_domain is on
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role.name not in [
@ -294,6 +308,7 @@ def apikey_can_create_domain(f):
- deny_domain_override is off or
- override_domain is true (from request)
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.apikey.role.name not in [
@ -320,6 +335,7 @@ def apikey_can_remove_domain(http_methods=[]):
- user is in Operator role or higher, or
- allow_user_remove_domain is on
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -333,7 +349,9 @@ def apikey_can_remove_domain(http_methods=[]):
current_app.logger.error(msg.format(g.apikey.id))
raise NotEnoughPrivileges()
return f(*args, **kwargs)
return decorated_function
return decorator
@ -341,6 +359,7 @@ def apikey_is_admin(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.apikey.role.name != 'Administrator':
@ -358,6 +377,7 @@ def apikey_can_access_domain(f):
- user has Operator role or higher, or
- user has explicitly been granted access to domain
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.apikey.role.name not in ['Administrator', 'Operator']:
@ -382,6 +402,7 @@ def apikey_can_configure_dnssec(http_methods=[]):
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
def decorator(f=None):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -395,9 +416,12 @@ def apikey_can_configure_dnssec(http_methods=[]):
current_app.logger.error(msg.format(g.apikey.id))
raise DomainAccessForbidden(message=msg)
return f(*args, **kwargs) if f else None
return decorated_function
return decorator
def allowed_record_types(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -423,6 +447,7 @@ def allowed_record_types(f):
return decorated_function
def allowed_record_ttl(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -436,7 +461,7 @@ def allowed_record_ttl(f):
return f(*args, **kwargs)
allowed_ttls = Setting().get_ttl_options()
allowed_numeric_ttls = [ ttl[0] for ttl in allowed_ttls ]
allowed_numeric_ttls = [ttl[0] for ttl in allowed_ttls]
content = request.get_json()
try:
for record in content['rrsets']:
@ -497,6 +522,7 @@ def dyndns_login_required(f):
return decorated_function
def apikey_or_basic_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -505,4 +531,5 @@ def apikey_or_basic_auth(f):
return apikey_auth(f)(*args, **kwargs)
else:
return api_basic_auth(f)(*args, **kwargs)
return decorated_function

View File

@ -1,44 +1,32 @@
import os
import urllib.parse
basedir = os.path.abspath(os.path.dirname(__file__))
### BASIC APP CONFIG
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'
SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
HSTS_ENABLED = False
SERVER_EXTERNAL_SSL = os.getenv('SERVER_EXTERNAL_SSL', True)
SESSION_TYPE = 'sqlalchemy'
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_HTTPONLY = True
#CAPTCHA Config
CAPTCHA_ENABLE = True
CAPTCHA_LENGTH = 6
CAPTCHA_WIDTH = 160
CAPTCHA_HEIGHT = 60
CAPTCHA_LENGTH = 6
CAPTCHA_SESSION_KEY = 'captcha_image'
### DATABASE CONFIG
SQLA_DB_USER = 'pda'
SQLA_DB_PASSWORD = 'changeme'
SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'pda'
CAPTCHA_WIDTH = 160
CSRF_COOKIE_HTTPONLY = True
HSTS_ENABLED = False
PORT = 9191
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'
SAML_ASSERTION_ENCRYPTED = True
SAML_ENABLED = False
SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
SERVER_EXTERNAL_SSL = os.getenv('SERVER_EXTERNAL_SSL', True)
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_TYPE = 'sqlalchemy'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
SQLALCHEMY_TRACK_MODIFICATIONS = True
### DATABASE - MySQL
# SQLA_DB_USER = 'pda'
# SQLA_DB_PASSWORD = 'changeme'
# SQLA_DB_HOST = '127.0.0.1'
# SQLA_DB_NAME = 'pda'
# SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format(
# urllib.parse.quote_plus(SQLA_DB_USER),
# urllib.parse.quote_plus(SQLA_DB_PASSWORD),
# SQLA_DB_HOST,
# SQLA_DB_NAME
# )
### DATABASE - SQLite
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
# SAML Authnetication
SAML_ENABLED = False
SAML_ASSERTION_ENCRYPTED = True

View File

@ -0,0 +1,638 @@
import os
from pathlib import Path
basedir = os.path.abspath(Path(os.path.dirname(__file__)).parent)
class AppSettings(object):
defaults = {
# Flask Settings
'bind_address': '0.0.0.0',
'csrf_cookie_secure': False,
'log_level': 'WARNING',
'port': 9191,
'salt': '$2b$12$yLUMTIfl21FKJQpTkRQXCu',
'secret_key': 'e951e5a1f4b94151b360f47edf596dd2',
'session_cookie_secure': False,
'session_type': 'sqlalchemy',
'sqlalchemy_track_modifications': True,
'sqlalchemy_database_uri': 'sqlite:///' + os.path.join(basedir, 'pdns.db'),
'sqlalchemy_engine_options': {},
# General Settings
'captcha_enable': True,
'captcha_height': 60,
'captcha_length': 6,
'captcha_session_key': 'captcha_image',
'captcha_width': 160,
'mail_server': 'localhost',
'mail_port': 25,
'mail_debug': False,
'mail_use_ssl': False,
'mail_use_tls': False,
'mail_username': '',
'mail_password': '',
'mail_default_sender': '',
'remote_user_enabled': False,
'remote_user_cookies': [],
'remote_user_logout_url': '',
'hsts_enabled': False,
'server_external_ssl': True,
'maintenance': False,
'fullscreen_layout': True,
'record_helper': True,
'login_ldap_first': True,
'default_record_table_size': 15,
'default_domain_table_size': 10,
'auto_ptr': False,
'record_quick_edit': True,
'pretty_ipv6_ptr': False,
'dnssec_admins_only': False,
'allow_user_create_domain': False,
'allow_user_remove_domain': False,
'allow_user_view_history': False,
'custom_history_header': '',
'delete_sso_accounts': False,
'bg_domain_updates': False,
'enable_api_rr_history': True,
'preserve_history': False,
'site_name': 'PowerDNS-Admin',
'site_url': 'http://localhost:9191',
'session_timeout': 10,
'warn_session_timeout': True,
'pdns_api_url': '',
'pdns_api_key': '',
'pdns_api_timeout': 30,
'pdns_version': '4.1.1',
'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,
'pdns_admin_log_level': 'WARNING',
# Local Authentication Settings
'local_db_enabled': True,
'signup_enabled': True,
'pwd_enforce_characters': False,
'pwd_min_len': 10,
'pwd_min_lowercase': 3,
'pwd_min_uppercase': 2,
'pwd_min_digits': 2,
'pwd_min_special': 1,
'pwd_enforce_complexity': False,
'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': False,
# Google OAuth 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 OAuth Settings
'github_oauth_enabled': False,
'github_oauth_key': '',
'github_oauth_secret': '',
'github_oauth_scope': 'email',
'github_oauth_api_url': 'https://api.github.com/user',
'github_oauth_auto_configure': False,
'github_oauth_metadata_url': '',
'github_oauth_token_url': 'https://github.com/login/oauth/access_token',
'github_oauth_authorize_url': 'https://github.com/login/oauth/authorize',
# Azure OAuth Settings
'azure_oauth_enabled': False,
'azure_oauth_key': '',
'azure_oauth_secret': '',
'azure_oauth_scope': 'User.Read openid email profile',
'azure_oauth_api_url': 'https://graph.microsoft.com/v1.0/',
'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': 'displayName',
'azure_group_accounts_name_re': '',
'azure_group_accounts_description': 'description',
'azure_group_accounts_description_re': '',
# OIDC OAuth Settings
'oidc_oauth_enabled': False,
'oidc_oauth_key': '',
'oidc_oauth_secret': '',
'oidc_oauth_scope': 'email',
'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': 'preferred_username',
'oidc_oauth_email': 'email',
'oidc_oauth_firstname': 'given_name',
'oidc_oauth_last_name': 'family_name',
'oidc_oauth_account_name_property': '',
'oidc_oauth_account_description_property': '',
# SAML Authentication Settings
'saml_enabled': False,
'saml_debug': False,
'saml_path': os.path.join(basedir, 'saml'),
'saml_metadata_url': None,
'saml_metadata_cache_lifetime': 1,
'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'saml_idp_entity_id': None,
'saml_nameid_format': None,
'saml_attribute_account': None,
'saml_attribute_email': 'email',
'saml_attribute_givenname': 'givenname',
'saml_attribute_surname': 'surname',
'saml_attribute_name': None,
'saml_attribute_username': None,
'saml_attribute_admin': None,
'saml_attribute_group': None,
'saml_group_admin_name': None,
'saml_group_operator_name': None,
'saml_group_to_account_mapping': None,
'saml_sp_entity_id': None,
'saml_sp_contact_name': None,
'saml_sp_contact_mail': None,
'saml_sign_request': False,
'saml_want_message_signed': True,
'saml_logout': True,
'saml_logout_url': None,
'saml_assertion_encrypted': True,
'saml_cert': None,
'saml_key': None,
# Zone Record Settings
'forward_records_allow_edit': {
'A': True,
'AAAA': True,
'AFSDB': False,
'ALIAS': False,
'CAA': True,
'CERT': False,
'CDNSKEY': False,
'CDS': False,
'CNAME': True,
'DNSKEY': False,
'DNAME': False,
'DS': False,
'HINFO': False,
'KEY': False,
'LOC': True,
'LUA': False,
'MX': True,
'NAPTR': False,
'NS': True,
'NSEC': False,
'NSEC3': False,
'NSEC3PARAM': False,
'OPENPGPKEY': False,
'PTR': True,
'RP': False,
'RRSIG': False,
'SOA': False,
'SPF': True,
'SSHFP': False,
'SRV': True,
'TKEY': False,
'TSIG': False,
'TLSA': False,
'SMIMEA': False,
'TXT': True,
'URI': False
},
'reverse_records_allow_edit': {
'A': False,
'AAAA': False,
'AFSDB': False,
'ALIAS': False,
'CAA': False,
'CERT': False,
'CDNSKEY': False,
'CDS': False,
'CNAME': False,
'DNSKEY': False,
'DNAME': False,
'DS': False,
'HINFO': False,
'KEY': False,
'LOC': True,
'LUA': False,
'MX': False,
'NAPTR': False,
'NS': True,
'NSEC': False,
'NSEC3': False,
'NSEC3PARAM': False,
'OPENPGPKEY': False,
'PTR': True,
'RP': False,
'RRSIG': False,
'SOA': False,
'SPF': False,
'SSHFP': False,
'SRV': False,
'TKEY': False,
'TSIG': False,
'TLSA': False,
'SMIMEA': False,
'TXT': True,
'URI': False
},
}
types = {
# Flask Settings
'bind_address': str,
'csrf_cookie_secure': bool,
'log_level': str,
'port': int,
'salt': str,
'secret_key': str,
'session_cookie_secure': bool,
'session_type': str,
'sqlalchemy_track_modifications': bool,
'sqlalchemy_database_uri': str,
'sqlalchemy_engine_options': dict,
# General Settings
'captcha_enable': bool,
'captcha_height': int,
'captcha_length': int,
'captcha_session_key': str,
'captcha_width': int,
'mail_server': str,
'mail_port': int,
'mail_debug': bool,
'mail_use_ssl': bool,
'mail_use_tls': bool,
'mail_username': str,
'mail_password': str,
'mail_default_sender': str,
'hsts_enabled': bool,
'remote_user_enabled': bool,
'remote_user_cookies': list,
'remote_user_logout_url': str,
'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,
'pdns_admin_log_level': str,
'forward_records_allow_edit': dict,
'reverse_records_allow_edit': dict,
# Local Authentication Settings
'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 Authentication Settings
'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 Settings
'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 Settings
'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 Settings
'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 Settings
'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,
# SAML Authentication Settings
'saml_enabled': bool,
'saml_debug': bool,
'saml_path': str,
'saml_metadata_url': str,
'saml_metadata_cache_lifetime': int,
'saml_idp_sso_binding': str,
'saml_idp_entity_id': str,
'saml_nameid_format': str,
'saml_attribute_account': str,
'saml_attribute_email': str,
'saml_attribute_givenname': str,
'saml_attribute_surname': str,
'saml_attribute_name': str,
'saml_attribute_username': str,
'saml_attribute_admin': str,
'saml_attribute_group': str,
'saml_group_admin_name': str,
'saml_group_operator_name': str,
'saml_group_to_account_mapping': str,
'saml_sp_entity_id': str,
'saml_sp_contact_name': str,
'saml_sp_contact_mail': str,
'saml_sign_request': bool,
'saml_want_message_signed': bool,
'saml_logout': bool,
'saml_logout_url': str,
'saml_assertion_encrypted': bool,
'saml_cert': str,
'saml_key': str,
}
groups = {
'authentication': [
# Local Authentication Settings
'local_db_enabled',
'signup_enabled',
'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 OAuth 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 OAuth 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 OAuth 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 OAuth 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',
]
}
@staticmethod
def convert_type(name, value):
import json
from json import JSONDecodeError
if name in AppSettings.types:
var_type = AppSettings.types[name]
# Handle boolean values
if var_type == bool and isinstance(value, str):
if value.lower() in ['True', 'true', '1'] or value is 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) and isinstance(value, str) and len(value) > 0:
try:
return json.loads(value)
except JSONDecodeError as e:
# Provide backwards compatibility for legacy non-JSON format
value = value.replace("'", '"').replace('True', 'true').replace('False', 'false')
try:
return json.loads(value)
except JSONDecodeError as e:
raise ValueError('Cannot parse json {} for variable {}'.format(value, name))
if var_type == str:
return str(value)
return value
@staticmethod
def load_environment(app):
""" Load app settings from environment variables when defined. """
import os
for var_name, default_value in AppSettings.defaults.items():
env_name = var_name.upper()
current_value = None
if env_name + '_FILE' in os.environ:
if env_name in os.environ:
raise AttributeError(
"Both {} and {} are set but are exclusive.".format(
env_name, env_name + '_FILE'))
with open(os.environ[env_name + '_FILE']) as f:
current_value = f.read()
f.close()
elif env_name in os.environ:
current_value = os.environ[env_name]
if current_value is not None:
app.config[env_name] = AppSettings.convert_type(var_name, current_value)

View File

@ -1,12 +1,10 @@
import sys
import traceback
import pytimeparse
from ast import literal_eval
from distutils.util import strtobool
from flask import current_app
from .base import db
from powerdnsadmin.lib.settings import AppSettings
class Setting(db.Model):
@ -14,437 +12,8 @@ class Setting(db.Model):
name = db.Column(db.String(64), unique=True, index=True)
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 = {
# General Settings
'maintenance': False,
'fullscreen_layout': True,
'record_helper': True,
'login_ldap_first': True,
'default_record_table_size': 15,
'default_domain_table_size': 10,
'auto_ptr': False,
'record_quick_edit': True,
'pretty_ipv6_ptr': False,
'dnssec_admins_only': False,
'allow_user_create_domain': False,
'allow_user_remove_domain': False,
'allow_user_view_history': False,
'custom_history_header': '',
'delete_sso_accounts': False,
'bg_domain_updates': False,
'enable_api_rr_history': True,
'preserve_history': False,
'site_name': 'PowerDNS-Admin',
'site_url': 'http://localhost:9191',
'session_timeout': 10,
'warn_session_timeout': True,
'pdns_api_url': '',
'pdns_api_key': '',
'pdns_api_timeout': 30,
'pdns_version': '4.1.1',
'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,
'signup_enabled': True,
'pwd_enforce_characters': False,
'pwd_min_len': 10,
'pwd_min_lowercase': 3,
'pwd_min_uppercase': 2,
'pwd_min_digits': 2,
'pwd_min_special': 1,
'pwd_enforce_complexity': False,
'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': 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_key': '',
'github_oauth_secret': '',
'github_oauth_scope': 'email',
'github_oauth_api_url': 'https://api.github.com/user',
'github_oauth_auto_configure': False,
'github_oauth_metadata_url': '',
'github_oauth_token_url': 'https://github.com/login/oauth/access_token',
'github_oauth_authorize_url': 'https://github.com/login/oauth/authorize',
# Azure OAuth2 Settings
'azure_oauth_enabled': False,
'azure_oauth_key': '',
'azure_oauth_secret': '',
'azure_oauth_scope': 'User.Read openid email profile',
'azure_oauth_api_url': 'https://graph.microsoft.com/v1.0/',
'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': 'displayName',
'azure_group_accounts_name_re': '',
'azure_group_accounts_description': 'description',
'azure_group_accounts_description_re': '',
# OIDC OAuth2 Settings
'oidc_oauth_enabled': False,
'oidc_oauth_key': '',
'oidc_oauth_secret': '',
'oidc_oauth_scope': 'email',
'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': 'preferred_username',
'oidc_oauth_email': 'email',
'oidc_oauth_firstname': 'given_name',
'oidc_oauth_last_name': 'family_name',
'oidc_oauth_account_name_property': '',
'oidc_oauth_account_description_property': '',
# Zone Record Settings
'forward_records_allow_edit': {
'A': True,
'AAAA': True,
'AFSDB': False,
'ALIAS': False,
'CAA': True,
'CERT': False,
'CDNSKEY': False,
'CDS': False,
'CNAME': True,
'DNSKEY': False,
'DNAME': False,
'DS': False,
'HINFO': False,
'KEY': False,
'LOC': True,
'LUA': False,
'MX': True,
'NAPTR': False,
'NS': True,
'NSEC': False,
'NSEC3': False,
'NSEC3PARAM': False,
'OPENPGPKEY': False,
'PTR': True,
'RP': False,
'RRSIG': False,
'SOA': False,
'SPF': True,
'SSHFP': False,
'SRV': True,
'TKEY': False,
'TSIG': False,
'TLSA': False,
'SMIMEA': False,
'TXT': True,
'URI': False
},
'reverse_records_allow_edit': {
'A': False,
'AAAA': False,
'AFSDB': False,
'ALIAS': False,
'CAA': False,
'CERT': False,
'CDNSKEY': False,
'CDS': False,
'CNAME': False,
'DNSKEY': False,
'DNAME': False,
'DS': False,
'HINFO': False,
'KEY': False,
'LOC': True,
'LUA': False,
'MX': False,
'NAPTR': False,
'NS': True,
'NSEC': False,
'NSEC3': False,
'NSEC3PARAM': False,
'OPENPGPKEY': False,
'PTR': True,
'RP': False,
'RRSIG': False,
'SOA': False,
'SPF': False,
'SSHFP': False,
'SRV': False,
'TKEY': False,
'TSIG': False,
'TLSA': False,
'SMIMEA': False,
'TXT': True,
'URI': False
},
}
groups = {
'authentication': [
# Local Authentication Settings
'local_db_enabled',
'signup_enabled',
'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',
]
}
ZONE_TYPE_FORWARD = 'forward'
ZONE_TYPE_REVERSE = 'reverse'
def __init__(self, id=None, name=None, value=None):
self.id = id
@ -457,44 +26,12 @@ class Setting(db.Model):
self.name = name
self.value = value
def convert_type(self, name, value):
import json
from json import JSONDecodeError
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 is 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) and isinstance(value, str) and len(value) > 0:
try:
return json.loads(value)
except JSONDecodeError as e:
pass
if var_type == str:
return str(value)
return value
def set_maintenance(self, mode):
maintenance = Setting.query.filter(
Setting.name == 'maintenance').first()
if maintenance is None:
value = self.defaults['maintenance']
value = AppSettings.defaults['maintenance']
maintenance = Setting(name='maintenance', value=str(value))
db.session.add(maintenance)
@ -516,7 +53,7 @@ class Setting(db.Model):
current_setting = Setting.query.filter(Setting.name == setting).first()
if current_setting is None:
value = self.defaults[setting]
value = AppSettings.defaults[setting]
current_setting = Setting(name=setting, value=str(value))
db.session.add(current_setting)
@ -535,27 +72,30 @@ class Setting(db.Model):
return False
def set(self, setting, value):
import json
current_setting = Setting.query.filter(Setting.name == setting).first()
if current_setting is None:
current_setting = Setting(name=setting, value=None)
db.session.add(current_setting)
value = str(self.convert_type(setting, value))
value = AppSettings.convert_type(setting, value)
if isinstance(value, dict) or isinstance(value, list):
value = json.dumps(value)
try:
current_setting.value = value
db.session.commit()
return True
except Exception as e:
current_app.logger.error('Cannot edit setting {0}. DETAIL: {1}'.format(
setting, e))
current_app.logger.error('Cannot edit setting {0}. DETAIL: {1}'.format(setting, e))
current_app.logger.debug(traceback.format_exec())
db.session.rollback()
return False
def get(self, setting):
if setting in self.defaults:
if setting in AppSettings.defaults:
if setting.upper() in current_app.config:
result = current_app.config[setting.upper()]
@ -566,51 +106,45 @@ class Setting(db.Model):
if hasattr(result, 'value'):
result = result.value
return self.convert_type(setting, result)
return AppSettings.convert_type(setting, result)
else:
return self.defaults[setting]
return AppSettings.defaults[setting]
else:
current_app.logger.error('Unknown setting queried: {0}'.format(setting))
def get_group(self, group):
if not isinstance(group, list):
group = self.groups[group]
group = AppSettings.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)
for var_name, default_value in AppSettings.defaults.items():
if var_name in group:
result[var_name] = self.get(var_name)
return result
def get_records_allow_to_edit(self):
return list(
set(self.get_forward_records_allow_to_edit() +
self.get_reverse_records_allow_to_edit()))
set(self.get_supported_record_types(self.ZONE_TYPE_FORWARD) +
self.get_supported_record_types(self.ZONE_TYPE_REVERSE)))
def get_forward_records_allow_to_edit(self):
records = self.get('forward_records_allow_edit')
f_records = literal_eval(records) if isinstance(records,
str) else records
r_name = [r for r in f_records if f_records[r]]
# Sort alphabetically if python version is smaller than 3.6
if sys.version_info[0] < 3 or (sys.version_info[0] == 3
and sys.version_info[1] < 6):
r_name.sort()
return r_name
def get_supported_record_types(self, zone_type):
setting_value = []
if zone_type == self.ZONE_TYPE_FORWARD:
setting_value = self.get('forward_records_allow_edit')
elif zone_type == self.ZONE_TYPE_REVERSE:
setting_value = self.get('reverse_records_allow_edit')
records = literal_eval(setting_value) if isinstance(setting_value, str) else setting_value
types = [r for r in records if records[r]]
def get_reverse_records_allow_to_edit(self):
records = self.get('reverse_records_allow_edit')
r_records = literal_eval(records) if isinstance(records,
str) else records
r_name = [r for r in r_records if r_records[r]]
# Sort alphabetically if python version is smaller than 3.6
if sys.version_info[0] < 3 or (sys.version_info[0] == 3
and sys.version_info[1] < 6):
r_name.sort()
return r_name
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
types.sort()
return types
def get_ttl_options(self):
return [(pytimeparse.parse(ttl), ttl)

View File

@ -128,14 +128,17 @@ def extract_changelogs_from_history(histories, record_name=None, record_type=Non
# filter only the records with the specific record_name, record_type
if record_name != None and record_type != None:
details['add_rrsets'] = list(filter_rr_list_by_name_and_type(details['add_rrsets'], record_name, record_type))
details['del_rrsets'] = list(filter_rr_list_by_name_and_type(details['del_rrsets'], record_name, record_type))
details['add_rrsets'] = list(
filter_rr_list_by_name_and_type(details['add_rrsets'], record_name, record_type))
details['del_rrsets'] = list(
filter_rr_list_by_name_and_type(details['del_rrsets'], record_name, record_type))
if not details['add_rrsets'] and not details['del_rrsets']:
continue
# same record name and type RR are being deleted and created in same entry.
del_add_changes = set([(r['name'], r['type']) for r in details['add_rrsets']]).intersection([(r['name'], r['type']) for r in details['del_rrsets']])
del_add_changes = set([(r['name'], r['type']) for r in details['add_rrsets']]).intersection(
[(r['name'], r['type']) for r in details['del_rrsets']])
for del_add_change in del_add_changes:
changes.append(HistoryRecordEntry(
entry,
@ -1271,7 +1274,8 @@ def history_table(): # ajax call data
)
).order_by(History.created_on.desc()) \
.limit(lim).all()
elif user_name != None and current_user.role.name in ['Administrator', 'Operator']: # only admins can see the user login-logouts
elif user_name != None and current_user.role.name in ['Administrator',
'Operator']: # only admins can see the user login-logouts
histories = base_query.filter(
db.and_(
@ -1296,7 +1300,8 @@ def history_table(): # ajax call data
temp.append(h)
break
histories = temp
elif (changed_by != None or max_date != None) and current_user.role.name in ['Administrator', 'Operator']: # select changed by and date filters only
elif (changed_by != None or max_date != None) and current_user.role.name in ['Administrator',
'Operator']: # select changed by and date filters only
histories = base_query.filter(
db.and_(
History.created_on <= max_date if max_date != None else True,
@ -1305,7 +1310,8 @@ def history_table(): # ajax call data
)
) \
.order_by(History.created_on.desc()).limit(lim).all()
elif (changed_by != None or max_date != None): # special filtering for user because one user does not have access to log-ins logs
elif (
changed_by != None or max_date != None): # special filtering for user because one user does not have access to log-ins logs
histories = base_query.filter(
db.and_(
History.created_on <= max_date if max_date != None else True,
@ -1396,7 +1402,7 @@ def setting_basic_edit(setting):
new_value = jdata['value']
result = Setting().set(setting, new_value)
if (result):
if result:
return make_response(
jsonify({
'status': 'ok',
@ -1460,52 +1466,29 @@ def setting_pdns():
@login_required
@operator_role_required
def setting_records():
from powerdnsadmin.lib.settings import AppSettings
if request.method == 'GET':
_fr = Setting().get('forward_records_allow_edit')
_rr = Setting().get('reverse_records_allow_edit')
f_records = literal_eval(_fr) if isinstance(_fr, str) else _fr
r_records = literal_eval(_rr) if isinstance(_rr, str) else _rr
forward_records = Setting().get('forward_records_allow_edit')
reverse_records = Setting().get('reverse_records_allow_edit')
return render_template('admin_setting_records.html',
f_records=f_records,
r_records=r_records)
f_records=forward_records,
r_records=reverse_records)
elif request.method == 'POST':
fr = {}
rr = {}
records = Setting().defaults['forward_records_allow_edit']
records = AppSettings.defaults['forward_records_allow_edit']
for r in records:
fr[r] = True if request.form.get('fr_{0}'.format(
r.lower())) else False
rr[r] = True if request.form.get('rr_{0}'.format(
r.lower())) else False
Setting().set('forward_records_allow_edit', str(fr))
Setting().set('reverse_records_allow_edit', str(rr))
Setting().set('forward_records_allow_edit', json.dumps(fr))
Setting().set('reverse_records_allow_edit', json.dumps(rr))
return redirect(url_for('admin.setting_records'))
def has_an_auth_method(local_db_enabled=None,
ldap_enabled=None,
google_oauth_enabled=None,
github_oauth_enabled=None,
oidc_oauth_enabled=None,
azure_oauth_enabled=None):
if local_db_enabled is None:
local_db_enabled = Setting().get('local_db_enabled')
if ldap_enabled is None:
ldap_enabled = Setting().get('ldap_enabled')
if google_oauth_enabled is None:
google_oauth_enabled = Setting().get('google_oauth_enabled')
if github_oauth_enabled is None:
github_oauth_enabled = Setting().get('github_oauth_enabled')
if oidc_oauth_enabled is None:
oidc_oauth_enabled = Setting().get('oidc_oauth_enabled')
if azure_oauth_enabled is None:
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
@admin_bp.route('/setting/authentication', methods=['GET', 'POST'])
@login_required
@admin_role_required
@ -1517,6 +1500,7 @@ def setting_authentication():
@login_required
@admin_role_required
def setting_authentication_api():
from powerdnsadmin.lib.settings import AppSettings
result = {'status': 1, 'messages': [], 'data': {}}
if request.form.get('commit') == '1':
@ -1524,7 +1508,7 @@ def setting_authentication_api():
data = json.loads(request.form.get('data'))
for key, value in data.items():
if key in model.groups['authentication']:
if key in AppSettings.groups['authentication']:
model.set(key, value)
result['data'] = Setting().get_group('authentication')

View File

@ -72,9 +72,9 @@ def domain(domain_name):
quick_edit = Setting().get('record_quick_edit')
records_allow_to_edit = Setting().get_records_allow_to_edit()
forward_records_allow_to_edit = Setting(
).get_forward_records_allow_to_edit()
).get_supported_record_types(Setting().ZONE_TYPE_FORWARD)
reverse_records_allow_to_edit = Setting(
).get_reverse_records_allow_to_edit()
).get_supported_record_types(Setting().ZONE_TYPE_REVERSE)
ttl_options = Setting().get_ttl_options()
records = []