powerdns-admin: 0.4.2 -> 0.6.1 (#549053)

This commit is contained in:
Gaétan Lepage
2026-08-07 22:34:39 +00:00
committed by GitHub
10 changed files with 48 additions and 302 deletions

View File

@@ -132,13 +132,13 @@ let
# Login
# Outputs 'Redirecting' if successful
curl -sSfb session http://127.0.0.1:8000/login \
curl -sSf -b session -c session http://127.0.0.1:8000/login \
-F "_csrf_token=$csrf_token" \
-F "username=user" \
-F "password=password" | grep Redirecting
# Check that we are logged in, this redirects to /admin/setting/pdns if we are
curl -sSfb session http://127.0.0.1:8000/dashboard/ | grep /admin/setting
curl -sSf -b session -c session http://127.0.0.1:8000/dashboard/ | grep /admin/setting
'';
};
unix = {

View File

@@ -1,25 +0,0 @@
From 29b58e29c813d9bf0b31139a19b556614c28638e Mon Sep 17 00:00:00 2001
From: Flakebi <flakebi@t-online.de>
Date: Sat, 2 Dec 2023 16:26:22 +0100
Subject: [PATCH 1/6] Fix flask 2.3 issue
'Blueprint' object has no attribute 'before_app_first_request'
---
powerdnsadmin/routes/index.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py
index d56ce61..2176bd6 100644
--- a/powerdnsadmin/routes/index.py
+++ b/powerdnsadmin/routes/index.py
@@ -46,7 +46,6 @@ index_bp = Blueprint('index',
url_prefix='/')
-@index_bp.before_app_first_request
def register_modules():
global google
global github
--
2.42.0

View File

@@ -1,34 +1,25 @@
From c60a9658fe2ca429327680fbffb86f609d98c52c Mon Sep 17 00:00:00 2001
From: Flakebi <flakebi@t-online.de>
Date: Sat, 2 Dec 2023 16:27:49 +0100
Subject: [PATCH 2/6] Remove cssrewrite filter
---
powerdnsadmin/assets.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/powerdnsadmin/assets.py b/powerdnsadmin/assets.py
index d46d431..3c582be 100644
index b339c0e..6cf8087 100644
--- a/powerdnsadmin/assets.py
+++ b/powerdnsadmin/assets.py
@@ -13,7 +13,7 @@ css_login = Bundle(
@@ -50,7 +50,7 @@ class ModernBrowserCssFilter(Filter):
css_login = Bundle(
'node_modules/@fortawesome/fontawesome-free/css/all.css',
'node_modules/icheck/skins/square/blue.css',
'node_modules/admin-lte/dist/css/adminlte.css',
- filters=('rcssmin', 'cssrewrite'),
+ filters=('rcssmin'),
- filters=(ModernBrowserCssFilter, 'rcssmin', 'cssrewrite'),
+ filters=(ModernBrowserCssFilter, 'rcssmin'),
output='generated/login.css')
js_login = Bundle(
@@ -37,7 +37,7 @@ css_main = Bundle(
@@ -66,7 +66,7 @@ css_main = Bundle(
'node_modules/admin-lte/dist/css/adminlte.css',
'node_modules/datatables.net-bs5/css/dataTables.bootstrap5.css',
'custom/css/custom.css',
'node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.css',
- filters=('rcssmin', 'cssrewrite'),
+ filters=('rcssmin'),
- filters=(ModernBrowserCssFilter, 'rcssmin', 'cssrewrite'),
+ filters=(ModernBrowserCssFilter, 'rcssmin'),
output='generated/main.css')
js_main = Bundle(
--
2.42.0
2.43.0

View File

@@ -1,25 +0,0 @@
From 8c320a34bcca6dc2c1b423a1445235bf178b653e Mon Sep 17 00:00:00 2001
From: Flakebi <flakebi@t-online.de>
Date: Sat, 2 Dec 2023 16:31:02 +0100
Subject: [PATCH 3/6] Fix flask-migrate 4.0 compatibility
See https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues/1376
---
migrations/env.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/migrations/env.py b/migrations/env.py
index 4742e14..739d753 100755
--- a/migrations/env.py
+++ b/migrations/env.py
@@ -73,7 +73,6 @@ def run_migrations_online():
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
- render_as_batch=config.get_main_option('sqlalchemy.url').startswith('sqlite:'),
**current_app.extensions['migrate'].configure_args)
try:
--
2.42.0

View File

@@ -1,26 +0,0 @@
From 4b4ac26ef1cbb0b5b2354c251b216498325d0411 Mon Sep 17 00:00:00 2001
From: Flakebi <flakebi@t-online.de>
Date: Sat, 2 Dec 2023 16:31:50 +0100
Subject: [PATCH 4/6] Fix flask-session and powerdns-admin compatibility
flask-session and powerdns-admin both try to add sqlalchemy to flask.
Reuse the database for flask-session.
---
powerdnsadmin/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/powerdnsadmin/__init__.py b/powerdnsadmin/__init__.py
index d447a00..653af33 100755
--- a/powerdnsadmin/__init__.py
+++ b/powerdnsadmin/__init__.py
@@ -60,6 +60,7 @@ def create_app(config=None):
if 'SESSION_TYPE' in os.environ:
app.config['SESSION_TYPE'] = os.environ.get('SESSION_TYPE')
+ app.config['SESSION_SQLALCHEMY'] = models.base.db
sess = Session(app)
# create sessions table if using sqlalchemy backend
--
2.42.0

View File

@@ -1,58 +0,0 @@
diff --git a/powerdnsadmin/__init__.py b/powerdnsadmin/__init__.py
index 660f96b..4d45c36 100755
--- a/powerdnsadmin/__init__.py
+++ b/powerdnsadmin/__init__.py
@@ -72,10 +72,16 @@ def create_app(config=None):
# SMTP
app.mail = Mail(app)
+ from powerdnsadmin.routes.index import register_modules
+
# Load app's components
assets.init_app(app)
models.init_app(app)
- routes.init_app(app)
+
+ with app.app_context():
+ register_modules()
+ routes.init_app(app)
+
services.init_app(app)
# Register filters
diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py
index 0918261..870d824 100644
--- a/powerdnsadmin/routes/index.py
+++ b/powerdnsadmin/routes/index.py
@@ -52,11 +52,16 @@ def register_modules():
global azure
global oidc
global saml
- google = google_oauth()
- github = github_oauth()
- azure = azure_oauth()
- oidc = oidc_oauth()
- saml = SAML()
+
+ try:
+ google = google_oauth()
+ github = github_oauth()
+ azure = azure_oauth()
+ oidc = oidc_oauth()
+ saml = SAML()
+ except Exception:
+ # Database not ready yet, will initialize on first request
+ pass
@index_bp.before_request
@@ -65,6 +70,9 @@ def before_request():
g.user = current_user
login_manager.anonymous_user = Anonymous
+ if google is None:
+ register_modules()
+
# Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [

View File

@@ -1,46 +0,0 @@
diff --git a/powerdnsadmin/lib/utils.py b/powerdnsadmin/lib/utils.py
index f8cc997..0de4c5c 100644
--- a/powerdnsadmin/lib/utils.py
+++ b/powerdnsadmin/lib/utils.py
@@ -121,7 +121,7 @@ def display_record_name(data):
if record_name == domain_name:
return '@'
else:
- return re.sub('\.{}$'.format(domain_name), '', record_name)
+ return re.sub(r'\.{}$'.format(domain_name), '', record_name)
def display_master_name(data):
diff --git a/powerdnsadmin/models/domain.py b/powerdnsadmin/models/domain.py
index f0b9a30..84c6d1b 100644
--- a/powerdnsadmin/models/domain.py
+++ b/powerdnsadmin/models/domain.py
@@ -482,24 +482,24 @@ class Domain(db.Model):
if re.search('ip6.arpa', reverse_host_address):
for i in range(1, 32, 1):
address = re.search(
- '((([a-f0-9]\.){' + str(i) + '})(?P<ipname>.+6.arpa)\.?)',
+ r'((([a-f0-9]\.){' + str(i) + r'})(?P<ipname>.+6\.arpa)\.?)',
reverse_host_address)
if None != self.get_id_by_name(address.group('ipname')):
c = i
break
return re.search(
- '((([a-f0-9]\.){' + str(c) + '})(?P<ipname>.+6.arpa)\.?)',
+ r'((([a-f0-9]\.){' + str(c) + r'})(?P<ipname>.+6\.arpa)\.?)',
reverse_host_address).group('ipname')
else:
for i in range(1, 4, 1):
address = re.search(
- '((([0-9]+\.){' + str(i) + '})(?P<ipname>.+r.arpa)\.?)',
+ r'((([0-9]+\.){' + str(i) + r'})(?P<ipname>.+r\.arpa)\.?)',
reverse_host_address)
if None != self.get_id_by_name(address.group('ipname')):
c = i
break
return re.search(
- '((([0-9]+\.){' + str(c) + '})(?P<ipname>.+r.arpa)\.?)',
+ r'((([0-9]+\.){' + str(c) + r'})(?P<ipname>.+r\.arpa)\.?)',
reverse_host_address).group('ipname')
def delete(self, domain_name):

View File

@@ -1,48 +0,0 @@
diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py
index 23d88bb..edfab3f 100644
--- a/powerdnsadmin/routes/index.py
+++ b/powerdnsadmin/routes/index.py
@@ -392,11 +392,38 @@ def login():
return authenticate_user(user, 'Azure OAuth')
if 'oidc_token' in session:
- user_data = json.loads(oidc.get('userinfo').text)
- oidc_username = user_data[Setting().get('oidc_oauth_username')]
- oidc_first_name = user_data[Setting().get('oidc_oauth_firstname')]
- oidc_last_name = user_data[Setting().get('oidc_oauth_last_name')]
- oidc_email = user_data[Setting().get('oidc_oauth_email')]
+ try:
+ oidc_metadata = oidc.load_server_metadata()
+ except Exception as e:
+ current_app.logger.warning(
+ 'OIDC: unable to load server metadata ({}); '
+ 'falling back to relative userinfo endpoint'.format(e))
+ oidc_metadata = {}
+
+ userinfo_endpoint = oidc_metadata.get('userinfo_endpoint')
+ try:
+ if userinfo_endpoint:
+ userinfo_resp = oidc.get(userinfo_endpoint, timeout=15)
+ else:
+ userinfo_resp = oidc.get('userinfo', timeout=15)
+ userinfo_resp.raise_for_status()
+ user_data = userinfo_resp.json()
+ except Exception as e:
+ current_app.logger.error('OIDC: failed to fetch userinfo: {}'.format(e))
+ session.pop('oidc_token', None)
+ return redirect(url_for('index.login'))
+
+ oidc_username = user_data.get(Setting().get('oidc_oauth_username'))
+ oidc_first_name = user_data.get(Setting().get('oidc_oauth_firstname'), '')
+ oidc_last_name = user_data.get(Setting().get('oidc_oauth_last_name'), '')
+ oidc_email = user_data.get(Setting().get('oidc_oauth_email'), '')
+
+ if not oidc_username:
+ current_app.logger.error(
+ 'OIDC: username claim "{}" not present in userinfo'.format(
+ Setting().get('oidc_oauth_username')))
+ session.pop('oidc_token', None)
+ return redirect(url_for('index.login'))
user = User.query.filter_by(username=oidc_username).first()
if not user:

View File

@@ -1,30 +0,0 @@
diff --git a/powerdnsadmin/models/user.py b/powerdnsadmin/models/user.py
index 42f894f..f9d845e 100644
--- a/powerdnsadmin/models/user.py
+++ b/powerdnsadmin/models/user.py
@@ -435,7 +435,7 @@ class User(db.Model):
name='Administrator').first().id
if hasattr(self, "plain_text_password"):
- if self.plain_text_password != None:
+ if self.plain_text_password:
self.password = self.get_hashed_password(
self.plain_text_password)
else:
@@ -476,7 +476,7 @@ class User(db.Model):
# store new password hash (only if changed)
if hasattr(self, "plain_text_password"):
- if self.plain_text_password != None:
+ if self.plain_text_password:
user.password = self.get_hashed_password(
self.plain_text_password).decode("utf-8")
@@ -495,7 +495,7 @@ class User(db.Model):
user.lastname = self.lastname if self.lastname else user.lastname
if hasattr(self, "plain_text_password"):
- if self.plain_text_password != None:
+ if self.plain_text_password:
user.password = self.get_hashed_password(
self.plain_text_password).decode("utf-8")

View File

@@ -2,23 +2,29 @@
lib,
stdenv,
fetchFromGitHub,
fetchYarnDeps,
yarnConfigHook,
yarn-berry_4-fetcher,
nixosTests,
writeText,
runCommand,
python3,
}:
let
pname = "powerdns-admin";
version = "0.4.2";
version = "0.6.1";
src = fetchFromGitHub {
owner = "PowerDNS-Admin";
repo = "PowerDNS-Admin";
tag = "v${version}";
hash = "sha256-q9mt8wjSNFb452Xsg+qhNOWa03KJkYVGAeCWVSzZCyk=";
hash = "sha256-VhUz3Uw2MKN7rJgCrFd5nSN8FVTHM6LHCc01scMNjbc=";
};
inherit (yarn-berry_4-fetcher) fetchYarnBerryDeps yarnBerryConfigHook;
yarnLock = runCommand "yarn-v9.lock" { } ''
sed -e 's/^ version: 8$/ version: 9/' ${src}/yarn.lock > $out
'';
python = python3;
pythonDeps = with python.pkgs; [
@@ -69,36 +75,50 @@ let
standard-imghdr
];
all_patches = [
./0001-Fix-flask-2.3-issue.patch
];
assets = stdenv.mkDerivation {
pname = "${pname}-assets";
inherit version src;
offlineCache = fetchYarnDeps {
yarnLock = "${src}/yarn.lock";
hash = "sha256-rXIts+dgOuZQGyiSke1NIG7b4lFlR/Gfu3J6T3wP3aY=";
offlineCache = fetchYarnBerryDeps {
inherit yarnLock;
hash = "sha256-VVew6/rjc0Uz6xM2komL9Sceym3vFrGnAqrdLtGxQVI=";
};
postPatch = ''
cp ${yarnLock} yarn.lock
# flask-assets needs a real node_modules tree
printf 'nodeLinker: node-modules\n' >> .yarnrc.yml
'';
nativeBuildInputs = [
yarnConfigHook
yarnBerryConfigHook
]
++ pythonDeps;
patches = all_patches ++ [
patches = [
./0002-Remove-cssrewrite-filter.patch
];
buildPhase = ''
runHook preBuild
if [ -d node_modules ] && [ ! -d powerdnsadmin/static/node_modules ]; then
mv node_modules powerdnsadmin/static/node_modules
fi
SESSION_TYPE=filesystem FLASK_APP=./powerdnsadmin/__init__.py flask assets build
runHook postBuild
'';
installPhase = ''
runHook preInstall
# https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/54b257768f600c5548a1c7e50eac49c40df49f92/docker/Dockerfile#L43
mkdir $out
cp -r powerdnsadmin/static/{generated,assets,img} $out
find powerdnsadmin/static/node_modules -name webfonts -exec cp -r {} $out \; -printf "Copying %P\n"
find powerdnsadmin/static/node_modules -name fonts -exec cp -r {} $out \; -printf "Copying %P\n"
find powerdnsadmin/static/node_modules/icheck/skins/square -name '*.png' -exec cp {} $out/generated \;
runHook postInstall
'';
};
@@ -128,15 +148,6 @@ stdenv.mkDerivation {
exec python -m gunicorn.app.wsgiapp "powerdnsadmin:create_app()" "$@"
'';
patches = all_patches ++ [
./0003-Fix-flask-migrate-4.0-compatibility.patch
./0004-Fix-flask-session-and-powerdns-admin-compatibility.patch
./0005-Fix-app-context-and-register-modules.patch
./0006-Fix-regex.patch
./0007-Fix-oidc.patch
./0008-Fix-profile-save-overwriting-password-with-empty-val.patch
];
postPatch = ''
rm -r powerdnsadmin/static powerdnsadmin/assets.py
'';
@@ -162,6 +173,8 @@ stdenv.mkDerivation {
runHook postInstall
'';
__darwinAllowLocalNetworking = true;
passthru = {
# PYTHONPATH of all dependencies used by the package
pythonPath = python3.pkgs.makePythonPath pythonDeps;