5
0
mirror of https://github.com/cwinfo/powerdns-admin.git synced 2025-04-28 06:08:52 +00:00

Compare commits

...

793 Commits

Author SHA1 Message Date
Matt Scott
fa4861a6ed
Update README.md
Removed Azorian Solutions branding from project README.
2024-06-04 13:55:56 -04:00
Matt Scott
d255cb3d16
Updating master branch to the latest release of 0.4.2 () 2024-01-31 16:40:41 -05:00
Matt Scott
af462a9bae
Disabling Mega Linter for all recognized branch patterns. 2024-01-31 16:32:41 -05:00
Matt Scott
b47b080692
Merged the latest from the branch. 2024-01-31 16:29:59 -05:00
Matt Scott
876bc78ba7
Updated NPM dependencies (cryto-js) () 2024-01-31 16:10:31 -05:00
Matt Scott
9c457f1db0
Updated the following NPM dependencies:
- crypto-js from `4.1.1` to `4.2.0`
2024-01-31 16:08:37 -05:00
Matt Scott
077f893e41
Bump crypto-js from 4.1.1 to 4.2.0 () 2024-01-31 16:00:17 -05:00
dependabot[bot]
51bdeca218
Bump crypto-js from 4.1.1 to 4.2.0
Bumps [crypto-js](https://github.com/brix/crypto-js) from 4.1.1 to 4.2.0.
- [Commits](https://github.com/brix/crypto-js/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: crypto-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 20:59:09 +00:00
Matt Scott
3247869df9
Updated Pip Dependencies (Jinaj2, certifi, cryptography, requests, werkzeug) () 2024-01-31 15:57:16 -05:00
Matt Scott
7998dd80c9
Managed to complete the following pip dependency changes:
- Jinaj2 - `3.1.3` - upgrade from `3.1.2`
- certifi - `2023.11.17` - downgrade from `2023.12.17`
- cryptography - `42.0.2` - upgrade from `39.0.2`
- requests - `2.31.0` - upgrade from `2.28.2`
- werkzeug - `2.3.8` - upgrade from `2.2.3`
2024-01-31 15:54:58 -05:00
Matt Scott
4b57254ae4
Bump jinja2 from 3.1.2 to 3.1.3 () 2024-01-31 15:23:19 -05:00
Matt Scott
2a1f8484e5
Bump sqlalchemy from 1.3.24 to 1.4.51 () 2024-01-31 15:22:28 -05:00
Matt Scott
5904be885a
Bump pytest from 7.2.1 to 7.4.4 () 2024-01-31 15:21:09 -05:00
dependabot[bot]
fdc1ba59e7
Bump sqlalchemy from 1.3.24 to 1.4.51
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.24 to 1.4.51.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: sqlalchemy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 20:20:34 +00:00
Matt Scott
aa4d97da9c
Bump bcrypt from 4.0.1 to 4.1.2 () 2024-01-31 15:19:49 -05:00
Matt Scott
ecdbfce256
Bump mysqlclient from 2.0.1 to 2.2.1 () 2024-01-31 15:19:02 -05:00
dependabot[bot]
eb3243a075
Bump jinja2 from 3.1.2 to 3.1.3
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-11 20:33:31 +00:00
dependabot[bot]
69dda3a5de
Bump pytest from 7.2.1 to 7.4.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.1 to 7.4.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.2.1...7.4.4)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 07:37:17 +00:00
dependabot[bot]
e3e773cc85
Bump bcrypt from 4.0.1 to 4.1.2
Bumps [bcrypt](https://github.com/pyca/bcrypt) from 4.0.1 to 4.1.2.
- [Changelog](https://github.com/pyca/bcrypt/blob/main/release.py)
- [Commits](https://github.com/pyca/bcrypt/compare/4.0.1...4.1.2)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 07:08:40 +00:00
dependabot[bot]
577b350916
Bump mysqlclient from 2.0.1 to 2.2.1
Bumps [mysqlclient](https://github.com/PyMySQL/mysqlclient) from 2.0.1 to 2.2.1.
- [Release notes](https://github.com/PyMySQL/mysqlclient/releases)
- [Changelog](https://github.com/PyMySQL/mysqlclient/blob/main/HISTORY.rst)
- [Commits](https://github.com/PyMySQL/mysqlclient/compare/v2.0.1...v2.2.1)

---
updated-dependencies:
- dependency-name: mysqlclient
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 07:12:28 +00:00
Matt Scott
8532ca5368
Added support for application to run in sub-paths while not breaking the Docker health check () 2023-12-11 08:38:36 -05:00
Matt Scott
9415b4663f
Applied the latest suggestion from @ashneilson which appears to be a good fix this time. 2023-12-11 08:36:37 -05:00
Matt Scott
bbe8d69345
Fixing new LDAP search filter cleansing mechanism to only target user DN value returned on AD connections () 2023-12-08 06:53:31 -05:00
Matt Scott
59a32a148f
Corrected a mistake with the new LDAP search filter cleansing that broke LDAP altogether. Moved the filtering to only target the user DN with Active Directory LDAP connections. 2023-12-08 06:17:34 -05:00
Matt Scott
11371e1b81
Merge zone editor record action controls into single column () 2023-12-08 05:16:08 -05:00
Matt Scott
3caded9b7f
Added LDAP search filter cleansing mechanism to properly escape special characters () 2023-12-08 04:59:23 -05:00
Matt Scott
7b6aafbb2c
Adding LDAP search filter cleansing mechanism to account for special characters that need replaced in LDAP search queries. 2023-12-08 04:53:52 -05:00
Matt Scott
06fa9537a8
Updated project documentation to better control the flow of new issue submission attempts.
Updated project README to include clear notice of the latest update for the project freeze.
2023-11-29 15:21:31 -05:00
Matt Scott
17e6adb8a7
Updating the issue configuration to disable the submission of new issues of all types. 2023-11-25 08:17:56 -05:00
Matt Scott
5d4e560836
Updating the issue configuration to disable the submission of new issues of all types. 2023-11-25 08:17:37 -05:00
Matt Scott
6f47cbd91b
Updating the issue configuration to disable the submission of new issues of all types. 2023-11-25 08:16:26 -05:00
Matt Scott
66c262c57d
Adding latest project announcements to repository. 2023-11-25 08:09:46 -05:00
Matt Scott
ddb3151b61
Correcting bug introduced by PR 1658. 2023-11-24 11:14:09 -05:00
Matt Scott
b494423e83
Fix zone name encoding for UI XHR requests as well as requests to the PDNS API () 2023-11-24 10:02:46 -05:00
Matt Scott
18f38fd1ca
Updated backend to properly encode the zone name sent to PDNS API so that zones with URL unsafe characters don't break the request. 2023-11-24 09:49:40 -05:00
Matt Scott
48f7f2d19f
Merge branch 'dev' of github.com:PowerDNS-Admin/PowerDNS-Admin into 1630-dnssec-feature-breaks-for-zones-that-contain-improperly-escaped-slashes 2023-11-24 09:29:40 -05:00
Matt Scott
4dab950efc
Reverting a bad change I made to remove a deprecated feature that is removed in Flask 2.3. 2023-11-24 09:28:21 -05:00
Matt Scott
b347e3df55
Updated zone list view to properly encode the zone name when using it to build request URIs to the back-end. 2023-11-24 09:19:16 -05:00
Matt Scott
24c08a269e
Updated zone type comparison logic in domain router to be case-insensitive () 2023-11-24 09:11:40 -05:00
Matt Scott
09014bf4a9
Correcting case-sensitivity issue with zone type comparison. 2023-11-24 09:03:39 -05:00
Matt Scott
a85827f302
Update index router to replace the use of the deprecated before_app_first_request event with record_once () 2023-11-24 08:25:38 -05:00
Matt Scott
28c63abea4
Updated the index router to pivot from the use of the deprecated before_app_first_request event to the replacement of record_once. 2023-11-24 08:18:20 -05:00
Matt Scott
5147d72999
Fixes local user setup to perform case-insensitive verification of existing usernames / emails () 2023-11-24 08:07:20 -05:00
Matt Scott
457c704de3
Update static fonts to use relative paths instead of static () 2023-11-24 07:44:29 -05:00
Matt Scott
447bb14742
Updated the roboto_mono.css and source_sans_pro.css font-face definition files to utilize relative pathing to resolve issues with installations deployed in subdirectories. 2023-11-24 07:40:49 -05:00
Matt Scott
67085653ae
Tested the proposed modification to the Docker healthcheck command to support subdirectory root paths to no avail.
Staging changes until a proper resolution is proposed.
2023-11-24 07:33:58 -05:00
Matt Scott
0472aba25e
Updated Python requirements for PyYAML from version 5.4 to 6.0.1 to resolve a conflict with Cython. 2023-11-24 06:54:55 -05:00
Matt Scott
4442577b0b
Created a new model to represent the Flask-Session storage schema sessions with a method for removing expired sessions.
Added a trigger for the Flask-Session model's session clean-up method to the `before_request` handler of the user router.
2023-11-24 06:26:38 -05:00
Ronan
c52bdd0daf
Fix case sensitivity for duplicate email creation 2023-08-31 16:28:06 +10:00
Ronan
7fcd2b8aa6
Fix case sensitivity for duplicate username creation 2023-08-31 16:26:48 +10:00
Ronan
ad9e4938bc
Add additional log information
Print out the message returned by create_local_user() when it fails to create a new local user.
2023-08-31 16:25:12 +10:00
Kristian Feldsam
c03f5c4f9e Dashboard domains table - join actions into single column
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>

dfdas

Signed-off-by: Kristian Feldsam <feldsam@gmail.com>
2023-08-08 00:55:55 +02:00
Tyler Todd
45f1ba7b82
Fix non rr_set events in Zone Changelog display () 2023-06-22 12:04:49 -04:00
Tyler Todd
08c39c64c2
Fix non rr_set events in Zone Changelog display
This will resolve 
2023-06-22 12:03:42 -04:00
Tyler Todd
8b51313027
Remove Misc Code () 2023-06-22 10:36:20 -04:00
Tyler Todd
fa7b146c78
Remove Misc Code 2023-06-22 10:35:52 -04:00
Tyler Todd
8e53e4ae48
Update Versions
Update Flask and Werkzeug versions
2023-06-22 10:24:59 -04:00
Tyler Todd
e88a836f26
Fix ordering and type
Fixes the ordering of variables for cosmetic, and adds the missing code for the compare of changes.
2023-06-22 10:21:15 -04:00
Tyler Todd
6fc2279c3b
Indicate Unsaved Changes () 2023-06-22 09:49:31 -04:00
Tyler Todd
953221578b
Update base.html 2023-06-22 08:50:14 -04:00
Tyler Todd
11be125e3b
Update domain.html 2023-06-22 08:48:50 -04:00
Matt Scott
8aef6fe8f1
Automatically focus username field in login view () 2023-04-28 04:47:19 -04:00
Roel Schroeven
9350c98ea2 Autofocus username field on Login screen 2023-04-26 17:26:52 +02:00
Matt Scott
86e75c8b6b
Fix record comment removal () 2023-04-21 18:29:20 -04:00
corubba
f0e32a035d Fix deletion of comments
The compaitibility for backends that don't support comments broke the
normal deletion of comments. This is fixed by limiting the
compaitibility to when we don't know for certain whether the backend of
that zone supports comments or not. This is done by checking if the
zone currently contains any comments: If it does, the backend definitly
supports comment; if it doesn't, we don't know and have to assume it
doesn't. The check is done by the "modified_at" attribute, because this
only exists on persistent comments from pdns and not on the "fill-up"
comments that PDA adds.
Luckily this also works perfectly for the deletion case, because to
delete a comment it had to already exist previously.

Fixes 
2023-04-16 16:03:44 +02:00
Matt Scott
055ac761e3
Updating documentation to reflect the latest app settings changes. 2023-04-14 19:38:54 -04:00
Matt Scott
66f422754b
Updated the application version references in preparation for the next release of 0.4.2. 2023-04-14 19:33:32 -04:00
Matt Scott
9193317d00
Allow all application settings to be configured by environment variables () 2023-04-14 19:30:18 -04:00
Matt Scott
c74c06c327
Added some missing settings to the new AppSettings class.
Corrected typo in app setup method.
2023-04-14 19:26:02 -04:00
Matt Scott
601539f16e
Removed unnecessary parenthesis. 2023-04-14 19:00:39 -04:00
Matt Scott
ccd7373efe
Corrected issue with encoding / decoding of dictionary and list type settings values.
Updated zone record settings management to use valid JSON format with backwards compatibility support for the non-JSON literal format.
2023-04-14 18:52:27 -04:00
Matt Scott
c842d09195
Overhauled app settings implementation to remove redundancy of definitions. Additionally, re-factored settings initialization code to allow for every setting to be defined by environment variable for both bare metal and Docker container deployments. 2023-04-14 07:12:02 -04:00
Matt Scott
9ddfde02b8
Fixed issue with all unassigned zones being selected after a new account's name fails to validate () 2023-04-13 16:16:51 -04:00
Matt Scott
7eee3134d4
Corrected the logic flaw in the account add / edit form that would cause all unassigned zones to be selected automatically following the attempt of account creation with an invalid name. 2023-04-13 16:09:10 -04:00
Matt Scott
d773e078f5
Updated the Docker image build workflow to hopefully push the additional platform builds to Docker Hub after the build is complete. 2023-04-13 13:53:39 -04:00
Matt Scott
c6a63053f3
Corrected issue with SERVER_EXTERNAL_SSL setting not being extracted from the app's environment. () 2023-04-13 13:47:42 -04:00
Matt Scott
84f84f2809
Updated the OAuth service providers to properly respect the new OAuth auto-configuration settings for each provider. () 2023-04-13 13:40:25 -04:00
Matt Scott
8cfc62e9d0
Corrected issue with SERVER_EXTERNAL_SSL setting not being extracted from the app's environment. 2023-04-13 13:40:06 -04:00
Matt Scott
d7f3610b51
Updated the OAuth service providers to properly respect the new OAuth autoconfiguration settings for each provider. 2023-04-13 13:34:41 -04:00
Matt Scott
8108caf96a
Updated default value of SERVER_EXTERNAL_SSL environment setting () 2023-04-12 08:13:19 -04:00
Matt Scott
003ee07596
Updated the default value of the SERVER_EXTERNAL_SSL environment setting. 2023-04-12 08:07:40 -04:00
Matt Scott
c778004287
Removed ARM v7 support from the Docker image build process it added a lot of build time to the process. 2023-04-12 07:35:34 -04:00
Matt Scott
a8c61abef6
Added support for building ARM and ARM64 based Docker images. () 2023-04-12 06:28:44 -04:00
Matt Scott
606b0ccc84
Added support for building ARM and ARM64 based Docker images. 2023-04-12 06:26:03 -04:00
Matt Scott
b60a74d764
Zone rrset changelog display improvement () 2023-04-12 05:47:49 -04:00
Matt Scott
e45324c619
Updated issue templates to include latest version release.
Added `VERSION` file to repository root for easy tracking of current app version.

Corrected bug with the latest changes to the settings model that can lead to a JSON decoding error for installations without a properly stored value.
2023-04-12 05:30:06 -04:00
Rauno Tuul
0ccdf9ac0d resolved conflict from 737e104912af07d07bdf2daf7242fae94adcba45 2023-04-12 09:28:58 +03:00
Matt Scott
a8895ffe7a
Merge branch 'release/0.4.1' 2023-04-11 19:13:04 -04:00
Matt Scott
1081751c41
Merge remote-tracking branch 'origin/dev' into dev 2023-04-11 19:09:46 -04:00
Matt Scott
f66d64ecbc
Fixed an issue with Azure and OIDC sessions not being properly cleared on logout. 2023-04-11 19:09:33 -04:00
Matt Scott
71405549a7
Feature Update: Allow more than 100 rows in the zone list view. () 2023-04-11 18:57:51 -04:00
Matt Scott
db30c77584
Show the current zone type and soa-edit-api settings on the zone settings page () 2023-04-11 18:56:29 -04:00
Matt Scott
c98c174c23
Hopefully provided a reliable fix to the settings type conversion issues brought upon with the authentication settings editor overhaul. 2023-04-11 18:50:47 -04:00
Matt Scott
feb62cf39f
Updated settings model boolean value conversion to include additional test values. 2023-04-11 07:05:46 -04:00
Matt Scott
4e54a2bb3f
Feature Update: OAuth Settings Management / Dual-Schema Support () 2023-04-10 20:03:28 -04:00
Matt Scott
c7aba5626d
Moved authentication settings editor JavaScript into js_main assets build process instead of direct linking it to the view. 2023-04-10 19:58:58 -04:00
Matt Scott
9f076330d6
Removed legacy backend controller code for handling authentication settings form submission. 2023-04-10 19:54:47 -04:00
Matt Scott
69ce3cb88a
Added additional UI alerts / messaging to handle success / failure scenarios. 2023-04-10 19:52:18 -04:00
Matt Scott
e132ced669
Completed first pass at the jQuery Validation implementation for the authentication settings editor. 2023-04-10 19:29:18 -04:00
Matt Scott
ea10b814d6
Working on implementing the jQuery Validation plugin for the authentication settings editor. 2023-04-10 18:35:25 -04:00
Matt Scott
cf62890fcf
Working on implementing the jQuery Validation plugin for the authentication settings editor. 2023-04-10 17:28:54 -04:00
Matt Scott
a4b6fba2be
Working on implementing the jQuery Validation plugin for the authentication settings editor. 2023-04-10 16:35:02 -04:00
Matt Scott
6a19ed2903
Completed basic handling of authentication settings save process to the database. 2023-04-10 13:24:42 -04:00
Matt Scott
2e30b83545
Added jQuery Validation 1.19.5 to the project NPM configuration as well as the Flask assets build script for js_main. 2023-04-10 13:06:06 -04:00
Matt Scott
022e780d77
Working on authentication editor data saving workflow. 2023-04-10 12:49:12 -04:00
Matt Scott
0912dd2805
Working on Knockout model integration into existing authentication settings editor view.
Settings are now loading via new backend API.
2023-04-10 07:39:21 -04:00
Matt Scott
827da59ae2
Working on Knockout model integration into existing authentication settings editor view. 2023-04-09 10:26:07 -04:00
Matt Scott
737e104912
Added KnockoutJS NPM package.
Re-formatted and re-organized settings model.

Working on Knockout model integration into existing authentication settings editor view.
2023-04-09 10:11:00 -04:00
Matt Scott
ece9626212
Updated the OAuth login handlers to utilize uniform user naming variables.
Updated the GitHub login process to split the user's full name based on spaces so that first and last name are filled in on PDA profile.
2023-04-08 18:14:40 -04:00
Matt Scott
9168dd99e0
Updated the OAuth login handlers to utilize uniform user naming variables.
Updated the GitHub login process to split the user's full name based on spaces so that first and last name are filled in on PDA profile.
2023-04-08 18:11:55 -04:00
Matt Scott
a46ab760fd
Reorganized universal OAuth fields' order to a uniform standard. Also updated the Client ID field prompts to a uniform standard. 2023-04-08 17:40:30 -04:00
Matt Scott
ee9012fa24
Completed OAuth change to make the use of the metadata URL setting exclusive to the authorization and token URL settings. If the former is defined, it will be used in preference to the latter. 2023-04-08 17:14:55 -04:00
Matt Scott
ab4495dc46
Completed the implementation of the SERVER_EXTERNAL_SSL environment setting into the app config files.
Completed the implementation of the aforementioned environment setting into the OAuth workflows.

Documented the aforementioned setting in the Environment-variables.md wiki document.
2023-04-08 17:05:27 -04:00
Rauno Tuul
f5565bef23 merged activity_sql_perf_tuning to solve conflicts in forehand 2023-04-06 12:55:13 +03:00
Rauno Tuul
d27fa2aa96 minor syntax improvement to use single history object as list 2023-04-04 22:27:21 +03:00
Rauno Tuul
84d792ac07 Resolved merge conflict 2023-04-04 15:50:46 +03:00
Rauno Tuul
fe10665e19 Refactored zone history retrieval, parsing and displaying code. 2023-04-04 15:32:52 +03:00
Stefan Ubbink
bae746cffe Show the current zone type and soa-edit-api settings on the zone settings page 2023-04-03 17:40:46 +02:00
Matt Scott
cacfc042e2
Fix Zone Type can not be changed from "native" when adding or modifying zones () 2023-04-02 15:07:57 -04:00
Stefan Ubbink
a2429ad9d6 Make it possible again to use a different Zone Type than 'native', fixes 2023-04-02 20:46:32 +02:00
Matt Scott
1f6a0504c8
OAuth Settings Updates ()
Completed the removal of the OAuth JWKS URL setting as well as the update of how the existing metadata URL settings are being used.

For additional information, reference GitHub issue .
2023-04-02 09:35:17 -04:00
Matt Scott
19335439bd
Completed the removal of the OAuth JWKS URL setting as well as the update of how the existing metadata URL settings are being used.
For additional information, reference GitHub issue .
2023-04-02 09:19:05 -04:00
Rauno Tuul
e6c0b4c15f Performance gain in activity records list as in 2023-03-30 16:23:03 +03:00
Jan Koppe
0d0339a316
fix : allow more than 100 rows default in dashboard
The dashboard.domains_custom route was hardcoded to either return all
the domains, or at most 100, regardless of default_domain_table_size
setting.

Make this limit be dependent on default_domain_table_size instead.

The API will now limit to 100 or default_domain_table_size, whichever
one is higher. This is done to not break any seconday use-cases that
might depend on the hardcoded setting.
2023-03-29 14:52:00 +02:00
Rauno Tuul
bb34daa333 Activity pages history base_query unification and perfomance improvement for standard user 2023-03-28 16:41:08 +03:00
Matt Scott
53cfa4fdaa
Fix activity search invalid form markup causing submission failures 2023-03-25 10:00:12 -04:00
Rauno Tuul
e0dffff325 Fix activity search form structure 2023-03-25 11:47:58 +02:00
Matt Scott
b86282b442
Added references to the project's discord server. 2023-03-24 19:43:08 -04:00
Matt Scott
7b91804a8e
Merge remote-tracking branch 'origin/dev' into dev 2023-03-24 19:42:45 -04:00
Matt Scott
15e29b6771
Added references to the project's discord server. 2023-03-24 19:42:35 -04:00
Matt Scott
004d1d40c9
Improve exception handling for invalid UTF-8 encoded X-API-KEY header () 2023-03-22 04:34:55 -04:00
Nigel Kukard
a954800869 fix(api): fixed internal server error being generated from invalid UTF-8 encoded X-API-KEY 2023-03-22 01:27:52 +00:00
Matt Scott
92033aa109
Updated project README to include organization sponsorship reference. 2023-03-21 19:11:30 -04:00
Matt Scott
271f483062
Updated project README to include organization sponsorship reference. 2023-03-21 19:09:48 -04:00
Matt Scott
1762a5481b
Updated build-and-publish workflow to exclude non-relevant project paths. 2023-03-19 17:06:01 -04:00
Matt Scott
419bf35892
Updated build-and-publish workflow to exclude non-relevant project paths. 2023-03-19 17:05:30 -04:00
Matt Scott
a187d70470
Updated CodeQL workflow to exclude non-relevant project paths. 2023-03-19 17:03:02 -04:00
Matt Scott
f6009ba47b
Updated CodeQL workflow to exclude non-relevant project paths. 2023-03-19 17:02:45 -04:00
Matt Scott
55faefeedc Updated stale thread workflow with updated message verbiage.
Updated lock thread workflow to properly exclude threads with specific labels.
2023-03-19 15:11:01 -04:00
Matt Scott
236487eada Updated Security section header of the project README. 2023-03-19 15:11:01 -04:00
Matt Scott
976f52ce7a Corrected minor formatting issue with project's Code of Conduct policy. 2023-03-19 15:11:01 -04:00
Matt Scott
4e54b5ae0a Added GitHub sponsors configuration. 2023-03-19 15:11:01 -04:00
Matt Scott
ba19943c64 Updated stale thread workflow with updated message verbiage.
Updated lock thread workflow to properly exclude threads with specific labels.
2023-03-19 15:09:52 -04:00
Matt Scott
6b9638ca19 Updated Security section header of the project README. 2023-03-19 12:39:44 -04:00
Matt Scott
e11f55523d Corrected minor formatting issue with project's Code of Conduct policy. 2023-03-19 12:36:44 -04:00
Matt Scott
506a75300a Added GitHub sponsors configuration. 2023-03-18 21:45:28 -04:00
Matt Scott
80b191bc0d Updated project README to include donation section. 2023-03-18 20:55:41 -04:00
Matt Scott
5acbabaed5 Updated project README to include donation section. 2023-03-18 20:55:20 -04:00
Matt Scott
0a66089cad Updated dependabot configuration to target the dev branch. 2023-03-18 20:49:15 -04:00
Matt Scott
522705a52b Updated dependabot configuration to target the dev branch. 2023-03-18 20:49:01 -04:00
Matt Scott
519b8579db
Merge pull request from GHSA-6c8m-4h29-hmmh
fix: fix for CVE-2023-0286 & CVE-2023-23931 - cryptography update to 39.0.2
2023-03-18 20:00:05 -04:00
Nigel Kukard
e7547ff8d3 fix: fix for CVE-2023-0286 & CVE-2023-23931 - cryptography update to 39.0.2 2023-03-18 23:56:22 +00:00
Matt Scott
b71f9ae5b4
Merge pull request from GHSA-3pcw-h28g-9w3v
Upgrade setuptools to 65.5.1 (CVE-2022-40897 fix)
2023-03-18 19:53:41 -04:00
Nigel Kukard
78e8d9950d fix: upgrade setuptools to fix CVE-2022-40897 2023-03-18 23:38:48 +00:00
Matt Scott
ca4bf18f67 Updated invalid value in dependabot workflow. 2023-03-18 19:21:14 -04:00
Matt Scott
1918f713e1 Merge remote-tracking branch 'origin/dev' into dev 2023-03-18 19:20:47 -04:00
Matt Scott
33614ae102 Updated invalid value in dependabot workflow. 2023-03-18 19:20:36 -04:00
Matt Scott
d3da1e43ed
Fix LDAP group restrictions by allowing the use of any combination of groups. () 2023-03-18 19:15:01 -04:00
Nigel Kukard
138532fb95 fix: allow the specification of any combination of groups in LDAP group security configuration
Previous behavior required the specification of all three group security groups before the
"Save Settings" button would be enabled.

This adds a check into users.py which checks that the group is set before searching and
removes the javascript preventing the specification of any combination of groups.

Tested:
- Tested all combinations on AD after MR 1238
- Tested all combinations on OpenLDAP
- Tested enabling the Group Security with no groups set which correctly prevents login

Resolves 
2023-03-18 20:30:52 +00:00
Matt Scott
c24b4b047d
Merge pull request from nkukard/nkupdates-fix-session-clear
fix: fixed session clearing and let logout_user take care of cleanup
2023-03-18 16:05:20 -04:00
Matt Scott
defb3e5a48
Merge pull request from melck/fix-ldap-ad-nested-member
Fix LDAP user group search for nested groups 
2023-03-18 16:03:48 -04:00
Nigel Kukard
f44ff7d261 fix: fixed session clearing and let logout_user take care of cleanup
It seems when logging in and logging out, then logging back in, setting
the session timeout to 5 minutes, then waiting for expiry can cause
a situation when using SQLA-based sessions which results in a NULL field
in the database and causes a persistent 500 Internal Server Error.

As per issue 1439 here is a fix found by @raunz.

Resolves .

Tested for about 8 hours and tons and tons of expired sessions, could not
reproduce with the fix applied.
2023-03-18 19:14:58 +00:00
Matt Scott
340e84ab89 Updated MegaLinter workflow to include a manual dispatch option. 2023-03-18 08:52:39 -04:00
Matt Scott
2606ad0395 Updated various yaml files to include proper opening lines.
Tweaked the name of the stale threads workflow.
2023-03-18 08:48:34 -04:00
Matt Scott
d716f8cc88 Updated various yaml files to include proper opening lines.
Tweaked the name of the stale threads workflow.
2023-03-18 08:48:07 -04:00
Matt Scott
1aac3c0f0d Updated the stale issue / PR workflow to include better verbiage for the contribution guide.
Also updated the stale issue / PR workflow to exclude security vulnerabilities.
2023-03-17 18:25:33 -04:00
Matt Scott
2ca712af49 Updated the stale issue / PR workflow to include better verbiage for the contribution guide.
Also updated the stale issue / PR workflow to exclude security vulnerabilities.
2023-03-17 18:25:05 -04:00
Matt Scott
92f5071a84 Corrected URL mistake in stale issue / PR workflow. 2023-03-17 18:16:28 -04:00
Matt Scott
763f06a830 Corrected URL mistake in stale issue / PR workflow. 2023-03-17 18:16:06 -04:00
Matt Scott
98e6b8946f Updated labels for the issue templates. 2023-03-17 18:03:34 -04:00
Matt Scott
3294ed80f3 Updated labels for the issue templates. 2023-03-17 18:03:18 -04:00
Matt Scott
1bfb5429a1 Updated stale issue / PR workflow to include proper exceptions. 2023-03-17 17:57:18 -04:00
Matt Scott
687571101f Updated stale issue / PR workflow to include proper exceptions. 2023-03-17 17:56:59 -04:00
Matt Scott
1358e47b5b Corrected project name reference mistake in contribution guide. 2023-03-17 17:48:30 -04:00
Matt Scott
ae16e9868a Corrected project name reference mistake in contribution guide. 2023-03-17 17:48:07 -04:00
Matt Scott
a2e5c7d5bc Corrected an input type mistake in the bug report and feature request templates.
Corrected URL mistake in the issue template config.yml file.

Updated project README policy reference URLs to use master branch.
2023-03-17 17:29:45 -04:00
Matt Scott
fc6d8505b7 Corrected an input type mistake in the bug report and feature request templates.
Corrected URL mistake in the issue template config.yml file.

Updated project README policy reference URLs to use master branch.
2023-03-17 17:29:05 -04:00
Matt Scott
31c8577409 Updated project README to include reference to new security policy. 2023-03-17 17:22:08 -04:00
Matt Scott
6681d0f5b0 Relocated new security policy to the project root to meet GitHub feature expectations. 2023-03-17 17:22:08 -04:00
Matt Scott
0f7c2da814 Updated project README to include references to the new security policy.
Moved the project's code of conduct out of the contributions guide and into the appropriate policy file.

Updated the contribution guide to follow the NetBox project format.

Added various issue templates based on the NetBox project formats but updated for PDA.

Added additional GitHub workflows to handle stale and closed issue and PR management.

Removed legacy stale issue workflow that was not in use.
2023-03-17 17:22:08 -04:00
Matt Scott
23d6dd1fde Updated project README to include reference to new security policy. 2023-03-17 16:48:11 -04:00
Matt Scott
4b3759d140 Relocated new security policy to the project root to meet GitHub feature expectations. 2023-03-17 16:46:34 -04:00
Matt Scott
5c6cf77996 Updated project README to include references to the new security policy.
Moved the project's code of conduct out of the contributions guide and into the appropriate policy file.

Updated the contribution guide to follow the NetBox project format.

Added various issue templates based on the NetBox project formats but updated for PDA.

Added additional GitHub workflows to handle stale and closed issue and PR management.

Removed legacy stale issue workflow that was not in use.
2023-03-17 16:42:05 -04:00
Matt Scott
a25dda8ac1 Made some formatting tweaks to the authentication settings view to unify section header styling.
Corrected improper markup introduced by recent PR for password complexity requirements.
2023-03-17 15:50:08 -04:00
Matt Scott
4a6d31cfa4
Merge pull request from nkukard/nkupdates-password-policy
Implement password strength & complexity checking
2023-03-17 15:35:10 -04:00
Matt Scott
78f0332a2d
Merge branch 'dev' into nkupdates-password-policy 2023-03-17 15:31:10 -04:00
Matt Scott
4fa8bf2556
Merge pull request from nkukard/nkupdates-fix-basic-auth-exception2
fix(auth:basic): Basic auth exception handling improvement
2023-03-17 15:28:20 -04:00
Matt Scott
b23523db4b
Merge pull request from subbink/1412-rename-domain-to-zone
Finish Updating Zone Nomenclature From Domain
2023-03-17 15:24:26 -04:00
Matt Scott
92be9567de
Merge pull request from subbink/improve-psql-docs
PostgreSQL Support Improvement
2023-03-17 15:17:41 -04:00
Nigel Kukard
64017195da feat(authentication): check password policy during user profile password change 2023-03-17 03:45:37 +00:00
Nigel Kukard
fc14e9189d feat(authentication): check password policy during registration of new users 2023-03-17 03:45:09 +00:00
Nigel Kukard
1cea4b7ce3 feat(authentication): added password policy checker function 2023-03-17 03:44:08 +00:00
Nigel Kukard
bb6d2d0497 feat(authentication): added admin settings for password policies 2023-03-17 03:42:45 +00:00
Stefan Ubbink
7489e2c9a2 Merge branch '1412-rename-domain-to-zone' of subbink/PowerDNS-Admin into 1412-rename-domain-to-zone 2023-03-16 18:41:19 +01:00
Stefan Ubbink
a9e18ec594 Make sure old history items will also be shown 2023-03-16 18:36:47 +01:00
Stefan Ubbink
a2d1179fd2 Change domain(s) to zone(s) in the python code and docs 2023-03-16 17:02:23 +01:00
Stefan Ubbink
34902f6cf9 Change domain(s) to zone(s) in the templates 2023-03-16 17:02:23 +01:00
Nigel Kukard
17e3a8f942 fix(auth:basic): Basic auth exception handling improvement
Currently passing an invalid Basic auth header (random string base64 encoded) would result in an exception being raised due to a username, password = auth_header.split().

Similary passing a `Digest` authentication type would result in an exception as there is no :.

Thirdly passing invalid base64 encoded UTF-8 code sequences would result in exceptions as this issue ().

I added code to check explicitly that we are doing basic authentication then by checking the number of entries returned by the split.

I also added exception handling for invalid UTF-8 code sequence exceptions.

Tested with a fuzzer.

Tested with valid and invalid credentials.

This fixes .
2023-03-15 01:09:46 +00:00
Matt Scott
73447d396a
Merge pull request from nkukard/nkupdates-fix-basic-auth-exception
Basic Auth Exception Handling Improvement
2023-03-14 19:37:37 -04:00
Nigel Kukard
24f94abc32 fix(auth:basic): improved API basic auth handling to avoid exceptions
Currently passing an invalid Basic auth header (random string base64 encoded) would result in an
exception being raised due to a `username, password = auth_header.split()`.

I refactored the code in this decorator by checking explicitly that we are doing basic authentication
then by checking the number of entries returned by the split.

I also added exception handling for invalid UTF-8 code sequences.

Tested with a fuzzer.

Tested with valid and invalid credentials.

This fixes .
2023-03-14 23:19:40 +00:00
Matt Scott
57b4457add
Merge pull request from nkukard/nkupdates-migrations-fix
Fix user confirmed column migration issue
2023-03-14 17:22:34 -04:00
Nigel Kukard
61e607fb3f fix(db:migrate): fix migration user 'confirmed' column migration
This change fixes the migration on the `user` table, `confirmed` column to be compatible with PostgreSQL and MySQL databases.

Fixes  which introduced a breaking change for MySQL databases and resolves .

```
Tested on:
- PostgreSQL:14 - WORKING
- PostgreSQL:15 - WORKING
- MariaDB:10.11 - WORKING
- MariaDB:10.10 - WORKING
- MariaDB:10.9  - WORKING
- MariaDB:10.8  - WORKING
- MariaDB:10.7  - WORKING
- MariaDB:10.6  - WORKING
- MariaDB:10.5  - WORKING
- MariaDB:10.3  - WORKING
```
2023-03-14 21:16:50 +00:00
Matt Scott
4751ebed3e
Merge pull request from nkukard/nkupdates-lxml-fix
Upgrade lxml & python3-saml dependencies with workaround
2023-03-14 17:16:30 -04:00
Nigel Kukard
7e2fa1bfaa fix: fixes Python 3.11 incompatibility using ancient lxml binary PyPI
Fixes Python 3.11 incompatibility using ancient lxml binary PyPI built against old libxml2.

This fixes  and closes .

The issue with python3-saml not working is because the binary lxml wheel is built against a different version of libxml2.

``--no-binary lxml`` can be used to fix this and python3-saml will work.

for references check these:
- https://github.com/onelogin/python3-saml/issues/292
- https://bugs.launchpad.net/lxml/+bug/1960668
- https://github.com/open-formulieren/open-forms/pull/2247

```
Tested on:
 - ubuntu:23.04 - WORKING
 - ubuntu:22.10 - WORKING
 - ubuntu:22.04 - WORKING
 - ubuntu:20.04 - WORKING
 - ubuntu:18.04 - NOT WORKING - pip usage error with -r requirements.txt
 - debian:10    - WORKING
 - debian:11    - WORKING
 - alpine:edge  - WORKING
 - alpine:3.17  - WORKING
 - alpine:3.16  - WORKING
 - alpine:3.15  - WORKING
 - alpine:3.14  - WORKING
 - rockylinux:9 - WORKING
 - rockylinux:8 - NOT WORKING - pip usage error with -r requirements.txt
 - almalinux:9  - WORKING
 - almalinux:8  - NOT WORKING - pip usage error with -r requirements.txt
 - fedora:36    - WORKING
 - fedora:37    - WORKING
 - fedora:38    - WORKING
 - fedora:39    - WORKING
```
2023-03-14 21:12:06 +00:00
Matt Scott
4420621cfe
Merge pull request from subbink/1237-otp-with-site-name
Make the OTP label the same as the site_name 
2023-03-13 19:19:28 -04:00
Stefan Ubbink
6eef5eb59c Make the OTP label the same as the site_name 2023-03-13 18:54:49 +01:00
Stefan Ubbink
a2ef456ad7 Change domain(s) to zone(s) in the python code and docs 2023-03-12 20:44:56 +01:00
Stefan Ubbink
3e9e73fb3a Change domain(s) to zone(s) in the templates 2023-03-12 20:44:56 +01:00
Matt Scott
6a5bc8adeb
Merge pull request from benshalev849/custom_current_user
Added custom header to be used in the created_by column.
2023-03-12 11:39:33 -04:00
Your Name
6a402969ec Merge branch 'custom_current_user' of https://github.com/benshalev849/PowerDNS-Admin into custom_current_user 2023-03-12 15:33:15 +00:00
Your Name
695d746295 Changed basic_settings.md path 2023-03-12 15:32:32 +00:00
benshalev849
bd30c73ca4
Merge branch 'dev' into custom_current_user 2023-03-12 17:23:44 +02:00
Your Name
0ac7a5a453 Added some explanation about some of the 'basic' settings in the admin 2023-03-12 15:00:32 +00:00
Matt Scott
84cfd165b4 Re-arranged side navigation to include the "Global Search" feature regardless of user role as the global search feature is now accessible to all users.
Also moved the "Activity" feature link higher in the menu to remove duplicate code from the navigation code base.
2023-03-12 10:27:04 -04:00
Your Name
ee68b18e27 Added custom header in created_by segment option 2023-03-12 13:36:30 +00:00
Matt Scott
f09d37ae42
Merge pull request from PowerDNS-Admin/1435-feature-add-oauth-setting-for-server-metadata-url
Feature: Add OAuth Setting for Server Metadata URL
2023-03-12 09:20:47 -04:00
Matt Scott
1afe9b4908 Finished adding new OAuth Server Metadata URL setting to Google, GitHub, and Microsoft OAuth service configuration features. 2023-03-12 09:13:54 -04:00
Stefan Ubbink
c61489adfc Improve things for using PostgreSQL 2023-03-12 13:11:20 +01:00
Matt Scott
7ce1f09522
Merge pull request from PowerDNS-Admin/1431-feature-add-oauth-setting-for-jwks-url
Feature: Added new JWKS URL setting for each OAuth provider
2023-03-11 14:50:23 -05:00
Matt Scott
369188e80e Disabled MegaLinter workflow for all branches currently. 2023-03-11 14:50:02 -05:00
Matt Scott
fd30e3ff49 Added new JWKS URL setting for each OAuth provider and updated the associated authorization service to use the setting during the initialization of the authlib. 2023-03-11 14:46:58 -05:00
Matt Scott
b8ab0d3478 Updated the project README with a refreshed screenshot of the dashboard. 2023-03-11 10:21:36 -05:00
Matt Scott
16de70008c Updated project README.md file to include contributing guidelines reference. 2023-03-11 09:32:55 -05:00
Matt Scott
22370d0a57 Added a CONTRIBUTING.md documentation file to the project's documentation. 2023-03-11 09:30:51 -05:00
Matt Scott
5ed8d0c2f0 Added a resolutions entry to the project's package.json file to deal with the Font Awesome icon issue created by AdminLTE's dependency on the older 5.x Font Awesome release. 2023-03-11 09:08:12 -05:00
Matt Scott
87891a3eb9 Re-formatted the assets.py file to current PEP8 standards.
Modified the yarn.lock file to remove what appears to be a dependency overwrite for Font Awesome icons which results in an older 5.x release overwriting the newer 6.x release.
2023-03-11 08:48:19 -05:00
Matt Scott
4c24fbaec6 Revert "Removed Docker image build line that updates the CSS asset build filters."
This reverts commit 05e2f1370169f2fa2b049bf7ae7b9ec7a99635ef.
2023-03-10 18:58:30 -05:00
Matt Scott
05e2f13701 Removed Docker image build line that updates the CSS asset build filters. 2023-03-10 18:53:07 -05:00
Matt Scott
dd867eb4e8 Added application version to base template footer, starting with the next planned production release of 0.4.0. 2023-03-10 18:43:20 -05:00
Matt Scott
7ef6ee4422 Tweaked the Docker image build workflow to properly tag the image based on the repository branch name. 2023-03-10 18:26:39 -05:00
Matt Scott
c0f1698a9a Tweaked the Docker image build workflow to properly tag the image based on the repository branch name. 2023-03-10 18:22:42 -05:00
Matt Scott
cb929c3265 Tweaked the assets.py build script to switch to rcssmin for the login CSS build process. 2023-03-10 18:16:55 -05:00
Matt Scott
2e61a1d44a Tweaked the Docker image build workflow to not be exclusive to only the master branch. 2023-03-10 18:13:54 -05:00
Matt Scott
913528d08f Updated the CodeQL workflow to include the new "dev" branch. 2023-03-10 18:10:10 -05:00
Matt Scott
b1b2a0c7b5 Updated project documentation to use the "latest" Docker Hub image tag in preparation for the upcoming changeover where the "latest" tag will represent the current stable production release.
Updated the Docker image build workflow to include the new "dev" branch.
2023-03-10 18:08:24 -05:00
Matt Scott
ff2b532c29 Updated MegaLinter workflow to ignore dev branch as well. 2023-03-10 17:08:06 -05:00
Matt Scott
863b0a021d Merge branch 'subbink-fix_issue_635' into dev 2023-03-10 16:47:28 -05:00
Matt Scott
afcf0fbea7 Merge branch 'fix_issue_635' of github.com:subbink/PowerDNS-Admin into subbink-fix_issue_635 2023-03-10 16:47:00 -05:00
Matt Scott
c617aa1483 Merge branch 'raunz-session_type_sqlalchemy' into dev 2023-03-10 16:35:22 -05:00
Matt Scott
356667f989 Tweaked PR to include the latest asset build changes for CSS minimizer. Also updated the default session storage to use SQLAlchemy instead of the file system. 2023-03-10 16:34:55 -05:00
Matt Scott
1d6fdb1c23 Merge branch 'session_type_sqlalchemy' of github.com:raunz/PowerDNS-Admin into raunz-session_type_sqlalchemy 2023-03-10 16:27:06 -05:00
Matt Scott
26f3f79388 Corrected unauthorized side navigation change regarding the placement of the "Global Search" feature.
Removed the statistics and recent activity / history data display from the dashboard view.
2023-03-10 16:23:33 -05:00
Matt Scott
e7fc082b60 Merge branch 'ckbaker10-subfolder_fix_documentation_assets_css_hang' into dev 2023-03-10 16:11:15 -05:00
Matt Scott
6be6f3d389 Updated core project to also use rcssmin filter. 2023-03-10 16:10:42 -05:00
Rauno Tuul
c707f1e1c5 Added support for dict/json environment variables for docker image 2023-03-10 15:20:18 +02:00
Rauno Tuul
aa70951964 Read flask session type from environment variable and create sessions table if not exist. 2023-03-08 17:05:32 +02:00
Rauno Tuul
68d9fb3755 Support multiple Flask session types, not just filesystem. Set via generic SESSION_TYPE environment variable 2023-03-08 12:08:07 +02:00
Lukas
622003a46e Typo 2023-03-08 10:51:01 +01:00
Lukas
d055fd83c5 Documentation, Fix 2023-03-08 08:52:27 +01:00
Matt Scott
4933351ac1 Revert "Revert "Clean up dashboard zone tabs""
This reverts commit 5f2fc514df84b521ade5a5857a7522f637ade034.
2023-03-06 08:30:00 -05:00
Stefan Ubbink
fcda4977e2 Fix migration issue as proposed by @keesbos 2023-03-05 11:01:17 +01:00
Matt Scott
5f2fc514df Revert "Clean up dashboard zone tabs"
This reverts commit fc39cc40ee4a8a188ff66c54bb269eda76a94f78.
2023-03-04 11:27:44 -05:00
Matt Scott
9003b3f6c8
Merge pull request from corubba/feature/dashboard-tab-cleanup
Clean up dashboard zone tabs
2023-03-04 11:03:57 -05:00
Matt Scott
840076dae3
Merge pull request from corubba/feature/history-diff
Diff-ify changelog view for zone changes
2023-03-04 10:59:44 -05:00
Matt Scott
f5ddcc5809
Merge pull request from corubba/feature/sorting
Sort records label-wise right to left
2023-03-04 10:56:59 -05:00
Matt Scott
7f6924a966
Merge pull request from MDXDave/patch-1
Fixed scrolling on long content
2023-03-04 10:56:18 -05:00
Matt Scott
f4f1f31575
Merge pull request from raunz/global_search_for_all_users
Global Search available for all users
2023-03-04 10:54:49 -05:00
Matt Scott
062cb032c5
Merge pull request from raunz/dashboard_improvements
Fix dashboard MySQL performance with large history for standard users
2023-03-04 10:54:12 -05:00
Matt Scott
1a5a11bfa3
Merge pull request from raunz/perf_tuning
Standard user domain records list performance loop improvement
2023-03-04 10:53:35 -05:00
corubba
fc39cc40ee Clean up dashboard zone tabs
* Rename `customBoxes` to be more descriptive and follow CamelCase name convention for classes
* Change the tab info from a tuple to a named tuple
* Change all access to the tab info tuple from index to (new) property name
* Rename/Relabel the tabs
* Add docstrings
* Simplify the domain filter logic
* Simplify/Unify the tab html template
2023-03-03 14:34:13 +01:00
corubba
934e4a7af3 Sort records label-wise reverse
Account for the hierarchical nature of DNS by sorting records by their
name label-wise from right to left. Also justify the record names to the
right, so they visually line up on label borders.
2023-03-03 13:35:14 +01:00
corubba
8a40d21ea4 Diff-ify changelog view for zone changes
Improve and document the diff-computation and presentation, so you can
easier see what changed.
2023-03-03 13:22:29 +01:00
Dave
70073b9267
Fixed scrolling on long content 2023-03-02 16:08:39 +01:00
Rauno Tuul
68fe7c0e56 Standard user domain records list performance loop improvement 2023-03-01 18:43:47 +02:00
Rauno Tuul
ec687b13a5 Dashboard history query performance related change to nested subquery (allowed domains for user). 2023-03-01 11:42:21 +02:00
Rauno Tuul
dc69f00094 Fix dashboard MySQL performance with large history table and standard user privileges. 2023-03-01 11:29:17 +02:00
Rauno Tuul
524b6c6883 Move Global Search menu item next to dashboard. 2023-03-01 11:21:29 +02:00
Rauno Tuul
3688add76a Global Search available for all users. Apply allowed domain filter for standard users search result. 2023-03-01 11:12:42 +02:00
Matt Scott
077bbb813c Corrected styling mistake introduced by recent Admin-LTE upgrades. 2023-02-26 17:09:30 -05:00
Matt Scott
9678b64ad7 Disabling MegaLinter workflow on the primary project branch until further configuration can be made to get things into a passing state. 2023-02-26 08:34:14 -05:00
Matt Scott
50339fda55 Updated wiki documentation for Docker installations to reference the latest stable release published to Docker Hub instead of the latest image built from the primary branch. 2023-02-26 03:51:48 -05:00
Matt Scott
1840a4ea60 Updated project README to use direct wiki references instead of legacy wiki location which requires an additional click-through step. 2023-02-26 03:49:55 -05:00
Matt Scott
10f5bb7dc1 Updated project README to include a refreshed feature list.
Also updated project README Docker deployment instructions to reference the latest stable release version on Docker Hub.

Updated project license to include updated management organization references.
2023-02-26 03:47:28 -05:00
Matt Scott
df94baa81e Fixed a bug introduced by the recent updates for the mobile-first PR. 2023-02-25 16:50:08 -05:00
Matt Scott
81bd5804e0 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-02-25 16:46:24 -05:00
Matt Scott
e00f3ec47e
Merge pull request from ymage/add_oidc_env_vars_and_metadata_url
Add OIDC env vars and set SAML_ENABLED as False if unset
2023-02-23 21:27:29 -05:00
Ymage
63db17ec21 Add missing OIDC env vars
Set SAML_ENABLED default to false
2023-02-23 22:13:19 +01:00
Matt Scott
4a5b78c3ef
Merge pull request from MattCorp/master
Update domain_setting.html
2023-02-23 16:08:31 -05:00
Mathieu
5346bee291
Update domain_setting.html
correct value priamry in primary
2023-02-23 22:01:49 +01:00
Matt Scott
74935359e4 Tweaked activity logs card header on dashboard view to match the latest naming conventions. 2023-02-23 06:56:18 -05:00
Matt Scott
e3e5c265f4
Merge pull request from AdvanticGmbH/oauth_fix
Add support for oidc_oauth_metadata_url configuration option
2023-02-23 06:49:47 -05:00
AdvanticGmbH
5ad384bfe9 Add support for oidc_oauth_metadata_url configuration option
This commit adds support for the `oidc_oauth_metadata_url` configuration
option. This option specifies the URL of the OIDC server's
metadata endpoint, which contains information about the OIDC server's
endpoints, supported scopes, and other configuration details. By using this
option, we can ensure compatibility with different OIDC servers and reduce
the risk of errors due to manual endpoint configuration.
2023-02-23 09:21:01 +01:00
Matt Scott
1cda4f774a Updated card styles for API keys list view that was missed in the recent mobile-first PR. 2023-02-22 09:12:13 -05:00
Matt Scott
c6ae393894 Testing updated workflows. 2023-02-21 10:17:44 -05:00
Matt Scott
45d66b057a Testing updated workflows. 2023-02-21 10:16:50 -05:00
Matt Scott
8fbf40a9d1 Added MegaLinter as a new GitHub action for the project. 2023-02-21 10:15:33 -05:00
Matt Scott
c155a31857 Updated labels.yml file to include additional definition that was added recently. 2023-02-21 08:05:37 -05:00
Matt Scott
b04ab933c6 Tweaked breadcrumb label for activity view that was missed in the recent mobile-first updates. 2023-02-21 08:03:06 -05:00
Matt Scott
8b0f005006 Fixed bug introduced by PR 1391 involving the saving up settings that have an associated text input. 2023-02-21 07:57:59 -05:00
Matt Scott
564e393292 Fixed bug introduced by PR 1391 involving the saving up settings that have an associated text input. 2023-02-21 07:55:15 -05:00
Matt Scott
182ef10a87 Updated primary Dockerfile to include additional command for Font Awesome NPM fonts. 2023-02-21 07:37:42 -05:00
Matt Scott
b01bf41bf1
Merge pull request from PowerDNS-Admin/1391-feature-mobile-first-responsive-ui-design
Issue 1391 - Feature: Mobile First Responsive UI Design
2023-02-20 15:52:22 -05:00
Matt Scott
b98bcc3bec Cleaned up a UX issue with the zone records list editor view. 2023-02-20 15:52:00 -05:00
Matt Scott
4d0cf87338 Cleaned up a UX issue with the zone records list editor view. 2023-02-20 15:50:03 -05:00
Matt Scott
3ee63aca8c Tweaked styling for global search query form to include curved edges in accordance with the latest styling. 2023-02-20 14:52:01 -05:00
Matt Scott
32c53cf2a1 Tweaked markup generated from JS file to remove legacy btn-flat classes from various buttons. 2023-02-20 13:15:17 -05:00
Matt Scott
fcdbc45de7 Moved temporary global styles out of base view template and into custom.css file to be properly included to asset generation. 2023-02-20 12:49:05 -05:00
Matt Scott
78b88c8e0e Finished minor tweaks on the activity view, but it will still need a major re-work to correct the numerous mistakes present in the code. 2023-02-20 12:43:49 -05:00
Matt Scott
40034b2f26 Corrected minor styling issue for small mobile screens on the dashboard. 2023-02-20 12:37:22 -05:00
Matt Scott
be827c2362 Partially reverted a UX change on zone template record editor view to keep things working until it gets a full rebuild. 2023-02-20 12:30:57 -05:00
Matt Scott
65b7b9a5c3 Completed first-round updates for the error template views. 2023-02-20 12:28:56 -05:00
Matt Scott
b3c80df674 Completed first-round updates for the email confirmation views. 2023-02-20 12:12:22 -05:00
Matt Scott
f977a42cea Completed first-round updates for the user profile edit view. 2023-02-20 12:08:32 -05:00
Matt Scott
2980e5cec9 Completed first-round updates for the authentication settings view. 2023-02-20 11:39:52 -05:00
Matt Scott
0e94e18485 Working on first-round updates for the authentication settings view. 2023-02-20 11:25:25 -05:00
Matt Scott
bd94c97486 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin into 1391-feature-mobile-first-responsive-ui-design 2023-02-20 10:45:20 -05:00
Matt Scott
764b83b5d5 Working on first-round updates for the authentication settings view. 2023-02-20 10:42:25 -05:00
Matt Scott
772c1129f5 Working on first-round updates for the authentication settings view. 2023-02-20 10:35:15 -05:00
Matt Scott
4da10a9d00
Merge pull request from raunz/historyfix
API record change history format fix
2023-02-20 10:26:04 -05:00
Matt Scott
3ec66c359b
Merge pull request from ichisuke55/fix-captcha-env
fix: configurable CAPTCHA_ENABLE env when docker use
2023-02-20 10:25:28 -05:00
Matt Scott
4bd2519a76 Completed first-found updates for the server settings view. 2023-02-20 10:18:41 -05:00
ichisuke55
25dcc81e03 fix: configurable CAPTCHA_ENABLE env when docker use 2023-02-21 00:17:22 +09:00
Matt Scott
09127fb326 Completed first-found updates for the zone record settings view. 2023-02-20 10:14:09 -05:00
Matt Scott
a5d69e3e40 Completed first-found updates for the basic settings view. 2023-02-20 10:08:09 -05:00
Matt Scott
5a4279d7b8 Completed first-found updates for the API key add/edit view. 2023-02-20 09:56:26 -05:00
Matt Scott
db70e34c98 Completed first-found updates for the API keys list view. 2023-02-20 09:39:13 -05:00
Matt Scott
4a12d62828 Completed first-found updates for the user create / edit view. 2023-02-20 09:22:41 -05:00
Matt Scott
9ac81363e3 Completed first-found updates for the users list view. 2023-02-20 09:14:58 -05:00
Matt Scott
0e67366c5f Completed first-found updates for the account creation view. 2023-02-20 09:07:07 -05:00
Matt Scott
8c6fc5e262 Completed first-found updates for the accounts list view. 2023-02-20 08:50:17 -05:00
Matt Scott
a42d610759 Holding on remaining changes to zone template editor as a rebuild is also necessary here. 2023-02-20 08:43:49 -05:00
Matt Scott
e5269b5626 Removed text label from zone template list action dropdown control to reduce space consumption. 2023-02-20 08:32:53 -05:00
Rauno
7635686c43
Merge branch 'master' into historyfix 2023-02-20 10:32:52 +02:00
Matt Scott
4d64076dac Working on the first-round updates for the zone template editor. 2023-02-19 21:20:23 -05:00
Matt Scott
fe49651e81 Completed first-found of updates for the zone template creation view. 2023-02-19 21:05:23 -05:00
Matt Scott
f1d17c166a Completed first-round of updates for the zone templates list view. 2023-02-19 20:50:27 -05:00
Matt Scott
c4d9bf3a9c Started first-round of updates on the activity feature AKA history, but it's a very broken implementation that will require a complete re-build. Saving that for later. 2023-02-19 20:41:26 -05:00
Matt Scott
761909f0f8 Updated client-side navigation matching to only compare the base path without the query string. 2023-02-19 20:27:22 -05:00
Matt Scott
e960326a58 Working on first-round changes for the global search feature. 2023-02-19 16:02:34 -05:00
Matt Scott
f48a6b8209 Working on first-round changes for the global search feature. 2023-02-19 15:54:54 -05:00
Matt Scott
14e534468a Working on first-round changes for the global search feature. 2023-02-19 15:54:11 -05:00
Matt Scott
67040ad9c2 Completed first-round of updates for the server statistics and configuration features. 2023-02-19 15:16:50 -05:00
Matt Scott
62018686f5 Updated global styles for record lists as well as general card styling. 2023-02-19 15:12:34 -05:00
Matt Scott
65bfc53acb Split the server statistics and configuration feature into separate pages. 2023-02-19 15:04:30 -05:00
Matt Scott
55e4f5f829 Working on the first-round updates for the domain settings management view.
Tweaked sidebar navigation to reflect the latest terminology use in associated views.
2023-02-19 13:19:26 -05:00
Matt Scott
fd1bc4afa5 Working on the first-round updates for the domain settings management view. 2023-02-19 12:46:11 -05:00
Matt Scott
6e10f97e9d Completed first-found updates of the zone remove view.
Tweaked cancel button style and help text on the zone add view.
2023-02-19 12:13:13 -05:00
Matt Scott
75e262e7e9 Completed first-found updates of the zone add view. 2023-02-19 12:06:39 -05:00
Matt Scott
9548cbce1c Removed legacy style tag attribute. 2023-02-19 11:42:39 -05:00
Matt Scott
ec28e76ff5 Moved global styles to base view template until permanently merged into global CSS file. 2023-02-19 11:39:56 -05:00
Matt Scott
b52b7d7e4f Wrapping up first-round changes to the dashboard view. 2023-02-19 11:38:19 -05:00
Matt Scott
b4a354b0f8 Updated icons for sidebar navigation as well as some labels.
Tweaked zone record list on dashboard to always include the account column, regardless of user role.
2023-02-19 11:31:24 -05:00
Matt Scott
c0799b95f8 Updated sidebar navigation to use updated terminology of zone instead of domain. 2023-02-19 11:04:45 -05:00
Matt Scott
abf1f4eca3 Moved user profile edit and user logout navigation items into sidebar info block for a more condensed UI. 2023-02-19 11:03:33 -05:00
Matt Scott
1cd5ce9ccc Working on dashboard zone list action controls and styling. 2023-02-19 10:45:19 -05:00
Matt Scott
4a5db674f4 Working on condensing the zones list on the dashboard.
Changed the terminology for zones on the dashboard from domains to zones.
2023-02-19 10:19:13 -05:00
Matt Scott
49bc8e948d Continuing work on re-design and clean-up of dashboard view. 2023-02-19 09:54:15 -05:00
Matt Scott
4f83879e95 Added Jinja filter for handle formatting of zone type labels to enforce modern social standards.
Removed text label of dashboard domain list action menu to reduce size.

Continuing work on re-design and clean-up of dashboard view.
2023-02-19 09:48:43 -05:00
Matt Scott
d70ded18c2 Added head_styles Jinja block to base view template head section to allow for page specific style injections.
Continuing work on the re-design and clean-up of the dashboard view.
2023-02-19 09:11:44 -05:00
Matt Scott
58aabacd91 Re-formatted base view template to be more in-line with PEP8 standards.
Working on dashboard clean-up and redesign.

Added custom Jinja date/time formatting function to utils.py.
2023-02-19 08:52:00 -05:00
Matt Scott
bad36b5e75 Added default CAPTCHA settings to default configuration.
Added flash_sessions directory pattern to git ignore file.
2023-02-18 19:18:59 -05:00
Matt Scott
e34de31186
Merge pull request from AgentTNT/Fix-Reqs-2023.02.18
Fix reqs and Flask Migrate Order
2023-02-18 12:51:30 -05:00
Tyler Todd
7a61c56c49 Fix reqs and Flask Migrate Order 2023-02-18 17:38:43 +00:00
Matt Scott
516bc52c2f Revert "Revert "Merge pull request from AgentTNT/AdminLTE-Upgrade""
This reverts commit e2ad3e200179dd02bd8dfe42913b59bae9bd4263.
2023-02-18 11:04:14 -05:00
Matt Scott
839c1ecf17 Revert "Revert "Error pages updated for Font Awesome v6 - fa-solid""
This reverts commit 35493fc218d1a27bfc36c2f6b056fb134542a26f.
2023-02-18 11:02:47 -05:00
Matt Scott
e2ad3e2001 Revert "Merge pull request from AgentTNT/AdminLTE-Upgrade"
This reverts commit 929cb6302da6bc0d78e2a7af9deb77cee9d80732, reversing
changes made to 0418edddd9e5ddba0fdbe2b2abd2cf016cf887a3.
2023-02-18 09:04:37 -05:00
Matt Scott
35493fc218 Revert "Error pages updated for Font Awesome v6 - fa-solid"
This reverts commit e1bbe10fc31b9ef93492ad584e6af48a7fd99bdd.
2023-02-18 08:53:46 -05:00
Matt Scott
8ae8d33c12 Removed duplicate entries from requirements.txt file. 2023-02-17 20:10:34 -05:00
Matt Scott
47b50e5e1e Updated default app config to comment out MySQL default settings. 2023-02-17 19:35:36 -05:00
Matt Scott
d2f135cc6e Removed temporary style from login form that was used for recent development. 2023-02-17 19:07:05 -05:00
Matt Scott
e82759cbc4 Updated Docker file to include npm as a new requirement for the admin-lte npm module.
Also added session persistence setting to default and docker configuration files.

Changed the default persistence configuration of the default config file to use SQLite instead of MySQL.
2023-02-17 19:00:09 -05:00
Matt Scott
d12f03c734 Corrected dependency conflicts with the recently merged requirements.txt file.
Updated minor formatting issue with app's run.py file.
2023-02-17 18:32:36 -05:00
Matt Scott
1e86ef676a Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-02-17 18:24:35 -05:00
Matt Scott
929cb6302d
Merge pull request from AgentTNT/AdminLTE-Upgrade
[WIP] Admin LTE & Bootstrap Upgrade
2023-02-17 18:17:47 -05:00
Matt Scott
2ff01fbfe9
Merge branch 'master' into AdminLTE-Upgrade 2023-02-17 18:17:32 -05:00
Tyler Todd
9a7bd27fe3 Formatting changes 2023-02-17 23:07:36 +00:00
Tyler Todd
9b696a42a4 PR Conflict resolution 2023-02-17 22:53:08 +00:00
Tyler Todd
d0961ca5e7 Fix user auth history modal and provide more info 2023-02-17 22:47:23 +00:00
Tyler Todd
a368124040 Font Awesome v6 2023-02-17 22:35:46 +00:00
Tyler Todd
62d95e874a Final page edits for bootstrap v4 and Admin LTE v3.2 2023-02-17 22:25:11 +00:00
Tyler Todd
e1bbe10fc3 Error pages updated for Font Awesome v6 - fa-solid 2023-02-17 21:52:55 +00:00
Matt Scott
0418edddd9
Merge pull request from famedly/shine/config_table_key_uniqueness
fix: making the key name in the config database unique
2023-02-17 13:49:38 -05:00
Matt Scott
ef3880f76d
Merge pull request from VassilisAsteriou/auto_ptr_bugfix
Changed auto_ptr() logic to remove-then-add
2023-02-17 13:35:35 -05:00
Matt Scott
145358113d
Merge pull request from raunz/preserve_history
Preserve domain records history after domain deletion
2023-02-17 13:30:02 -05:00
Matt Scott
c27bf53445
Merge pull request from raunz/perf_tuning
Domain records list performance improvement - removing Setting.get query from loop
2023-02-17 13:28:35 -05:00
Matt Scott
5cdacc2e71 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-02-17 12:19:43 -05:00
Matt Scott
2a3ffe8481
Merge pull request from ymage/bugfix_tests
Improve bugfix tests
2023-02-17 12:19:35 -05:00
Matt Scott
f1b6bef1ab
Merge pull request from unilogicbv/routes_index_otp_force_oauth
routes/index.py: otp_force shouldn't apply to OAuth
2023-02-17 12:14:15 -05:00
Matt Scott
c825a148f2 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-02-17 12:04:26 -05:00
Matt Scott
ba14d52c8d
Merge pull request from AdvanticGmbH/assoc_domain_list
Allow to manage associated domains under account edit
2023-02-17 12:04:16 -05:00
Matt Scott
e025bc7343 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-02-17 08:47:38 -05:00
Tyler Todd
f888bd79f8 domain -> Changed pull-* to float-* 2023-02-14 18:25:06 +00:00
Tyler Todd
e0f939813e Tabs on dashboard.html now fade effect when switching between
First pass at HTML conversion from Master/Slave to Primary/Secondary (TODO: Backend)
Start work on migrating admin_auth_settings to Bootstrap v4
admin_setting_basic -> Change plain text for On/Off to toggles in current state, and changed "Action" column to the opposite toggle of current setting
dashboard_domain -> Reduce deuplicate code for the new dropdown-menu for Actions
register -> Add exclamation icon in front of error text
template_add -> changed box-body to card-body
user_profile -> Fixed tab naviation for Bootstrap v4. Tabs also fade between changes
2023-02-14 02:11:13 +00:00
Tyler Todd
48f80b37ed potential regex code fix for email validation 2023-02-13 15:38:33 +00:00
Rauno Tuul
642fb1605d Move pretty_ipv6_ptr setting retrieval out of record list loop 2023-02-13 14:43:22 +02:00
Rauno Tuul
7221271a7b Preserve domain records history after domain deletion. 2023-02-13 12:08:03 +02:00
Rauno Tuul
75b33b66b7 Merge branch 'historyfix' of github.com:raunz/PowerDNS-Admin into historyfix 2023-02-13 10:26:42 +02:00
Rauno Tuul
187b55e23a Patch API record update/delete logging to match current logging format 2023-02-13 10:25:17 +02:00
Tyler Todd
16d7a4f71e Add shadow element to some cards 2023-02-13 04:43:06 +00:00
Tyler Todd
d6605790bd More navigation Header Changes
"container-fluid" aditions
More button changes from flat to round and icon placement changes
2023-02-13 04:38:54 +00:00
Tyler Todd
c00ddea2fc More page formatting
Added server-side logic for register.html validation
Keep form firelds on register.html in the event of wrong input fields to save users from retyping info
More button rounding
2023-02-13 03:57:21 +00:00
Kateřina Churanová
c23e89bde3
Merge branch 'PowerDNS-Admin:master' into shine/config_table_key_uniqueness 2023-02-09 12:29:14 +00:00
Rauno Tuul
02700ee7b0 Patch API record update/delete logging to match current logging format 2023-02-08 17:03:35 +02:00
Vassilis Asteriou
0568a90ec1 Changed auto_ptr() logic to remove-then-add 2023-02-08 15:27:45 +02:00
Tyler Todd
d38a919644 Update yarn.lock packages 2023-02-06 15:49:39 +00:00
Tyler Todd
ac786f45be Remove btn-flat to convert to round buttons (first pass)
Convert col-xs-* to just col-* as part of bootstrap v3 -> v4
Convert box-* -> card-* as part of bootstrap v3 -> v4
Moved domain actions on main dashboard to a dropdown menu to avoid clutter
Added "Log Out" to top header left
Hid OTP on admin edit user to only show the disable card & options if the user account has OTP enabled
2023-02-06 15:45:13 +00:00
Tyler Todd
7f25e3b555 Initial go at upgrading from Bootstap v3 to v4 and to AdminLTE v3.2.0 2023-02-02 21:19:15 +00:00
Tyler Todd
e411bc9f19 Enable CAPTCHA 2023-01-30 22:46:59 +00:00
Matt Scott
c5b9e24604
Merge pull request from Ssshafi/master
Use SITE_NAME as title
2023-01-26 18:21:33 -05:00
Sshafi
91c1907486
Update login.html
Use SITE_NAME for login box title on login page (with default value).
This can be useful when using multiple powerdns admin in an organization.
2023-01-26 00:02:08 +01:00
Sshafi
b607c1b7ff
Update base.html
Use SITE_NAME for upper left title on base page.
This can be useful when using multiple powerdns admin in an organization.
2023-01-25 23:59:35 +01:00
Matt Scott
d50d57bc70
Merge pull request from pneb/patch-6
fix: Potential fix for a regex bug
2023-01-25 16:19:27 -05:00
Matt Scott
04ee128161 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2023-01-24 05:46:15 -05:00
Matt Scott
51249aecd3
Merge pull request from corubba/feature/privacy-first
Privacy first
2023-01-24 05:34:30 -05:00
Matt Scott
948973ac83 Merge branch 'feature/privacy-first' 2023-01-24 05:32:38 -05:00
Matt Scott
0c42bdad5f
Merge pull request from Metrax/issue-1358
Fixing Wrapping in History Details Modal in Dashboard
2023-01-14 12:04:43 -05:00
Robert Walter
246ad7f7d2
Fixing Wrapping in History Details Modal in Dashboard
resolves 
2023-01-13 10:05:20 +01:00
Bernward Sanchez
18bc336d7a
Potential fix 2023-01-11 18:21:40 +08:00
Matt Scott
bb29c27430
Merge pull request from Metrax/support-pdns4.7
Support pdns4.7
2023-01-10 08:01:17 -05:00
Matt Scott
debeda5b74
Merge pull request from pneb/patch-5
patch(lib/utils.py): Fixes pretty_domain_name issue
2023-01-10 08:00:28 -05:00
Robert Walter
c02cb3b7fe
Model change: Changing domain model type variable to 8 chars
PowerDNS 4.7 is supporting 2 new zone types: "producer" & "consumer"
Due to the domain type variable is limited to 6 chars, PDA Zone update will fail if producer or cusomer zones exist.
To solve this problem, this commit increases the lenght of the domain model type variable to 8 chars.
2023-01-10 13:51:04 +01:00
Robert Walter
9088f93233
Upgrade Database: Changing domain table type column to 8 chars
PowerDNS 4.7 is supporting 2 new zone types: "producer" & "consumer"
Due to the domain type column is limited to 6 chars, PDA Zone update will fail if producer or cusomer zones exist.
To solve this problem, this commit increases the lenght of the domain type column to 8 chars.
2023-01-10 13:49:16 +01:00
Bernward Sanchez
b163e517bb
Update utils.py 2023-01-09 11:10:22 +08:00
Matt Scott
53f6f3186e
Merge pull request from trappiz/master
Bump alpine to 3.17 in dockerfile
2023-01-07 19:28:31 -05:00
Niklas Engvall
4de7bbe354 Use alpine 3.17 as base + pip fix to remove warnings 2023-01-06 16:43:48 +01:00
Niklas Engvall
7e973c7219 bump alpine to 3.15 2022-12-23 12:18:00 +01:00
Bernward Sanchez
c7eaec27d8
Update utils.py 2022-12-23 08:23:14 +08:00
Ymage
1d885278d4 Cosmetic 2022-12-22 22:55:05 +01:00
Ymage
7d153932b3 Fix back_populate relationships 2022-12-22 22:50:01 +01:00
Ymage
8dd03a4d85 Update tests fix
Fix migration init_db 'id'
Handle app context when needed
Fix conftest fixtures
Rearrange test Dockerfiles
Hide DeprecationWarning during pytest execution
Upgrade all python packages
2022-12-22 22:47:02 +01:00
Matt Scott
33ec2acd3f Added a GitHub labels configuration file. 2022-12-22 07:20:02 -05:00
Matt Scott
eb16353476 Testing new GitHub Dependabot configuration. 2022-12-22 06:20:12 -05:00
Matt Scott
32e19777cc Updating GitHub stale automation configuration to match new label schema. 2022-12-22 06:00:06 -05:00
corubba
8d849ee2a1 Cleanup
The `unit/apikey` directory is removed because it does not contain any
tests. Same for `unit/test_decorators.py`.
The `fixture` module is renamed to the special-name `conftest` [0] so
they are available in all tests without the need to import them. With
that in place, I removed all now unneeded or previously already unused
imports from the tests.
Also removed that wierd `sys.path` bit from `unit/zone/test_admin_apikey.py`,
no idea what that was originally intended for.

[0] https://docs.pytest.org/en/6.2.x/fixture.html#conftest-py-sharing-fixtures-across-multiple-files
2022-12-19 09:37:01 +01:00
corubba
e920bf5009 Fix broken code
PR  is the culprit, as was already predicted in the review.
2022-12-19 09:37:01 +01:00
corubba
b9eb593acd Fix tests
Increased the version of pytest to make it work with py 3.10 [0].
The GET calls no longer return list but the object itself, fixed the
tests and assertions to account for that. The tests did not account for
the later added `allow_user_remove_domain` setting. And there were
issues with missing and non-stopped patchers/mocks.

Now all tests are at least passing.

[0] https://github.com/pytest-dev/pytest/pull/8540
2022-12-19 09:37:01 +01:00
corubba
44cf98a159 Fix test docker
The Dockerfile did not work as is, because the dependencies in
requirements.txt are newer than the stretch-image with its python v3.5
can support/run. Use stable debian with the lts nodejs instead, plus
had to add some libs to make the wheel build succeed.
jsonschema v4 breaks things, so its version needs to be pinned until
bravado is fixed [0].

[0] https://github.com/Yelp/bravado-core/pull/385/files#r731674447
2022-12-19 09:37:01 +01:00
Matt Scott
0404748e6e
Merge pull request from pneb/master
enhancement: Added 2 new files (auto-setup)
2022-12-15 06:40:32 -05:00
Bernward Sanchez
3aa6d1f258
Create setup_win.bat 2022-12-15 12:54:31 +08:00
Bernward Sanchez
644be65495
Create setup_linux.sh 2022-12-15 12:53:53 +08:00
Matt Scott
3a6d173d05
Merge pull request from PowerDNS-Admin/revert-1331-patch-4
Revert "enhancement(routes/index.py): OIDC supports HTTP Scheme now"
2022-12-14 20:38:15 -05:00
Matt Scott
89f3d4d01a
Revert "enhancement(routes/index.py): OIDC supports HTTP Scheme now" 2022-12-14 20:37:30 -05:00
Matt Scott
96a88d918c
Merge pull request from pneb/patch-4
enhancement(routes/index.py): OIDC supports HTTP Scheme now
2022-12-14 19:01:27 -05:00
Bernward Sanchez
f6c49c379d
Update index.py 2022-12-15 06:13:27 +08:00
Matt Scott
30ed68471e
Merge pull request from Metrax/master
Fixing Validation problem on LDAP form
2022-12-13 20:59:22 -05:00
Matt Scott
8373363c4d
Merge pull request from jbe-dw/fixLDAPDeprecatedOpt
Draft: Fix pyhton-ldap upgrade
2022-12-13 20:56:03 -05:00
Matt Scott
1a05518018
Merge pull request from caraar12345/aaroncarson/fix/1329
Fix 
2022-12-13 20:54:26 -05:00
Aaron Carson
ff671ebabe Fix 1329 2022-12-14 00:34:12 +00:00
Matt Scott
9a38e1758f
Merge pull request from Ssshafi/patch-1
Update login.html
2022-12-13 09:04:40 -05:00
Matt Scott
c03f799c4a
Merge pull request from dmcken/docs-updates
Docs updates
2022-12-13 08:20:27 -05:00
Sshafi
d0290ac469
Update login.html
Use SITE_NAME for login box title on login page.
This can be useful when using multiple powerdns admin in an organization.
2022-12-13 09:10:21 +01:00
David Mc Ken
4d529ec1d6 Linted the main menu. 2022-12-13 02:29:05 -04:00
David Mc Ken
b0159beaec Add SQLite as a supported database. 2022-12-12 23:12:03 -04:00
David Mc Ken
dcbc4c3f7e Add PostgreSQL install documentation. 2022-12-12 22:50:26 -04:00
David Mc Ken
94ce26eaad Minor cleanup of MySQL docs. 2022-12-12 22:49:51 -04:00
David Mc Ken
cc63d069f6 Fill in MySQL server installation directions. 2022-12-12 22:41:41 -04:00
David Mc Ken
6f450457ef Update env docs from legal_envvars 2022-12-12 22:06:44 -04:00
David Mc Ken
89d0ab12f5 Add general arch doc. 2022-12-12 22:06:10 -04:00
David Mc Ken
23274301f8
Merge branch 'PowerDNS-Admin:master' into docs-updates 2022-12-12 17:17:23 -04:00
Matt Scott
10fd8b1563
Merge pull request from domXmob/master
fix of issue  & 
2022-12-12 14:21:55 -05:00
Dominik Fahr
650ea7660b updated image docker-compose.yml & deployment.yml
Docker Hub Repository Moved! 
2022-12-12 19:24:46 +01:00
David Mc Ken
12892d70a5
Merge branch 'PowerDNS-Admin:master' into docs-updates 2022-12-12 13:51:59 -04:00
Dominik Fahr
97a79645b0 fix of issue
split record by "."
idna.encode leads into full stop if the string starts with "_" or "-"
2022-12-12 17:31:32 +01:00
Dominik Fahr
52169f698c undo of commit a7f55de
did not fix issue 
leaded into issue 
2022-12-12 17:30:42 +01:00
Jérôme BECOT
8d5b92402d
fix: Remove deprecated option OPT_X_TLS 2022-12-12 15:57:11 +01:00
Robert Walter
23e0fdbedf Fixing Validation Problem at LDAP Form 2022-12-12 12:32:32 +01:00
Matt Scott
ce4447bb12 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2022-12-11 17:43:50 -05:00
Matt Scott
4e2ea4bc5e Revert "Removed mysqlclient requirement based on issue 1305. Wiki documentation has already been updated to reflect this change."
This reverts commit 542af959e17f57af44a1395c7f7cbbed8aab0d07.
2022-12-11 17:43:02 -05:00
Matt Scott
2565e4faff
Merge pull request from pneb/patch-3
feature: Added IDN Domain Search function as requested
2022-12-10 00:07:25 -05:00
Bernward Sanchez
dfdb0dca17
Update domain.py 2022-12-10 10:37:06 +08:00
Matt Scott
48c303dd84 Corrected typo / instructions related to Docker in the project README file. 2022-12-09 17:03:46 -05:00
Matt Scott
dec457e2ea Tweaked job name of the Docker Image workflow. 2022-12-09 17:02:36 -05:00
Matt Scott
4940e280bb Tweaked name of Docker image build workflow for Docker image status updates. 2022-12-09 16:58:40 -05:00
Matt Scott
81020fe2b5 Tweaked Docker status badge text in project README. 2022-12-09 16:57:13 -05:00
Matt Scott
56ee0d674e Added very basic Docker Compose / Portainer template for the current project. 2022-12-09 16:56:03 -05:00
Matt Scott
02e4fcc20a Updating project README to include updated status badges since LGTM is shutting down soon. 2022-12-09 16:42:53 -05:00
Matt Scott
45071c3a9f Updated CodeQL workflow to support manual dispatch. 2022-12-09 16:40:39 -05:00
Matt Scott
e810dc21e0 Merge branch 'master' of github.com:PowerDNS-Admin/PowerDNS-Admin 2022-12-09 15:59:28 -05:00
Matt Scott
c90592e039 Updated build/deploy workflow to include updated naming. 2022-12-09 15:58:55 -05:00
Matt Scott
cbcb4dfb5c
Merge pull request from PowerDNS-Admin/1313-docker-image-reference-update
Docker Image Repository References Updated
2022-12-09 12:56:49 -05:00
Matt Scott
3889ceaf4c Updated documentation to reflect the new Docker Hub home of the project's Docker images. 2022-12-09 12:55:45 -05:00
Matt Scott
ceedf895d8
Merge pull request from PowerDNS-Admin/1314-docker-image-build-workflow
Updated Docker Image Publishing Workflow
2022-12-09 12:31:36 -05:00
Matt Scott
58d0d6b71d Updated Docker image publish workflow to enable manual dispatch of the workflow. 2022-12-09 12:30:18 -05:00
Matt Scott
e6f6222762
Merge pull request from PowerDNS-Admin/1311-build-publish-workflow-updates-needed
Working on updated workflow for Docker image publishing.
2022-12-09 11:47:55 -05:00
Matt Scott
54775e6c69 Working on updated workflow for Docker image publishing. 2022-12-09 11:41:38 -05:00
David Mc Ken
02b7bda292 Update supported versions of python. 2022-12-09 12:32:23 -04:00
Matt Scott
33277a316d
Merge pull request from dmcken/setup-new-wiki-docs-2
Fix PostgreSQL documentation
2022-12-09 10:17:36 -05:00
David Mc Ken
022e87615d
Merge branch 'PowerDNS-Admin:master' into setup-new-wiki-docs-2 2022-12-09 11:00:06 -04:00
David Mc Ken
4f656d827e Move DB directions to that section of docs. 2022-12-09 10:56:08 -04:00
Matt Scott
cec8003379
Merge pull request from PowerDNS-Admin/1305-remove-mysqlclient-from-requirementstxt
Removed mysqlclient from requirements.txt
2022-12-09 09:55:21 -05:00
Matt Scott
542af959e1 Removed mysqlclient requirement based on issue 1305. Wiki documentation has already been updated to reflect this change. 2022-12-09 09:48:51 -05:00
Matt Scott
0e83a1f27e
Merge pull request from dmcken/setup-new-wiki-docs-2
Setup new wiki docs 
2022-12-09 09:46:31 -05:00
David Mc Ken
b1a3a98692 Add general install notes. 2022-12-09 07:25:25 -04:00
David Mc Ken
8b986db2ac Desc added for what is being created. 2022-12-09 07:11:12 -04:00
David Mc Ken
5ab04509c3 Remove MySQL specific documentation 2022-12-09 07:03:07 -04:00
David Mc Ken
e06fcd75ce Moved database specific packages to the setup MySQL directions. 2022-12-09 07:02:26 -04:00
David Mc Ken
92ef3d8610
Merge branch 'PowerDNS-Admin:master' into setup-new-wiki-docs-2 2022-12-09 06:49:18 -04:00
David Mc Ken
a5fdd8ffc4 Change notes to headers and move setup of first user to getting started. 2022-12-09 06:46:40 -04:00
David Mc Ken
496222e6b7 Remove TODO and add setup of first user. 2022-12-09 06:46:02 -04:00
Matt Scott
9a3fdfc986
Merge pull request from rootwyrm/alpine_python-openldap
Update to python-ldap 3.4.2
2022-12-08 22:14:54 -05:00
Matt Scott
3c0b0a1b2d
Merge pull request from unilogicbv/admin_edit_key_user_role_default
admin_edit_key: default to User role for new api keys
2022-12-08 22:13:23 -05:00
David Mc Ken
4864bff51d
Merge branch 'PowerDNS-Admin:master' into setup-new-wiki-docs-2 2022-12-08 23:11:29 -04:00
David Mc Ken
5f643ccb78 Explain where the config.py is. 2022-12-08 23:11:04 -04:00
Matt Scott
2cd8f60f8d
Merge pull request from unilogicbv/models_user_plain_text_password_guard
models/user.py: properly guard plain_text_password property
2022-12-08 22:10:21 -05:00
Matt Scott
7873e5f3f8
Merge pull request from unilogicbv/models_user_totp_valid_window
models/user.py: add non-zero valid_window to totp.verify
2022-12-08 22:05:34 -05:00
David Mc Ken
099579de11 Add db setup considerations for MySQL. 2022-12-08 23:04:23 -04:00
Matt Scott
e823f079b7
Merge pull request from WhatshallIbreaktoday/master
allow null/None JSON data (Used for pdns notifies via api and by LEGO-ACME v 4.9.0)
2022-12-08 21:57:18 -05:00
David Mc Ken
09b661a8a3 Fix spelling mistake 2022-12-08 22:53:23 -04:00
David Mc Ken
117659dd31 Moved database setup to database-setup. 2022-12-08 22:51:59 -04:00
Matt Scott
d2cb80f747
Merge pull request from wrouesnel/wrouesnel/fix_env_migrations
Fix handling of passwords with % in the SQLALCHEMY_DATABASE_URI
2022-12-08 21:51:38 -05:00
David Mc Ken
fa6c58978b Cleaned up mysql setup process. 2022-12-08 22:50:25 -04:00
David Mc Ken
be933db09a Add documentation on how to allow remote connection on postgres. 2022-12-08 22:37:29 -04:00
Matt Scott
a1619cfde9
Merge pull request from pneb/patch-2
enhancement: Small fix
2022-12-08 21:35:09 -05:00
David Mc Ken
9f8c416432 Make db list a list. 2022-12-08 22:06:24 -04:00
David Mc Ken
6c65265d1c Merge branch 'setup-new-wiki-docs-2' of github.com:dmcken/PowerDNS-Admin into setup-new-wiki-docs-2 2022-12-08 22:04:26 -04:00
David Mc Ken
3c5b883405 Add DB setup README.md 2022-12-08 22:02:51 -04:00
David Mc Ken
d6838cf802 Add mysql DB URI 2022-12-08 22:02:37 -04:00
David Mc Ken
83e2d3f655
Merge branch 'PowerDNS-Admin:master' into setup-new-wiki-docs-2 2022-12-08 21:56:50 -04:00
David Mc Ken
1cd14210b9 Change header 2022-12-08 21:56:16 -04:00
David Mc Ken
2129c050e8 Minor re-formatting. 2022-12-08 21:54:18 -04:00
David Mc Ken
4952f2de06 Add postgres dependencies for postgres. 2022-12-08 21:48:29 -04:00
David Mc Ken
b5661b61a0 Add postgres URI to getting-started. 2022-12-08 21:47:28 -04:00
David Mc Ken
55537e5bce Update links to database setup. 2022-12-08 21:46:49 -04:00
David Mc Ken
dc495ce426 Move preparation to database-setup. 2022-12-08 21:44:52 -04:00
Bernward Sanchez
2656242b45
Update api_key.py
I added the parentheses to the `db.session.rollback` line to call the method, which will now properly roll back any changes made to the database if an error occurs.
2022-12-09 09:33:17 +08:00
David Mc Ken
2dec2ad204 Fix default values. 2022-12-08 21:23:28 -04:00
David Mc Ken
19e5750974 Dropped documentation to 1.1.x since that is the version being used. 2022-12-08 21:22:13 -04:00
David Mc Ken
7a2f83a888 Update environment var required. 2022-12-08 21:19:55 -04:00
David Mc Ken
d2c0c94e61 Flesh out docker setup. 2022-12-08 21:19:18 -04:00
David Mc Ken
76315aac6d Add required column to table. 2022-12-08 21:16:26 -04:00
David Mc Ken
fb387eb570 Minor formatting change. 2022-12-08 21:13:26 -04:00
David Mc Ken
e0970541b4 Update links to be more readable. 2022-12-08 21:10:45 -04:00
David Mc Ken
2c34307365 Move environment vars to configuration 2022-12-08 20:59:35 -04:00
David Mc Ken
57b0927718 Minor formatting changes to README. 2022-12-08 20:56:40 -04:00
David Mc Ken
5a58e70e8c Add link to environment variables docs. 2022-12-08 20:53:30 -04:00
David Mc Ken
16fbf412d8 Fill in DB URI and secret key. 2022-12-08 20:53:14 -04:00
Matt Scott
a7f55dec17
Merge pull request from pneb/patch-1
patch: Fixes 
2022-12-08 19:52:58 -05:00
David Mc Ken
59ab3dcecd Add link to getting started. 2022-12-08 20:44:10 -04:00
David Mc Ken
254c00ae92 Add placeholder for getting started. 2022-12-08 20:43:54 -04:00
David Mc Ken
3bcda68df9 Add placeholders for environment variables and Docker. 2022-12-08 20:43:10 -04:00
David Mc Ken
1ced360e5f Fixed links to gunicorn docs. 2022-12-08 20:39:25 -04:00
David Mc Ken
c5524dc909 Fix link to gunicorn and nginx setup. 2022-12-08 20:36:55 -04:00
David Mc Ken
fc01be4cad Move systemd service to install folder. 2022-12-08 20:34:29 -04:00
David Mc Ken
9a4acf5305 Move configure Active directory auth to config folder. 2022-12-08 20:33:57 -04:00
Bernward Sanchez
3e68044420
Update utils.py
This should fix the error you were experiencing, as it will now only attempt to process the `data` argument if it is a tuple containing two elements. If the `data` argument is not in the expected format, the function will simply return an empty string instead of raising an exception.
2022-12-09 08:15:13 +08:00
Matt Scott
0bc7f2765b
Merge pull request from dmcken/setup-new-wiki-docs-1
Wiki content updated to work from project file structure.
2022-12-08 12:52:40 -05:00
David Mc Ken
c7dbb33dd7 Rename Fedora 23 filename. 2022-12-08 13:35:35 -04:00
David Mc Ken
99370a9afb Update the guinicorn. 2022-12-08 13:33:43 -04:00
David Mc Ken
31fd350f01 Add Fedora 23 directions and links. 2022-12-08 13:17:09 -04:00
David Mc Ken
4f177407dd Update github urls. 2022-12-08 13:13:05 -04:00
David Mc Ken
45e05f9487 Minor formatting updates. 2022-12-08 13:10:44 -04:00
David Mc Ken
0bdd09b3f1 Update links. 2022-12-08 13:06:00 -04:00
David Mc Ken
6babb1cd03 Update links. 2022-12-08 13:03:15 -04:00
David Mc Ken
3e9fc1f8fc Minor update to header. 2022-12-08 13:00:00 -04:00
David Mc Ken
88b7331db1 Fix missing extensions. 2022-12-08 12:34:31 -04:00
David Mc Ken
2c7c75b3a6 Update DynDNS2.md to features sub-folder. 2022-12-08 12:32:32 -04:00
David Mc Ken
7df3f03362 Move web server config to separate folder. 2022-12-08 12:29:50 -04:00
David Mc Ken
4584b2aa24 Move and fix links for install guides. 2022-12-08 12:26:00 -04:00
David Mc Ken
370aad4dfa Github seems to require the extension. 2022-12-08 12:22:57 -04:00
David Mc Ken
305e529cfe Fix header and update preparation links. 2022-12-08 12:21:45 -04:00
David Mc Ken
d259a6494e Move preparation guides to sub-folder. 2022-12-08 12:20:40 -04:00
David Mc Ken
5f750d1bb8 Move Home.md to README.md 2022-12-08 12:17:08 -04:00
Matt Scott
f6bca2c999
Merge pull request from PowerDNS-Admin/1297-move-project-wiki-into-files
Added current wiki content to project files.
2022-12-08 10:54:41 -05:00
Matt Scott
3cdf2b6b7c Added current wiki content to project files for ongoing maintenance. Existing wiki will be updated with a link reference to the wiki files. 2022-12-08 10:52:02 -05:00
Will Rouesnel
25ebbf132c
Fix handling of passwords with % in the SQLALCHEMY_DATABASE_URI
Fix Flask-Migrate ValueError from occurring when a password has '%'
characters in it when specified via SQLALCHEMY_DATABASE_URI.
2022-11-04 11:59:59 +11:00
jbe-dw
f6289d140c
Merge pull request from PowerDNS-Admin/api-doc
Update API.md
2022-10-14 16:03:43 +02:00
jbe-dw
d88da0fde3
Update API.md 2022-10-14 15:33:33 +02:00
WhatshallIbreaktoday
d25a22272e allow null/None JSON data
This change permits to proxy pdns zone notify api requests (which are expected to be with empty body)
2022-10-12 08:10:35 +02:00
jbe-dw
f8048bf6aa
Merge pull request from corubba/bugfix/api-order
fix: deletes shall come first in api payload ()
2022-09-23 09:20:41 +02:00
corubba
cb835978df Fix order of operations in api payload
PDNS checks that when a `CNAME` rrset is created that no other rrset of
the same name but a different rtype exists. When changing a record type
to `CNAME`, PDA will send two operations in one api call to PDNS: A
deletion of the old rrset, and the addition of the new rrset. For the
check in PDNS to pass, the deletion needs to happen before the addition.
Before PR  that was the case, the first api call did deletions and
the second handled additions and changes. Currently the api payload
contains additions first and deletions last. PDNS applies these in the
order they are passed in the payload to the api, so to restore the
original/correct/working behaviour the order of operations in the api
payload has to be reversed.

fixes 
2022-09-23 00:19:22 +02:00
Pascal de Bruijn
846c03f154 models/user.py: add non-zero valid_window to totp.verify
PyOTP's totp.verify defaults to the valid_window of zero, which means
it will reject valid codes, if submitted just past the 30 sec window.
It also means, users will run into authentication issues very quickly
if their phones time-sync isn't perfect.

Therefore valid_window should at the very least be 1 or more, settting
it higher trades security for robustness, especially with regard to
time desync issues.
2022-09-07 14:23:34 +02:00
Pascal de Bruijn
41a3995865 routes/index.py: otp_force shouldn't apply to OAuth
as 2FA policies are typically enforced on the OAuth proviers end

Relates to 
2022-09-06 16:28:45 +02:00
Pascal de Bruijn
4fd1b10018 models/user.py: properly guard plain_text_password property
Resolves the following issue, which occurs with force_otp enabled
and OAuth authentication sources:

File "/srv/powerdnsadmin/powerdnsadmin/models/user.py", line 481, in update_profile
  "utf-8") if self.plain_text_password else user.password
AttributeError: 'User' object has no attribute 'plain_text_password'
2022-09-06 15:31:43 +02:00
Pascal de Bruijn
9bf74a6baf admin_edit_key: default to User role for new api keys
hopefully this will prevent accidental administator api keys from being created
2022-09-06 15:25:28 +02:00
Melchior NOGUES
4383c337d4
fix: ldap type ad search user group when nested groups 2022-09-02 17:12:08 +02:00
Phil Jaenke
5f304ee29a
Update to python-ldap 3.4.2
Minor version bump. This is necessary to resolve build issues on Alpine 3.16+ without impacts for any other distributions.
2022-08-22 20:40:17 -04:00
Vasileios Markopoulos
204c996c81
Merge pull request from corubba/bugfix/changelog-hyphen
Fix rrset changelog for names with hyphen
2022-07-01 15:52:44 +03:00
AdvanticGmbH
3c68b611c6
Update powerdnsadmin/routes/admin.py
Looks good to me

Co-authored-by: Corubba <97832352+corubba@users.noreply.github.com>
2022-06-29 08:56:01 +02:00
AdvanticGmbH
cfab13824d Add history entries for association changes of domains 2022-06-28 11:19:00 +02:00
AdvanticGmbH
6a2ba1b1c3 Add list to manage with an account associated domains 2022-06-28 11:18:53 +02:00
jbe-dw
e6f6f9cea4
Update Javascript libraries ()
This PR includes all dependabot patches and replace jsmin (abandoned) with rjsmin
2022-06-24 23:23:56 +02:00
dependabot[bot]
e7fbc7af37
Bump shell-quote from 1.6.1 to 1.7.3
Bumps [shell-quote](https://github.com/substack/node-shell-quote) from 1.6.1 to 1.7.3.
- [Release notes](https://github.com/substack/node-shell-quote/releases)
- [Changelog](https://github.com/substack/node-shell-quote/blob/master/CHANGELOG.md)
- [Commits](https://github.com/substack/node-shell-quote/compare/1.6.1...1.7.3)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-24 23:03:12 +02:00
Jérôme BECOT
41642fcea4
fix: Update JS minifier library 2022-06-24 23:03:01 +02:00
dependabot[bot]
18150eea34
Bump moment from 2.22.2 to 2.29.2
Bumps [moment](https://github.com/moment/moment) from 2.22.2 to 2.29.2.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.22.2...2.29.2)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-24 23:03:01 +02:00
dependabot[bot]
34be227381
Bump cached-path-relative from 1.0.2 to 1.1.0
Bumps [cached-path-relative](https://github.com/ashaffer/cached-path-relative) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/ashaffer/cached-path-relative/releases)
- [Commits](https://github.com/ashaffer/cached-path-relative/commits)

---
updated-dependencies:
- dependency-name: cached-path-relative
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-24 23:03:00 +02:00
dependabot[bot]
289faa5019
Bump jquery-ui from 1.12.1 to 1.13.0
Bumps [jquery-ui](https://github.com/jquery/jquery-ui) from 1.12.1 to 1.13.0.
- [Release notes](https://github.com/jquery/jquery-ui/releases)
- [Commits](https://github.com/jquery/jquery-ui/compare/1.12.1...1.13.0)

---
updated-dependencies:
- dependency-name: jquery-ui
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-24 23:03:00 +02:00
dependabot[bot]
a88f4a66c6
Bump path-parse from 1.0.5 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.5 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-24 23:02:56 +02:00
jbe-dw
6908f1d209
Allow new domains to be absolute ()
author: corubba
2022-06-24 23:00:33 +02:00
corubba
5036619a67 Allow new domains to be absolute
Allow the new domain name to be input absolute (with a dot at the end).
To keep the rest of the logic working as-is, remove it fairly early in
the function.

Would have loved to use `str.removesuffix()` but that's python v3.9+.
2022-06-23 22:31:00 +02:00
corubba
9890ddfa64 Fix rrset changelog for names with hyphen
When clicking the changelog button for a record with the name
`foo-bar.example.org`, the url you get redirected to is
`/domain/example.org/changelog/foo-bar.example.org.-A`. Because of the
non-greedy behaviour of the path converter, the last part gets split at
the *first* hyphen, so the example above gets wrongly dissected into
`record_name=foo` and `record_type=bar.example.org.-A`. This results
for obvious reasons in an empty changelog.

As described in rfc5395 [0], types have to be alphanumerical, so its
converter is changed from path to string.

The hyphen is one of the few characters recommended by rfc1035 [1],
so it is a bad choice as separator. The separator is instead changed to
a slash.
Granted, this does not entirely solve the issue but at least makes it a
lot less likely to happen. Plus, a lot more and other things break in
pda with slashes in names.

[0] https://datatracker.ietf.org/doc/html/rfc5395#section-3.1
[1] https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
2022-06-19 12:16:40 +02:00
jbe-dw
dac232147e
enh: Cookies security ()
author: corruba
2022-06-18 22:51:47 +02:00
jbe-dw
35cbc59016
enh: Update zone using a single api call ()
author: corruba
2022-06-18 22:50:33 +02:00
corubba
3a8ad7c444 Remove OFFLINE_MODE config option 2022-06-18 19:11:16 +02:00
corubba
b809308d31 Add LDAP user images 2022-06-18 19:11:16 +02:00
corubba
607caa1a2d Rework user image handling
Moved all the logic out of the template into a separate endpoint. This
makes it easy to extend to also support images from different sources
like LDAP/SAML/OIDC. Session-based caching is hard to do, so to allow
time-based caching in the browser, the url needs to be unique for every
user by using a query parameter.

Replaced the default/fallback user image with a new one. It is based on
the old one, but does not need css to be visible. And removed said css.

Gravatar has now its own setting named `gravatar_enabled`, which is
disabled by default.
2022-06-18 19:11:13 +02:00
corubba
b795f1eadf Use the doc search directly 2022-06-18 19:07:23 +02:00
corubba
fee26b84ba Remove IE8 polyfills
These old browsers are EOL since 2016 [0], let them finally rest in
peace.

This effectively reverts/replaces commit b8dee5d17056788c2dc9940d14308648e32186d8.

[0] https://web.archive.org/web/20160115070611/https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support
2022-06-18 19:07:23 +02:00
corubba
54b2c5918f Serve the IE8 polyfills from local 2022-06-18 19:07:23 +02:00
corubba
674704609b Always use local fonts 2022-06-18 19:07:23 +02:00
corubba
af902f24a2 Update using only one api call
Starting with the very first commit, the update was always done with
two api calls: one for DELETE and one for REPLACE. It is however
perfectly valid and save to do both at once, which makes it atomic, so
no need for the rollback. Plus it only updates the serial once.
There is no point in sending the full RRset data when deleting it, the
key attributes to identify it are enough. This also make the behaviour
consistent with the api docs [0] where it says "MUST NOT be included
when changetype is set to DELETE."

[0] https://doc.powerdns.com/authoritative/http-api/zone.html#rrset
2022-06-18 18:58:39 +02:00
corubba
52b704baeb Set SameSite on cookies
Setting this attribute on a cookie marks it as non-cross-site, so it
is only send in requests to our own server. It is reasonable that no
one else should need our session or csrf data. Setting it explicitly
also prevents any issues from the ongoing change in browser behaviour [0]
when it is unset.

Seasurf supports the SameSite attribute starting with v0.3. As nothing
obviously broke, I used the opportunity and updated all the way to the
most recent version.

The SeaSurf default for SameSite is already `Lax`, so it only needs to
be set for the session cookie.

[0] https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure
2022-06-18 18:51:42 +02:00
corubba
1a77524447 Allow secure cookies in docker
Setting these two options to True is recommended if (and only if) you
serve PDA via TLS. It will break things on plain-HTTP deployments.
For plain deployments these can be set in the flask config file, for
docker they have to be whitelisted to be set via env vars.
2022-06-18 18:51:42 +02:00
corubba
ae2ad6527a Set csrf cookie to httponly
The CSRF token is currently inserted directly in the template and not
in the browser via JavaScript from the cookie, so making it inaccessible
is not a problem.

The Sesson-cookie is already httponly by default [0].

[0] https://flask.palletsprojects.com/en/2.1.x/config/?highlight=session_cookie_httponly#SESSION_COOKIE_HTTPONLY
2022-06-18 18:51:42 +02:00
corubba
3e462dab17 Fix csrf configuration
CSRF has been initialized *before* the app config was fully read. That
made it impossible to configure CSRF properly. Moved the CSRF init into
the routes module, and switched from programmatic to decorated
exemptions. GET routes don't need to be exempted because they are by
default.
2022-06-18 18:51:40 +02:00
jbe-dw
2c0225e961
feat: Allow underscores and hyphens in account name () 2022-06-18 15:14:37 +02:00
Jérôme BECOT
a87b931520
feat: Move the account parse calls to a method 2022-06-18 14:30:56 +02:00
Jérôme BECOT
eb13b37e09
feat: Add the extra chars as an option 2022-06-18 14:30:56 +02:00
Jérôme BECOT
a3c50828a6
feat: Allow underscores and hyphens in account name 2022-06-18 14:28:32 +02:00
AdvanticGmbH
beed738d02
enh: Improve performance of domain update ()
author: @AdvanticGmbH
2022-06-18 14:23:05 +02:00
RGanor
81f158d9bc
enh: Enforce Record Restrictions in API ()
Co-authored-by: Tom <tom@tom.com>
2022-06-18 14:20:49 +02:00
Vasileios Markopoulos
83d2f3c791
Merge pull request from joshsol1/master
Modification to SAML groups and group management
2022-06-18 13:39:01 +03:00
gadall
bf83e68a4b
Fix DynDNS2 using X-Forwarded-For ()
utils.validate_ipaddress() takes a string, not a list
2022-06-18 13:11:22 +03:00
TomSebty
1926b862b8
feat: Option to forbid the creation of domain if it exists as a record ()
When enabled, forbids the creation of a domain if it exists as a record in one of its parent domains (administrators and operators are not limited though).
2022-06-17 17:50:51 +02:00
jbe-dw
1112105683
feat: Add /api endpoint () 2022-06-17 16:48:23 +02:00
jbe-dw
2a75013de4
Merge pull request from AdvanticGmbH/idna_decode
fix: use idna module to support extended character set
2022-06-17 15:47:55 +02:00
Vasileios Markopoulos
9d7d701cd9
Merge pull request from pixelrebel/saml-fixes
Small fixes to SAML service
2022-06-15 15:56:28 +03:00
RGanor
3aba0693c4
Update README.md for k8s deployment ()
* Update deploy/kubernetes /README.md
2022-06-07 16:28:54 +03:00
RGanor
88c0aaea27
Updated k8s () 2022-06-07 16:22:38 +03:00
Artem Silenkov
bcc8441779
Add yml to deploy on kubernetes () 2022-06-07 16:13:31 +03:00
Vasileios Markopoulos
41343fd598
Merge pull request from corubba/bugfix/rrest-typo
Fix rrest typo in history detail
2022-05-25 10:45:50 +03:00
corubba
f98326ea90 Fix remaining typo occurrence 2022-05-24 23:45:14 +02:00
jbe-dw
0f1102a07b
Merge pull request from jbe-dw/fixADFilter
fix: Active directory filter is broken
2022-05-24 14:19:37 +02:00
Jérôme BECOT
88df88f30b
fix: Active directory filter is broken 2022-05-24 13:58:45 +02:00
jbe-dw
259bd0a906
Merge pull request from corubba/feature/modal-consolidation
enh: Consolidate generic modal code
2022-05-23 22:50:48 +02:00
jbe-dw
06c12cc3ac
Merge pull request from RGanor/master
Added health check
2022-05-23 20:18:17 +02:00
RGanor
1bee833326 Updated the unknown state 2022-05-23 16:46:11 +00:00
jbe-dw
e81453c5e3
Merge pull request from corubba/bugfix/pyOpenSSL
Small bugfixes
2022-05-23 13:59:18 +02:00
Josh Matthews
2020055ab2 added code to pull the operator and admin groups from SAML auth requests 2022-05-23 14:39:29 +10:00
Josh Matthews
715c6b76cd added code to raise user to operator on SAML auth if in the right group 2022-05-23 14:38:16 +10:00
RGanor
83ed5cfb28
Create codeql-analysis.yml () 2022-05-21 11:26:40 +03:00
pixelrebel
8c85e80c2b Add SAML_ATTRIBUTE_GROUP and SAML_GROUP_ADMIN_NAME to the development config, with instructions for use 2022-05-19 20:36:28 -07:00
pixelrebel
e4c8c3892f Use HTTP_X_FORWARDED_PROTO header from reverse proxy to rewrite https:// for SAML request URLs 2022-05-19 19:00:38 -07:00
pixelrebel
9221d58a1b Allow SAML AttributeStatements to be optional 2022-05-19 14:52:51 -07:00
pixelrebel
5b36ad034d Rename incorrect SAML cert/key config variables 2022-05-19 14:02:04 -07:00
corubba
0dfcdb6c3e Fix rrest typo in history detail
There is a misspelling of rrset throughout the history logic, which also
effects the json payload in the database. Code-wise this is a simple
search-and-replace, and the migration will fix the payloads.
2022-05-19 00:53:35 +02:00
corubba
70450315ba Add general modal functions
The two generic modals are defined in the base template, and are used
in various templates. So provide functions and remove duplicate code.
2022-05-19 00:53:20 +02:00
jbe-dw
1961581527
Merge pull request from Metrax/git-ignore
Adding venv and yarn-error.log to gitignore
2022-05-17 23:45:16 +02:00
Metrax
8b105d8aff
Adding venv and yarn-error.log to gitignore
venv: in the wiki, the installation is described with creating the venv into "venv", but only ".venv" is in gitignore
yarn-error.log: file is created if yarn fails, it should not be commited to the repo accidentally
2022-05-17 16:31:05 +02:00
RGanor
3d2ad1abc0 LGTM fix - unused variable 2022-05-15 13:57:13 +00:00
Cloud User
b3271e84d6 Using domain model and added authentication 2022-05-15 12:19:04 +00:00
jbe-dw
6579c9e830
Merge pull request from jbe-dw/revertCorruptedHistoryFix
fix: Insert valid JSON in history.detail and replace single quotes in the database
2022-05-12 21:30:20 +02:00
corubba
564ec6086d Replace pyOpenSSL with cryptography
This is literally the example from the docs [0]. The only thing I
adapted are the parameters for the keys and certificate, so they
stay the same.

Fixes 

[0] https://cryptography.io/en/latest/x509/tutorial/#creating-a-self-signed-certificate
2022-05-07 21:32:19 +02:00
corubba
fec649b747 Header for fixed order column
Semantically and syntactically it is better to have the same number of
`<th>` as `<td>`. Not that anyone will ever see that new header, since
that column is always invisible (except if the user disables javascript).

Plus remove a unmatched closing html element.
2022-05-07 21:14:57 +02:00
corubba
0e2cd063c5 Remove python v2 remnant
As vermin [0] confirms, the codebase has long moved beyond supporting
python v2 (which is not a bad thing). This removes the last explicit py2
piece of code.

And in case anyone wonders, vermin currently reports the minium version
to be v3.6.

[0] https://pypi.org/project/vermin/
2022-05-07 21:14:48 +02:00
corubba
68045cc60c Fix revision in migration filename
This has no functional impact, flask-migrate aka alembic was and will
continue to work as expected. It is just a cosmetic change for
consistency.
2022-05-07 21:14:29 +02:00
jbe-dw
fa9bdcfde0
Merge pull request from jbe-dw/fixAPIDeleteAccount
Fix API Account deletion
2022-05-06 23:35:24 +02:00
Jérôme BECOT
64f7968af9
fix: Use json.dumps instead of str 2022-05-06 17:04:39 +02:00
Jérôme BECOT
06ffee18a0
fix: Provide an Alembic update script to fixe quotes 2022-05-06 17:04:35 +02:00
Vasileios Markopoulos
9e999e7202
Merge pull request from gunet/dependency-fix
cryptography-dependency-addition
2022-04-27 16:35:24 +03:00
KostasMparmparousis
c8d14d91fe cryptography-dependency-addition 2022-04-27 16:11:09 +03:00
jbe-dw
82f03a4de2
Merge pull request from AdvanticGmbH/json_load_error
Json load error
2022-04-26 17:54:08 +02:00
AdvanticGmbH
26c60f175d Remove unnecessary call to str()
* json.dumps() already returns a str
2022-04-26 09:11:05 +02:00
jbe-dw
fc56a168c8
Merge pull request from gunet/ping-no-login-required
Login requirement removal for /ping endpoint
2022-04-25 16:22:21 +02:00
ManosKoukoularis
5040cf5282
Merge pull request from AdvanticGmbH/html_entity_domain_fix
Decode domain record data and comment from HTML entity to text
2022-04-25 12:49:10 +03:00
AdvanticGmbH
44c9aff5db Use json.dumps for every detail in history
This works much better instead of just writing a str to the db and
expect it to be loaded just fine from json.loads
2022-04-25 10:43:46 +02:00
AdvanticGmbH
3df36adbf4 Add more detailed info to the history when a msg and status exists 2022-04-25 10:43:40 +02:00
AdvanticGmbH
191e919626 Allow IDNA in SOA
* Previously having characters like "ü" in the SOA wouldnt allow to push
updates to the domain
* Also use the new method to_idna to support characters like "ß"
2022-04-25 10:19:40 +02:00
AdvanticGmbH
40deb3c145 Create method to encode and decode idna
Previously strings with characters like "ß" would throw and exception
This seems to happen because the lib behind encode().decode('idna')
cant handle characters like this
2022-04-25 10:05:46 +02:00
KostasMparmparousis
4d6c6224b4 Login requirement removal for /ping endpoint 2022-04-20 13:31:23 +03:00
RGanor
4958423cc7
Update api.py 2022-04-18 22:11:31 +03:00
root
f41696c310 WIP - Added health check 2022-04-18 09:01:22 +00:00
Vasileios Markopoulos
e891333971
Merge pull request from LordVeovis/fix/saml
Fix broken SAML login from 9c00e48f
2022-04-13 10:16:58 +03:00
Vasileios Markopoulos
c9c82d4244
Merge pull request from cropalato/master
Fixing AD login if there is a infinity loop in memberOf groups.
2022-04-13 10:15:54 +03:00
Veovis
bd92c5946c
Fix broken SAML login from 9c00e48f 2022-04-12 17:14:54 +02:00
Ricardo Melo
ee0511ff4c
[Fix] AD recursive problem
- Fixing #1011[https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues/1011]
2022-04-11 08:49:38 -04:00
Vasileios Markopoulos
098224eed1
Merge pull request from gunet/log-dnssec-enabling
Log DNSSEC status change for a domain
2022-04-11 15:21:59 +03:00
ManosKoukoularis
9e90dde144
Merge pull request from AdvanticGmbH/domain_xss
Render domain data table fields only as text
2022-04-11 13:05:43 +03:00
Vasileios Markopoulos
0ab2610064
Merge pull request from gunet/update_repo_url
Updated repository URL
2022-04-11 12:26:15 +03:00
vmarkop
9c62208c2e Updated repository URL 2022-04-11 12:21:34 +03:00
jbe-dw
8cf2985335
Merge pull request from mirko/make-onelogin-pkg-optional
routes/index.py: Make package 'onelogin.saml2.utils' optional
2022-04-07 13:37:00 +02:00
jbe-dw
33f1c6ad61
Merge pull request from mirko/add-WWW-Authenticate-header-for-dyndns
dyndns: Respond with HTTP header 'WWW-Authenticate' to unauthed requests
2022-04-07 13:31:03 +02:00
AdvanticGmbH
b534eadf19 Decode domain record data and comment from HTML entity to text 2022-04-04 14:43:02 +02:00
AdvanticGmbH
e596de37f4 Render Name, Type, Status, TTL, Data and Edit as text 2022-04-04 14:16:40 +02:00
AdvanticGmbH
930932d131 Render domain data table fields only as text 2022-04-04 14:06:31 +02:00
jbe-dw
13ff4df9f9
Merge pull request from gunet/auth_type_log_fix
Fixed LDAP Authenticator Type logging
2022-04-03 14:59:48 +02:00
jbe-dw
c6de972ed8
Merge pull request from decryptus/master
[BUG] Fixed delete zone from API
2022-04-03 00:29:47 +02:00
jbe-dw
bff020443f
Merge pull request from jbe-dw/fixBackendContentType
fix: Set Content-Type on backend API calls
2022-04-02 21:36:56 +02:00
Jérôme BECOT
17b4269e1b
fix: Set Content-Type on backend API calls 2022-03-30 23:39:00 +02:00
ManosKoukoularis
be7b657437
Merge pull request from gunet/refresh-on-login
Refresh on csrf token expiration
2022-03-30 10:37:29 +03:00
Vasileios Markopoulos
74efcc7cf7
Merge pull request from gunet/werkzeug-import-fix
Fixed werkzeug dependency
2022-03-29 10:50:13 +03:00
vmarkop
c9d97642b3 Fixed werkzeug dependency 2022-03-29 10:30:19 +03:00
Vasileios Markopoulos
35f2fde0a8
Merge pull request from gunet/jinja-depedency-fix
jinja-dependency-fix
2022-03-27 15:26:22 +03:00
KostasMparmparousis
063d259af8 jinja-dependency-fix 2022-03-27 15:19:35 +03:00
Vasileios Markopoulos
60e58a3895
Merge pull request from gunet/itsdangerous
Pinned compatible itsdangerous version to requirements
2022-03-27 14:50:58 +03:00
vmarkop
5d8e277b3f pinned compatible itsdangerous version 2022-02-28 11:35:24 +02:00
ManosKoukoularis
fcb8287f14
Update login.html 2022-02-25 12:59:23 +02:00
Jérôme BECOT
84a183d913
fix: Disassociate domains from account before deletion 2022-02-24 11:24:19 +01:00
Jérôme BECOT
6ba1254759
feat: Make domain update optional in assoc_account 2022-02-24 11:24:12 +01:00
kkmanos
10603fbb36 fixed csrf expiration for login page 2022-02-17 18:10:06 +02:00
kkmanos
e21f53085d added DNSSEC enabling/disabling to history logs 2022-02-17 17:40:48 +02:00
vmarkop
36cee8cddc Fixed 'LOCAL' Authenticator Type showing for LDAP auth 2022-02-17 17:34:54 +02:00
kkmanos
b9cf7245a5 fixed csrf expiration for login page 2022-02-17 17:02:11 +02:00
Adrien Delle Cave
6982e0107c Typo in routes/api.py 2022-01-20 12:49:37 +01:00
Adrien Delle Cave
e2fe84a7c5 Merge branch 'master' of https://github.com/PowerDNS-Admin/PowerDNS-Admin 2022-01-20 07:58:12 +01:00
dapillc
cd94b5c0ac
Update API.md ()
armless > harmless
2022-01-19 17:49:30 +02:00
Adrien Delle Cave
98bd9634a4 [BUG] Fixed delete zone from API 2022-01-19 13:50:12 +01:00
zoeller-freinet
0b2ad520b7 History table: relocate HTML for modal window ()
- Store HTML for modal window inside an invisible <div> element instead
  of inside the <button> element's value attribute
- Mark history.detailed_msg as safe as it is already manually run
  through the template engine beforehand and would be broken if escaped
  a second time
2022-01-01 21:20:01 +01:00
Christian
302e793665
Add button for admin page in single Domain view ()
* Added button for admin page in domain overview
2021-12-31 00:55:59 +01:00
RGanor
328780e2d4 Revert "Merge branch 'master' into master"
This reverts commit ca4c145a1809b3fee8510b49fb517a844c3524eb, reversing
changes made to 7808febad8fb0a56cdecce2d7cdb9a92ec30ff06.
2021-12-25 16:17:54 +02:00
RGanor
ca4c145a18
Merge branch 'master' into master 2021-12-25 16:10:18 +02:00
zoeller-freinet
7808febad8 login.html: don't suggest previous OTP tokens
This change has been tested to work with:
- Chromium 96.0.4664.93
- Firefox 95.0
- Edge 96.0.1054.57
2021-12-17 12:48:11 +01:00
dependabot[bot]
9ef0f2b8d6 Bump python-ldap from 3.3.1 to 3.4.0
Bumps [python-ldap](https://github.com/python-ldap/python-ldap) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/python-ldap/python-ldap/releases)
- [Commits](https://github.com/python-ldap/python-ldap/compare/python-ldap-3.3.1...python-ldap-3.4.0)

---
updated-dependencies:
- dependency-name: python-ldap
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 12:08:19 +01:00
Vasileios Markopoulos
94a923a965
Add 'otp_force' basic setting ()
If the 'otp_force' and 'otp_field_enabled' basic settings are both enabled, automatically enable 2FA for the user after login or signup, if needed, by setting a new OTP secret. Redirect the user to a welcome page for scanning the QR code.

Also show the secret key in ASCII form on the user profile page for easier copying into other applications.
2021-12-17 11:41:51 +01:00
Kateřina Churanová
eb70f6a066
fix: making the key name in the config database unique 2021-12-12 20:32:14 +01:00
Jérôme BECOT
0da9b2185e fix: Error in the swagger AccountSummary definition 2021-12-08 23:11:13 +01:00
zoeller-freinet
07f0d215a7 PDNS-API: factor in 'dnssec_admins_only' basic setting ()
`GET cryptokeys/{cryptokey_id}` returns the private key, which justifies
that the setting is honored in this case.
2021-12-06 22:38:16 +01:00
Khanh Ngo
fc8367535b
chore: remove funding and sponsor badges () 2021-12-08 17:44:44 +01:00
Jérôme BECOT
d2f35a4059 fix: Check user zone create/delete permission
Co-authored-by: zoeller-freinet <86965592+zoeller-freinet@users.noreply.github.com>
2021-12-05 14:16:45 +01:00
zoeller-freinet
737e1fb93b routes/admin.py: DetailedHistory: backward-compatibility
See https://github.com/ngoduykhanh/PowerDNS-Admin/pull/1066
2021-12-04 17:38:48 +01:00
zoeller-freinet
f0008ce401 routes/admin.py: refactor DetailedHistory
- Run HTML through the template engine, preventing XSS from various
  vectors
- Fix uncaught exception when a history entry about domain template
  deletion is processed
- Adapt indentation to 4 space characters per level
2021-12-04 16:09:53 +01:00
Dominic Zöller
6f12b783a8 models.user: get_accounts(): order by name
The order of account names returned by User.get_accounts() affects the
order account names are displyed in on /domain/add if the current user
neither has the Administrator role nor the Operator role and the
`allow_user_create_domain` setting is enabled at the same time.

If the current user does have the Administrator or Operator role,
routes.domain.add() already returns accounts ordered by name, so this
change makes it consistent.
2021-12-04 16:09:15 +01:00
Dominic Zöller
51a7f636b0 Use secrets module for generating new API keys and passwords
The implementation of `random.choice()` uses the Mersenne Twister, the
output of which is predictable by observing previous output, and is as
such unsuitable for security-sensitive applications. A cryptographically
secure pseudorandom number generator - which the `secrets` module relies
on - should be used instead in those instances.
2021-12-04 16:08:07 +01:00
ManosKoukoularis
9f46188c7e
Quotes fix ()
* minor fix in history
* made key access more generic
2021-12-03 20:14:14 +02:00
root
caa48b7fe5 Merge branch 'quotes-fix'
Conflicts:
	powerdnsadmin/routes/admin.py
2021-12-03 14:17:39 +00:00
root
591055d4aa Merge branch 'master' of https://github.com/ngoduykhanh/PowerDNS-Admin 2021-12-03 14:12:32 +00:00
root
940551e99e feat: Associate an API Key with accounts () 2021-12-03 14:12:11 +00:00
jbe-dw
f45ff2ce03
feat: Associate an API Key with accounts () 2021-12-03 15:35:15 +02:00
ManosKoukoularis
6c1dfd2408
Datepicker replace ()
* replaced jquery-ui-datepicker with bootstrap-datepicker

* removed obsolete static files
2021-12-02 11:59:36 +01:00
Dominic Zöller
701a442d12 default config: add exemplary URL encoding step for SQLA DB URL params
SQLAlchemy database URLs follow RFC-1738, so parameters like username
and password need to be encoded accordingly.

https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls
2021-11-30 22:29:00 +01:00
Nick Bouwhuis
a3b70a8f47
Add Keycloak documentation () 2021-11-30 12:26:58 +02:00
ManosKoukoularis
1332c8d29d
History Tab Overhaul & Domain Record Modifications Changelog ()
Co-authored-by: Konstantinos Kouris <85997752+konkourgr@users.noreply.github.com>
Co-authored-by: vmarkop <billy.mark.b.m.10@gmail.com>
Co-authored-by: KostasMparmparousis <mparmparousis.kostas@gmail.com>
Co-authored-by: dimpapac <demispapa@gmail.com>
2021-11-30 11:02:37 +02:00
benshalev849
b3f9b4a2b0
OIDC list accounts ()
Added the function to use lists instead of a single string in account autoprovision.
2021-11-19 17:53:17 +02:00
zoeller-freinet
bfaf5655ae
Clarify salt re-use for API keys () 2021-11-09 22:09:15 +02:00
Khanh Ngo
dd04a837bb
Update docker image build script 2021-11-06 15:44:20 +01:00
Khanh Ngo
5bb1a7ee29
Update docker image build script 2021-11-06 15:37:13 +01:00
Khanh Ngo
c85a5dac24
Update docker image build script 2021-11-06 15:25:20 +01:00
benshalev849
3081036c2c
Env oauth url ()
Overriding settings in DB using environment variable in docker
2021-11-05 18:22:38 +02:00
Daniel Molkentin
c7b4aa3434
fix: actually store OIDC logout URL () 2021-11-05 17:28:21 +02:00
Vitali Quiering
e7d5a3aba0
feat: enable_api_rr_history setting ()
* feat: introduce enable_api_rr_history setting to disable api record
changes
2021-11-05 17:26:38 +02:00
zoeller-freinet
20b866a784
strip() whitespace from new local user master data ()
When creating a new local user, there is a chance that, due to a copy &
paste or typing error, whitespace will be introduced at the start or end
of the username. This can lead to issues when trying to log in using the
affected username, as such a condition can easily be overlooked - no
user will be found in the database if entering the username without the
aforementioned whitespace. This commit therefore strip()s the username
string within routes/{admin,index}.py.

The firstname, lastname and email strings within
routes/{admin,index,user}.py are also strip()ped on this occasion.
2021-11-05 17:04:35 +02:00
Khanh Ngo
1662a812ba Update CI
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-31 14:34:35 +01:00
Khanh Ngo
c49df09ac8 Update CI
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-31 14:31:14 +01:00
Khanh Ngo
924537b468 Update CI
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-31 14:25:22 +01:00
Khanh Ngo
4f8a547d47 Update CI
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-31 14:23:49 +01:00
Khanh Ngo
ee9f568a8d
Update README.md 2021-10-31 13:16:42 +01:00
Khanh Ngo
d7ae34ed53 Update CI
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-31 13:08:22 +01:00
jbe-dw
1c9ca60508
fix: jsmin 2.2.2 no longer available. Use 3.0.0 () 2021-10-30 21:30:53 +02:00
zoeller-freinet
0e655c1357
user_profile tpl: set email input type attr to "email" ()
It is then consistent with the email address input elements declared in
admin_edit_account.html, admin_edit_user.html and register.html.
2021-10-30 21:30:26 +02:00
steschuser
ba2423d6f5
fix if condition in pretty_domain_name () 2021-10-30 21:29:55 +02:00
Andreas Dirnberger
46e51f16cb
Remove unnecessary build step ()
The builder image does not need to cleanup itself, 
the whole purpose of it is to be dropped after the final artifacts are copied out.
2021-10-30 21:29:23 +02:00
jbe-dw
b8ee91ab9a
fix: Accounts API is broken () 2021-10-30 21:28:36 +02:00
RGanor
c246775ffe
bg_domain button for operators and higher () 2021-10-30 21:26:46 +02:00
Hidde
f96103db79
Replace [ZONE] placeholder with domain_name () 2021-10-30 21:24:16 +02:00
steschuser
bf83662108
allow users to remove domain () 2021-10-30 21:21:45 +02:00
steschuser
1f34dbf810
fix for api key () 2021-10-30 21:19:49 +02:00
Khanh Ngo
b7197948c1 Reslove conflicts
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-30 21:19:01 +02:00
Khanh Ngo
ddf2d4788b Reslove conflicts
Signed-off-by: Khanh Ngo <khanh.ngo@taxfix.de>
2021-10-30 21:15:04 +02:00
steschuser
1ec6b76f89
Remove otp field () 2021-10-30 21:09:04 +02:00
Mark Zealey
4ce1b71c57
Fix when no records returned by API ()
For some reason when some programs delete a record we get an entry returned with records: []
2021-10-30 21:07:42 +02:00
steschuser
79457bdc85
Bug domain parse () 2021-10-30 21:06:44 +02:00
Mirko Vogt
282c630eb8 dyndns: Respond with HTTP header 'WWW-Authenticate' to unauthed requests
The common procedure for HTTP Basic Auth is that a client does /not/
immediately send out credentials via an 'Authorization'-header, but to
wait until the server tells the client to do so - which the server
indicates via the 'WWW-Authenticate'-header.

PowerDNS-Admin (and flask in general), though, abort the whole
communication if no Authorization header was found in the initial
request - resulting in '200 "badauth"'.

While this might work for /some/ HTTP clients - which right away add an
Authorization header crafted from provided credentials (via args or
extracted from given URL), this is /not/ standard and /not/ common.

Hence add the 'WWW-Authenticate'-header for every unauthenticated call
checking for dyndns authorisation.

Note, though, this changes the status code from 200 to 401 in this case,
which - given the explanation why 200 was chosen in the first place -
might cause side effects.
2021-10-20 15:12:17 +00:00
RoeiGanor
10dc2b0273 bg_domain button for operators and higher 2021-08-13 20:03:06 +03:00
steschuser
993e02b635
limit user to only create domains for the accounts he belongs to () 2021-08-05 19:42:58 +02:00
steschuser
07c71fb0bf
setting account_user_ids to empty list on GET /account/edit () 2021-08-05 19:41:28 +02:00
steschuser
c4a9498898
respect_bg_domain_updates in routes/api () 2021-08-05 19:39:26 +02:00
Kostas Mparmparousis
6e04d0419b
Provision PDA user privileges based On LDAP Attributes () 2021-08-05 19:37:48 +02:00
Mirko Vogt
9c00e48f0f routes/index.py: Make package 'onelogin.saml2.utils' optional
The onelogin package is not part of all saml packages for whatever
reason (e.g. Debian) and not easily installable from pypi (requires
CC toolchain).

As the onelogin functionality is already guarded by whether
SAML_ENABLED is set in other places (services/saml.py), also do so
in routes/index.py.
2021-07-23 06:56:09 +00:00
Carsten Rosenberg
d6e64dce8e fix some jinja typos 2021-06-04 15:24:49 +02:00
Steffen Schwebel
b069cea8d1 add css to base as well 2021-06-02 09:44:15 +02:00
Steffen Schwebel
fd933f8dbc remove unrelated files and changes as best as possible 2021-06-02 09:41:08 +02:00
Steffen Schwebel
0505b934a1 remove unrelated files and changes as best as possible 2021-06-02 09:39:39 +02:00
Steffen Schwebel
083a023e57 fix include 2021-06-01 16:41:26 +02:00
Steffen Schwebel
054e0e6eba add rule for 'custom_css' setting 2021-06-01 16:24:07 +02:00
Steffen Schwebel
c13dd2d835 add 'custom_css' setting to model; check for 'custom_css' in template; create custom css dir in dockerfile 2021-06-01 16:15:31 +02:00
steschuser
567f66fbde
Merge pull request from uvensys/remove_otp_field
Remove otp field
2021-06-01 15:28:41 +02:00
steschuser
ff5270fbad
Merge pull request from uvensys/add_background_jobs_to_docker
add environment to cron
2021-06-01 15:21:22 +02:00
Steffen Schwebel
92bad7b11c add environment to cron 2021-06-01 14:02:01 +02:00
Steffen Schwebel
43a6e46e66 add setting to hide otp_token field on login page 2021-05-27 22:51:07 +02:00
Steffen Schwebel
ee72fdf9c2 Merge branch 'master' of github.com:uvensys/PowerDNS-Admin into remove_otp_field 2021-05-27 21:56:01 +02:00
steschuser
8f73512d2e
Merge pull request from uvensys/add_background_jobs_to_docker
Add background jobs to docker
2021-05-27 21:33:27 +02:00
Steffen Schwebel
700fa0d9ce add new dockerfile with s6 overlay and multiple proccesses to have background jobs updating accounts and zones 2021-05-27 21:32:00 +02:00
Steffen Schwebel
00dc23f21b added new Dockerfile, to support more than one process running in docker, using s6 overlay 2021-05-27 16:39:51 +02:00
Steffen Schwebel
36fdb3733f Merge remote-tracking branch 'origin/master' into remove_otp_field 2021-05-25 15:30:32 +02:00
steschuser
ce60ca0b9d
Merge pull request from uvensys/bug_domain_parse
Bug domain parse
2021-05-25 12:53:57 +02:00
Steffen Schwebel
b197491a86 remove traceback 2021-05-25 12:44:07 +02:00
Steffen Schwebel
d23a57da50 handle decode error, output warning 2021-05-25 12:35:53 +02:00
Steffen Schwebel
4180882fb7 show traceback 2021-05-21 15:10:17 +02:00
root
bbbcf271fe remove otp token from login page, depending on Setting 2021-05-20 15:21:56 +02:00
jyoung15
32983635c6
Delete blank comments. Fix for ngoduykhanh/PowerDNS-Admin#919 () 2021-05-07 23:43:44 +02:00
Jay Linski
f3a98eb692
Emphasize importance of using a custom SECRET_KEY ()
This project provides a default SECRET_KEY for signing session-cookies:
https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY

By using the default SECRET_KEY, everyone will be able to create valid session-cookies.
So users should be aware that it is very important to set a custom SECRET_KEY.
2021-05-07 23:40:54 +02:00
Ian Bobbitt
39cddd3b34
SAML improvements for Docker ()
* Fix typo in managing user account membership with SAML assertion

* Support more config options from Docker env.

* Improve support for SAML key and cert from Docker secrets

Co-authored-by: Ian Bobbitt <ibobbitt@globalnoc.iu.edu>
2021-05-07 23:36:55 +02:00
jodygilbert
b66b37ecfd
delete history records when a domain is deleted ()
Co-authored-by: Jody <jody.gilbert@edftrading.com>
2021-05-07 22:55:45 +02:00
dependabot[bot]
5f10f739ea
Bump pyyaml from 5.3.1 to 5.4 () 2021-03-27 19:33:49 +01:00
jodygilbert
98db953820
Allow user role to view history () 2021-03-27 19:33:11 +01:00
dependabot[bot]
44c4531f02
Bump elliptic from 6.5.3 to 6.5.4 ()
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-16 19:41:46 +01:00
jbe-dw
86700f8fd7
upd: improve user api () 2021-03-16 19:39:53 +01:00
R. Daneel Olivaw
46993e08c0
Add punycode (IDN) support () 2021-03-16 19:37:05 +01:00
jodygilbert
4c19f95928
Improve account creation/permission handling based on Azure oAuth group membership () 2021-01-31 11:31:56 +01:00
jbe-dw
3a4efebf95
enh: display b64 encoded apikey on creation through the API () 2021-01-24 09:43:51 +01:00
jodygilbert
7f86730909
allow-server-side-sessions () 2021-01-24 09:09:53 +01:00
jbe-dw
8f6a800836
fix: account API output^ () 2021-01-24 09:08:32 +01:00
jbe-dw
3cd98251b3
fix: API (apikeys) behaviour does not match swagger definition () 2021-01-24 09:06:51 +01:00
jbe-dw
54b257768f
feat: Implement apikeys/<id> endpoint from swagger spec. () 2021-01-16 20:49:41 +01:00
jbe-dw
718b41e3d1
feat: limit zone list for users on servers endpoint () 2021-01-16 20:45:02 +01:00
jbe-dw
dd0a5f6326
feat: Allow sync domain with basic auth () 2021-01-16 20:37:11 +01:00
jbe-dw
c3d438842f
fix: user jsonify to set response headers to json () 2021-01-16 20:29:40 +01:00
jbe-dw
33e7ffb747
fix: Follow PDNS Api return format () 2021-01-07 23:26:48 +01:00
jbe-dw
2c18e5c88f
fix: User role was not assigned upon creation () 2021-01-07 23:07:20 +01:00
mrsrvman
2917c47fd1
Update entrypoint.sh ()
Fix typo
2020-12-23 17:23:32 +01:00
WhatshallIbreaktoday
c6e0293177
Tweaks to allow user apikey usage with powerdns terraform provider () 2020-12-07 22:06:37 +01:00
Attila DEBRECZENI
942482b706
set chown to /app docker workdir () 2020-12-07 19:46:08 +01:00
Khanh Ngo
4d1db72699
Merge pull request from andrewnimmo/patch-1
Avoid Safari telephone number detection
2020-10-14 17:49:51 +02:00
Andrew Nimmo
680e4cf431
Avoid Safari telephone number detection
Using PowerDNS-Admin on an iPad with Safari can cause incorrect identification of some record data as a telephone number. When submitted, the record with the incorrectly identified data causes an error because of the additional markup present on the submitted data. This was noted in particular with the SOA record. 

The proposed change is to add the Safari meta tag to disable format detection:
https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html#//apple_ref/doc/uid/TP40008193-SW5
2020-10-14 17:21:59 +02:00
191 changed files with 19058 additions and 8721 deletions
.github
.gitignore.travis.ymlLICENSEREADME.mdSECURITY.mdVERSION
configs
deploy
docker-compose-test.ymldocker-compose.yml
docker-test
docker
docs
migrations
package.json
powerdnsadmin

2
.github/FUNDING.yml vendored

@ -1 +1 @@
github: [ngoduykhanh]
github: [AzorianSolutions]

7
.github/ISSUE_TEMPLATE/config.yml vendored Normal file

@ -0,0 +1,7 @@
---
# Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: false
contact_links:
- name: 📖 Project Update - PLEASE READ!
url: https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions/1708
about: "Important information about the future of this project"

14
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file

@ -0,0 +1,14 @@
<!--
Thank you for your interest in contributing to the PowerDNS Admin project! Please note that our contribution
policy requires that a feature request or bug report be approved and assigned prior to opening a pull request.
This helps avoid wasted time and effort on a proposed change that we might want to or be able to accept.
IF YOUR PULL REQUEST DOES NOT REFERENCE AN ISSUE WHICH HAS BEEN ASSIGNED TO YOU, IT WILL BE CLOSED AUTOMATICALLY!
Please specify your assigned issue number on the line below.
-->
### Fixes: #1234
<!--
Please include a summary of the proposed changes below.
-->

15
.github/SUPPORT.md vendored Normal file

@ -0,0 +1,15 @@
# PowerDNS Admin
## Project Support
**Looking for help?** PDA has a somewhat active community of fellow users that may be able to provide assistance.
Just [start a discussion](https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions/new) right here on GitHub!
Looking to chat with someone? Join our [Discord Server](https://discord.powerdnsadmin.org).
Some general tips for engaging here on GitHub:
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.

23
.github/dependabot.yml vendored Normal file

@ -0,0 +1,23 @@
---
version: 2
updates:
- package-ecosystem: npm
target-branch: dev
directory: /
schedule:
interval: daily
ignore:
- dependency-name: "*"
update-types: [ "version-update:semver-major" ]
labels:
- 'feature / dependency'
- package-ecosystem: pip
target-branch: dev
directory: /
schedule:
interval: daily
ignore:
- dependency-name: "*"
update-types: [ "version-update:semver-major" ]
labels:
- 'feature / dependency'

98
.github/labels.yml vendored Normal file

@ -0,0 +1,98 @@
---
labels:
- name: bug / broken-feature
description: Existing feature malfunctioning or broken
color: 'd73a4a'
- name: bug / security-vulnerability
description: Security vulnerability identified with the application
color: 'd73a4a'
- name: docs / discussion
description: Documentation change proposals
color: '0075ca'
- name: docs / request
description: Documentation change request
color: '0075ca'
- name: feature / dependency
description: Existing feature dependency
color: '008672'
- name: feature / discussion
description: New or existing feature discussion
color: '008672'
- name: feature / request
description: New feature or enhancement request
color: '008672'
- name: feature / update
description: Existing feature modification
color: '008672'
- name: help / deployment
description: Questions regarding application deployment
color: 'd876e3'
- name: help / features
description: Questions regarding the use of application features
color: 'd876e3'
- name: help / other
description: General questions not specific to application deployment or features
color: 'd876e3'
- name: mod / accepted
description: This request has been accepted
color: 'e5ef23'
- name: mod / announcement
description: This is an admin announcement
color: 'e5ef23'
- name: mod / change-request
description: Used by internal developers to indicate a change-request.
color: 'e5ef23'
- name: mod / changes-requested
description: Changes have been requested before proceeding
color: 'e5ef23'
- name: mod / duplicate
description: This issue or pull request already exists
color: 'e5ef23'
- name: mod / good-first-issue
description: Good for newcomers
color: 'e5ef23'
- name: mod / help-wanted
description: Extra attention is needed
color: 'e5ef23'
- name: mod / invalid
description: This doesn't seem right
color: 'e5ef23'
- name: mod / rejected
description: This request has been rejected
color: 'e5ef23'
- name: mod / reviewed
description: This request has been reviewed
color: 'e5ef23'
- name: mod / reviewing
description: This request is being reviewed
color: 'e5ef23'
- name: mod / stale
description: This request has gone stale
color: 'e5ef23'
- name: mod / tested
description: This has been tested
color: 'e5ef23'
- name: mod / testing
description: This is being tested
color: 'e5ef23'
- name: mod / wont-fix
description: This will not be worked on
color: 'e5ef23'
- name: skill / database
description: Requires a database skill-set
color: '5319E7'
- name: skill / docker
description: Requires a Docker skill-set
color: '5319E7'
- name: skill / documentation
description: Requires a documentation skill-set
color: '5319E7'
- name: skill / html
description: Requires a HTML skill-set
color: '5319E7'
- name: skill / javascript
description: Requires a JavaScript skill-set
color: '5319E7'
- name: skill / python
description: Requires a Python skill-set
color: '5319E7'

19
.github/stale.yml vendored

@ -1,19 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- enhancement
- feature request
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: true

81
.github/workflows/build-and-publish.yml vendored Normal file

@ -0,0 +1,81 @@
---
name: 'Docker Image'
on:
workflow_dispatch:
push:
branches:
- 'dev'
- 'master'
tags:
- 'v*.*.*'
paths-ignore:
- .github/**
- deploy/**
- docker-test/**
- docs/**
- .dockerignore
- .gitattributes
- .gitignore
- .lgtm.yml
- .whitesource
- .yarnrc
- docker-compose.yml
- docker-compose-test.yml
- LICENSE
- README.md
- SECURITY.md
jobs:
build-and-push-docker-image:
name: Build Docker Image
runs-on: ubuntu-latest
steps:
- name: Repository Checkout
uses: actions/checkout@v2
- name: Docker Image Metadata
id: meta
uses: docker/metadata-action@v3
with:
images: |
powerdnsadmin/pda-legacy
tags: |
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: QEMU Setup
uses: docker/setup-qemu-action@v2
- name: Docker Buildx Setup
id: buildx
uses: docker/setup-buildx-action@v1
- name: Docker Hub Authentication
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME_V2 }}
password: ${{ secrets.DOCKERHUB_TOKEN_V2 }}
- name: Docker Image Build
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64
context: ./
file: ./docker/Dockerfile
push: true
tags: powerdnsadmin/pda-legacy:${{ github.ref_name }}
- name: Docker Image Release Tagging
uses: docker/build-push-action@v4
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with:
platforms: linux/amd64,linux/arm64
context: ./
file: ./docker/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

134
.github/workflows/codeql-analysis.yml vendored Normal file

@ -0,0 +1,134 @@
---
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
workflow_dispatch:
push:
branches:
- 'dev'
- 'main'
- 'master'
- 'dependabot/**'
- 'feature/**'
- 'issue/**'
paths-ignore:
- .github/**
- deploy/**
- docker/**
- docker-test/**
- docs/**
- powerdnsadmin/static/assets/**
- powerdnsadmin/static/custom/css/**
- powerdnsadmin/static/img/**
- powerdnsadmin/swagger-spec.yaml
- .dockerignore
- .gitattributes
- .gitignore
- .lgtm.yml
- .whitesource
- .yarnrc
- docker-compose.yml
- docker-compose-test.yml
- LICENSE
- package.json
- README.md
- requirements.txt
- SECURITY.md
- yarn.lock
pull_request:
# The branches below must be a subset of the branches above
branches:
- 'dev'
- 'main'
- 'master'
- 'dependabot/**'
- 'feature/**'
- 'issue/**'
paths-ignore:
- .github/**
- deploy/**
- docker/**
- docker-test/**
- docs/**
- powerdnsadmin/static/assets/**
- powerdnsadmin/static/custom/css/**
- powerdnsadmin/static/img/**
- powerdnsadmin/swagger-spec.yaml
- .dockerignore
- .gitattributes
- .gitignore
- .lgtm.yml
- .whitesource
- .yarnrc
- docker-compose.yml
- docker-compose-test.yml
- LICENSE
- package.json
- README.md
- requirements.txt
- SECURITY.md
- yarn.lock
schedule:
- cron: '45 2 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

24
.github/workflows/lock.yml vendored Normal file

@ -0,0 +1,24 @@
---
# lock-threads (https://github.com/marketplace/actions/lock-threads)
name: 'Lock threads'
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
with:
issue-inactive-days: 90
pr-inactive-days: 30
issue-lock-reason: 'resolved'
exclude-any-issue-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'
exclude-any-pr-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'

92
.github/workflows/mega-linter.yml vendored Normal file

@ -0,0 +1,92 @@
---
# MegaLinter GitHub Action configuration file
# More info at https://megalinter.io
name: MegaLinter
on:
workflow_dispatch:
push:
branches-ignore:
- "*"
- "dev"
- "main"
- "master"
- "dependabot/**"
- "feature/**"
- "issues/**"
- "release/**"
env: # Comment env block if you do not want to apply fixes
# Apply linter fixes configuration
APPLY_FIXES: all # When active, APPLY_FIXES must also be defined as environment variable (in github/workflows/mega-linter.yml or other CI tool)
APPLY_FIXES_EVENT: all # Decide which event triggers application of fixes in a commit or a PR (pull_request, push, all)
APPLY_FIXES_MODE: pull_request # If APPLY_FIXES is used, defines if the fixes are directly committed (commit) or posted in a PR (pull_request)
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
build:
name: MegaLinter
runs-on: ubuntu-latest
steps:
# Git Checkout
- name: Checkout Code
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
# MegaLinter
- name: MegaLinter
id: ml
# You can override MegaLinter flavor used to have faster performances
# More info at https://megalinter.io/flavors/
uses: oxsecurity/megalinter@v6
env:
# All available variables are described in documentation
# https://megalinter.io/configuration/
VALIDATE_ALL_CODEBASE: true # Validates all source when push on main, else just the git diff with main. Override with true if you always want to lint all sources
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PAT: ${{ secrets.PAT }}
# ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY
# DISABLE: COPYPASTE,SPELL # Uncomment to disable copy-paste and spell checks
# Upload MegaLinter artifacts
- name: Archive production artifacts
if: ${{ success() }} || ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: MegaLinter reports
path: |
megalinter-reports
mega-linter.log
# Create pull request if applicable (for now works only on PR from same repository, not from forks)
- name: Create PR with applied fixes
id: cpr
if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
commit-message: "[MegaLinter] Apply linters automatic fixes"
title: "[MegaLinter] Apply linters automatic fixes"
labels: bot
- name: Create PR output
if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
# Push new commit if applicable (for now works only on PR from same repository, not from forks)
- name: Prepare commit
if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
run: sudo chown -Rc $UID .git/
- name: Commit and push applied linter fixes
if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
uses: stefanzweifel/git-auto-commit-action@v4
with:
branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}
commit_message: "[MegaLinter] Apply linters fixes"

46
.github/workflows/stale.yml vendored Normal file

@ -0,0 +1,46 @@
# close-stale-issues (https://github.com/marketplace/actions/close-stale-issues)
name: 'Close Stale Threads'
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v6
with:
close-issue-message: >
This issue has been automatically closed due to lack of activity. In an
effort to reduce noise, please do not comment any further. Note that the
core maintainers may elect to reopen this issue at a later date if deemed
necessary.
close-pr-message: >
This PR has been automatically closed due to lack of activity.
days-before-stale: 90
days-before-close: 30
exempt-issue-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'
operations-per-run: 100
remove-stale-when-updated: false
stale-issue-label: 'mod / stale'
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. PDA
is governed by a small group of core maintainers which means not all opened
issues may receive direct feedback. **Do not** attempt to circumvent this
process by "bumping" the issue; doing so will result in its immediate closure
and you may be barred from participating in any future discussions. Please see our
[Contribution Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CONTRIBUTING.md).
stale-pr-label: 'mod / stale'
stale-pr-message: >
This PR has been automatically marked as stale because it has not had
recent activity. It will be closed automatically if no further action is
taken. Please see our
[Contribution Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CONTRIBUTING.md).

4
.gitignore vendored

@ -1,3 +1,5 @@
flask_session
# gedit
*~
@ -38,5 +40,7 @@ node_modules
powerdnsadmin/static/generated
.webassets-cache
.venv*
venv*
.pytest_cache
.DS_Store
yarn-error.log

@ -1,5 +0,0 @@
language: minimal
script:
- docker-compose -f docker-compose-test.yml up --exit-code-from powerdns-admin --abort-on-container-exit
services:
- docker

@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2016 Khanh Ngo - ngokhanhit[at]gmail.com
Copyright (c) 2022 Azorian Solutions - legal[at]azorian.solutions
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,47 +1,67 @@
# PowerDNS-Admin
A PowerDNS web interface with advanced features.
[![Build Status](https://travis-ci.org/ngoduykhanh/PowerDNS-Admin.svg?branch=master)](https://travis-ci.org/ngoduykhanh/PowerDNS-Admin)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:python)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:javascript)
[![CodeQL](https://github.com/PowerDNS-Admin/PowerDNS-Admin/actions/workflows/codeql-analysis.yml/badge.svg?branch=master)](https://github.com/PowerDNS-Admin/PowerDNS-Admin/actions/workflows/codeql-analysis.yml)
[![Docker Image](https://github.com/PowerDNS-Admin/PowerDNS-Admin/actions/workflows/build-and-publish.yml/badge.svg?branch=master)](https://github.com/PowerDNS-Admin/PowerDNS-Admin/actions/workflows/build-and-publish.yml)
#### Features:
- Multiple domain management
- Domain template
- User management
- User access management based on domain
- User activity logging
- Support Local DB / SAML / LDAP / Active Directory user authentication
- Support Google / Github / Azure / OpenID OAuth
- Support Two-factor authentication (TOTP)
- Dashboard and pdns service statistics
- Provides forward and reverse zone management
- Provides zone templating features
- Provides user management with role based access control
- Provides zone specific access control
- Provides activity logging
- Authentication:
- Local User Support
- SAML Support
- LDAP Support: OpenLDAP / Active Directory
- OAuth Support: Google / GitHub / Azure / OpenID
- Two-factor authentication support (TOTP)
- PDNS Service Configuration & Statistics Monitoring
- DynDNS 2 protocol support
- Edit IPv6 PTRs using IPv6 addresses directly (no more editing of literal addresses!)
- Limited API for manipulating zones and records
- Easy IPv6 PTR record editing
- Provides an API for zone and record management among other features
- Provides full IDN/Punycode support
## [Project Update - PLEASE READ!!!](https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions/1708)
## Running PowerDNS-Admin
There are several ways to run PowerDNS-Admin. The easiest way is to use Docker.
If you are looking to install and run PowerDNS-Admin directly onto your system check out the [Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki#installation-guides) for ways to do that.
There are several ways to run PowerDNS-Admin. The quickest way is to use Docker.
If you are looking to install and run PowerDNS-Admin directly onto your system, check out
the [wiki](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/wiki/) for ways to do that.
### Docker
This are two options to run PowerDNS-Admin using Docker.
To get started as quickly as possible try option 1. If you want to make modifications to the configuration option 2 may be cleaner.
Here are two options to run PowerDNS-Admin using Docker.
To get started as quickly as possible, try option 1. If you want to make modifications to the configuration option 2 may
be cleaner.
#### Option 1: From Docker Hub
The easiest is to just run the latest Docker image from Docker Hub:
To run the application using the latest stable release on Docker Hub, run the following command:
```
$ docker run -d \
-e SECRET_KEY='a-very-secret-key' \
-v pda-data:/data \
-p 9191:80 \
ngoduykhanh/powerdns-admin:latest
powerdnsadmin/pda-legacy:latest
```
This creates a volume called `pda-data` to persist the SQLite database with the configuration.
This creates a volume named `pda-data` to persist the default SQLite database with app configuration.
#### Option 2: Using docker-compose
1. Update the configuration
1. Update the configuration
Edit the `docker-compose.yml` file to update the database connection string in `SQLALCHEMY_DATABASE_URI`.
Other environment variables are mentioned in the [legal_envvars](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/configs/docker_config.py#L5-L46).
To use the Docker secrets feature it is possible to append `_FILE` to the environment variables and point to a file with the values stored in it.
Other environment variables are mentioned in
the [AppSettings.defaults](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/powerdnsadmin/lib/settings.py) dictionary.
To use a Docker-style secrets convention, one may append `_FILE` to the environment variables with a path to a file
containing the intended value of the variable (e.g. `SQLALCHEMY_DATABASE_URI_FILE=/run/secrets/db_uri`).
Make sure to set the environment variable `SECRET_KEY` to a long, random
string (https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY)
2. Start docker container
```
@ -51,12 +71,28 @@ This creates a volume called `pda-data` to persist the SQLite database with the
You can then access PowerDNS-Admin by pointing your browser to http://localhost:9191.
## Screenshots
![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png)
## LICENSE
MIT. See [LICENSE](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/LICENSE)
![dashboard](docs/screenshots/dashboard.png)
## Support
If you like the project and want to support it, you can *buy me a coffee*
<a href="https://www.buymeacoffee.com/khanhngo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>
**Looking for help?** Try taking a look at the project's
[Support Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/.github/SUPPORT.md) or joining
our [Discord Server](https://discord.powerdnsadmin.org).
## Security Policy
Please see our [Security Policy](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/SECURITY.md).
## Contributing
Please see our [Contribution Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CONTRIBUTING.md).
## Code of Conduct
Please see our [Code of Conduct Policy](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CODE_OF_CONDUCT.md).
## License
This project is released under the MIT license. For additional
information, [see the full license](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/LICENSE).

31
SECURITY.md Normal file

@ -0,0 +1,31 @@
# Security Policy
## No Warranty
Per the terms of the MIT license, PDA is offered "as is" and without any guarantee or warranty pertaining to its operation. While every reasonable effort is made by its maintainers to ensure the product remains free of security vulnerabilities, users are ultimately responsible for conducting their own evaluations of each software release.
## Recommendations
Administrators are encouraged to adhere to industry best practices concerning the secure operation of software, such as:
* Do not expose your PDA installation to the public Internet
* Do not permit multiple users to share an account
* Enforce minimum password complexity requirements for local accounts
* Prohibit access to your database from clients other than the PDA application
* Keep your deployment updated to the most recent stable release
## Reporting a Suspected Vulnerability
If you believe you've uncovered a security vulnerability and wish to report it confidentially, you may do so via email. Please note that any reported vulnerabilities **MUST** meet all the following conditions:
* Affects the most recent stable release of PDA, or a current beta release
* Affects a PDA instance installed and configured per the official documentation
* Is reproducible following a prescribed set of instructions
Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous.
If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/PowerDNS-Admin/PowerDNS-Admin/security/advisories/new) on GitHub, or email a brief description of the suspected bug and instructions for reproduction to **admin@powerdnsadmin.org**.
### Bug Bounties
As PDA is provided as free open source software, we do not offer any monetary compensation for vulnerability or bug reports, however your contributions are greatly appreciated.

1
VERSION Normal file

@ -0,0 +1 @@
0.4.2

@ -1,12 +1,13 @@
import os
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)))
#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
OFFLINE_MODE = False
SERVER_EXTERNAL_SSL = os.getenv('SERVER_EXTERNAL_SSL', None)
### DATABASE CONFIG
SQLA_DB_USER = 'pda'
@ -15,8 +16,34 @@ SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'pda'
SQLALCHEMY_TRACK_MODIFICATIONS = True
#CAPTCHA Config
CAPTCHA_ENABLE = True
CAPTCHA_LENGTH = 6
CAPTCHA_WIDTH = 160
CAPTCHA_HEIGHT = 60
CAPTCHA_SESSION_KEY = 'captcha_image'
#Server side sessions tracking
#Set to TRUE for CAPTCHA, or enable another stateful session tracking system
SESSION_TYPE = 'sqlalchemy'
### DATABASE - MySQL
# SQLALCHEMY_DATABASE_URI = 'mysql://' + SQLA_DB_USER + ':' + SQLA_DB_PASSWORD + '@' + SQLA_DB_HOST + '/' + SQLA_DB_NAME
## Don't forget to uncomment the import in the top
#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 - 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
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
@ -107,6 +134,14 @@ SAML_ENABLED = False
# ### the user is set as a non-administrator user.
# #SAML_ATTRIBUTE_ADMIN = 'https://example.edu/pdns-admin'
## Attribute to get admin status for groups with the IdP
# ### Default: Don't set administrator group with SAML attributes
#SAML_GROUP_ADMIN_NAME = 'GroupName'
## Attribute to get operator status for groups with the IdP
# ### Default: Don't set operator group with SAML attributes
#SAML_GROUP_OPERATOR_NAME = 'GroupName'
# ## Attribute to get account names from
# ### Default: Don't control accounts with SAML attribute
# ### If set, the user will be added and removed from accounts to match
@ -114,6 +149,16 @@ SAML_ENABLED = False
# ### be created and the user added to them.
# SAML_ATTRIBUTE_ACCOUNT = 'https://example.edu/pdns-account'
# ## Attribute name that aggregates group names
# ### Default: Don't collect IdP groups from SAML group attributes
# ### In Okta, you can assign administrators by group using "Group Attribute Statements."
# ### In this case, the SAML_ATTRIBUTE_GROUP will be the attribute name for a collection of
# ### groups passed in the SAML assertion. From there, you can specify a SAML_GROUP_ADMIN_NAME.
# ### If the user is a member of this group, and that group name is included in the collection,
# ### the user will be set as an administrator.
# #SAML_ATTRIBUTE_GROUP = 'https://example.edu/pdns-groups'
# #SAML_GROUP_ADMIN_NAME = 'PowerDNSAdmin-Administrators'
# SAML_SP_ENTITY_ID = 'http://<SAML SP Entity ID>'
# SAML_SP_CONTACT_NAME = '<contact name>'
# SAML_SP_CONTACT_MAIL = '<contact mail>'
@ -127,8 +172,8 @@ SAML_ENABLED = False
# CAUTION: For production use, usage of self-signed certificates it's highly discouraged.
# Use certificates from trusted CA instead
# ###########################################################################################
# SAML_CERT_FILE = '/etc/pki/powerdns-admin/cert.crt'
# SAML_CERT_KEY = '/etc/pki/powerdns-admin/key.pem'
# SAML_CERT = '/etc/pki/powerdns-admin/cert.crt'
# SAML_KEY = '/etc/pki/powerdns-admin/key.pem'
# Configures if SAML tokens should be encrypted.
# SAML_SIGN_REQUEST = False
@ -142,6 +187,10 @@ SAML_ENABLED = False
# #SAML_ASSERTION_ENCRYPTED = True
# Some IdPs, like Okta, do not return Attribute Statements by default
# Set the following to False if you are using Okta and not manually configuring Attribute Statements
# #SAML_WANT_ATTRIBUTE_STATEMENT = True
# Remote authentication settings
# Whether to enable remote user authentication or not

@ -1,102 +1,2 @@
# Defaults for Docker image
BIND_ADDRESS = '0.0.0.0'
PORT = 80
SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db'
legal_envvars = (
'SECRET_KEY',
'BIND_ADDRESS',
'PORT',
'LOG_LEVEL',
'SALT',
'SQLALCHEMY_TRACK_MODIFICATIONS',
'SQLALCHEMY_DATABASE_URI',
'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',
'OFFLINE_MODE',
'REMOTE_USER_LOGOUT_URL',
'REMOTE_USER_COOKIES'
)
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',
'SAML_ENABLED',
'SAML_DEBUG',
'SAML_SIGN_REQUEST',
'SAML_WANT_MESSAGE_SIGNED',
'SAML_LOGOUT',
'SAML_ASSERTION_ENCRYPTED',
'OFFLINE_MODE',
'REMOTE_USER_ENABLED'
)
# import everything from environment variables
import os
import sys
def str2bool(v):
return v.lower() in ("true", "yes", "1")
for v in legal_envvars:
ret = None
# _FILE suffix will allow to read value from file, usefull for Docker's
# 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)
sys.modules[__name__].__dict__[v] = ret
SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db'

@ -1,5 +1,5 @@
import os
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)))
basedir = os.path.abspath(os.path.dirname(__file__))
### BASIC APP CONFIG
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'

@ -0,0 +1,16 @@
#!/bin/bash
# Create a new group for PowerDNS-Admin
groupadd powerdnsadmin
# Create a user for PowerDNS-Admin
useradd --system -g powerdnsadmin powerdnsadmin
# Make the new user and group the owners of the PowerDNS-Admin files
chown -R powerdnsadmin:powerdnsadmin /opt/web/powerdns-admin
# Start the PowerDNS-Admin service
systemctl start powerdns-admin
# Enable the PowerDNS-Admin service to start automatically at boot
systemctl enable powerdns-admin

@ -0,0 +1,16 @@
@echo off
rem Create a new group for PowerDNS-Admin
net localgroup powerdnsadmin /add
rem Create a user for PowerDNS-Admin
net user powerdnsadmin /add /passwordchg:no /homedir:nul /active:yes /expires:never /passwordreq:no /s
rem Make the new user and group the owners of the PowerDNS-Admin files
icacls "C:\path\to\powerdns-admin" /setowner "powerdnsadmin"
rem Start the PowerDNS-Admin service
net start powerdns-admin
rem Enable the PowerDNS-Admin service to start automatically at boot
sc config powerdns-admin start= auto

@ -0,0 +1,15 @@
version: '3.3'
services:
core:
image: powerdnsadmin/pda-legacy:latest
restart: unless-stopped
environment:
- SECRET_KEY=INSECURE-CHANGE-ME-9I0DAtfkfj5JmBkPSaHah3ECAa8Df5KK
ports:
- "12000:9191"
volumes:
- "core_data:/data"
volumes:
core_data:

@ -0,0 +1,2 @@
# Kubernetes
Example and simplified deployment for kubernetes.

@ -0,0 +1,8 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: powerdnsadmin-env
data:
FLASK_APP: powerdnsadmin/__init__.py
SECRET_KEY: changeme_secret
SQLALCHEMY_DATABASE_URI: 'mysql://user:password@host/database'

@ -0,0 +1,29 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: powerdnsadmin
labels:
app: powerdnsadmin
spec:
strategy:
type: RollingUpdate
replicas: 1
selector:
matchLabels:
app: powerdnsadmin
template:
metadata:
labels:
app: powerdnsadmin
spec:
containers:
- name: powerdnsadmin
image: powerdnsadmin/pda-legacy
ports:
- containerPort: 80
protocol: TCP
envFrom:
- configMapRef:
name: powerdnsadmin-env
imagePullPolicy: Always
restartPolicy: Always

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: powerdnsadmin
namespace: powerdnsadmin
labels:
app: powerdnsadmin
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: powerdnsadmin

@ -1,11 +1,11 @@
version: "2.1"
version: "3.8"
services:
powerdns-admin:
image: powerdns-admin-test
build:
context: .
dockerfile: docker-test/Dockerfile
image: powerdns-admin-test
container_name: powerdns-admin-test
ports:
- "9191:80"
@ -17,10 +17,10 @@ services:
- pdns-server
pdns-server:
image: pdns-server-test
build:
context: .
dockerfile: docker-test/Dockerfile.pdns
image: pdns-server-test
ports:
- "5053:53"
- "5053:53/udp"

@ -2,7 +2,7 @@ version: "3"
services:
app:
image: ngoduykhanh/powerdns-admin:latest
image: powerdnsadmin/pda-legacy:latest
container_name: powerdns_admin
ports:
- "9191:80"
@ -15,4 +15,3 @@ services:
- GUNICORN_TIMEOUT=60
- GUNICORN_WORKERS=2
- GUNICORN_LOGLEVEL=DEBUG
- OFFLINE_MODE=False # True for offline, False for external resources

@ -1,15 +1,36 @@
FROM debian:stretch-slim
FROM debian:bullseye-slim
LABEL maintainer="k@ndk.name"
ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev build-essential libmariadb-dev-compat \
&& curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get install -y --no-install-recommends \
apt-transport-https \
curl \
build-essential \
libffi-dev \
libldap2-dev \
libmariadb-dev-compat \
libpq-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libxslt1-dev \
locales \
locales-all \
pkg-config \
python3-dev \
python3-pip \
python3-setuptools \
&& curl -sL https://deb.nodesource.com/setup_lts.x | bash - \
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
&& apt-get update -y \
&& apt-get install -y nodejs yarn \
&& apt-get install -y --no-install-recommends \
nodejs \
yarn \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
@ -21,8 +42,6 @@ RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
COPY . /app
COPY ./docker/entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENV FLASK_APP=powerdnsadmin/__init__.py
RUN yarn install --pure-lockfile --production \
@ -31,4 +50,4 @@ RUN yarn install --pure-lockfile --production \
COPY ./docker-test/wait-for-pdns.sh /opt
RUN chmod u+x /opt/wait-for-pdns.sh
CMD ["/opt/wait-for-pdns.sh", "/usr/local/bin/pytest","--capture=no","-vv"]
CMD ["/opt/wait-for-pdns.sh", "/usr/local/bin/pytest", "-W", "ignore::DeprecationWarning", "--capture=no", "-vv"]

@ -10,9 +10,9 @@ fi
# Import schema structure
if [ -e "/data/pdns.sql" ]; then
rm /data/pdns.db
rm -f /data/pdns.db
cat /data/pdns.sql | sqlite3 /data/pdns.db
rm /data/pdns.sql
rm -f /data/pdns.sql
echo "Imported schema structure"
fi

@ -1,14 +1,16 @@
FROM alpine:3.12 AS builder
LABEL maintainer="k@ndk.name"
FROM alpine:3.17 AS builder
ARG BUILD_DEPENDENCIES="build-base \
libffi-dev \
libpq-dev \
libxml2-dev \
mariadb-connector-c-dev \
openldap-dev \
python3-dev \
xmlsec-dev \
yarn"
npm \
yarn \
cargo"
ENV LC_ALL=en_US.UTF-8 \
LANG=en_US.UTF-8 \
@ -29,7 +31,7 @@ COPY ./requirements.txt /build/requirements.txt
# Get application dependencies
RUN pip install --upgrade pip && \
pip install -r requirements.txt
pip install --use-pep517 -r requirements.txt
# Add sources
COPY . /build
@ -37,7 +39,7 @@ COPY . /build
# Prepare assets
RUN yarn install --pure-lockfile --production && \
yarn cache clean && \
sed -i -r -e "s|'cssmin',\s?'cssrewrite'|'cssmin'|g" /build/powerdnsadmin/assets.py && \
sed -i -r -e "s|'rcssmin',\s?'cssrewrite'|'rcssmin'|g" /build/powerdnsadmin/assets.py && \
flask assets build
RUN mv /build/powerdnsadmin/static /tmp/static && \
@ -45,6 +47,7 @@ RUN mv /build/powerdnsadmin/static /tmp/static && \
cp -r /tmp/static/generated /build/powerdnsadmin/static && \
cp -r /tmp/static/assets /build/powerdnsadmin/static && \
cp -r /tmp/static/img /build/powerdnsadmin/static && \
find /tmp/static/node_modules -name 'webfonts' -exec cp -r {} /build/powerdnsadmin/static \; && \
find /tmp/static/node_modules -name 'fonts' -exec cp -r {} /build/powerdnsadmin/static \; && \
find /tmp/static/node_modules/icheck/skins/square -name '*.png' -exec cp {} /build/powerdnsadmin/static/generated \;
@ -64,21 +67,13 @@ RUN mkdir -p /app && \
mkdir -p /app/configs && \
cp -r /build/configs/docker_config.py /app/configs
# Cleanup
RUN pip install pip-autoremove && \
pip-autoremove cssmin -y && \
pip-autoremove jsmin -y && \
pip-autoremove pytest -y && \
pip uninstall -y pip-autoremove && \
apk del ${BUILD_DEPENDENCIES}
# Build image
FROM alpine:3.12
FROM alpine:3.17
ENV FLASK_APP=/app/powerdnsadmin/__init__.py \
USER=pda
RUN apk add --no-cache mariadb-connector-c postgresql-client py3-gunicorn py3-psycopg2 xmlsec tzdata libcap && \
RUN apk add --no-cache mariadb-connector-c postgresql-client py3-gunicorn py3-pyldap py3-flask py3-psycopg2 xmlsec tzdata libcap && \
addgroup -S ${USER} && \
adduser -S -D -G ${USER} ${USER} && \
mkdir /data && \
@ -87,16 +82,16 @@ RUN apk add --no-cache mariadb-connector-c postgresql-client py3-gunicorn py3-ps
apk del libcap
COPY --from=builder /usr/bin/flask /usr/bin/
COPY --from=builder /usr/lib/python3.8/site-packages /usr/lib/python3.8/site-packages/
COPY --from=builder /usr/lib/python3.10/site-packages /usr/lib/python3.10/site-packages/
COPY --from=builder --chown=root:${USER} /app /app/
COPY ./docker/entrypoint.sh /usr/bin/
WORKDIR /app
RUN chown ${USER}:${USER} ./configs && \
RUN chown ${USER}:${USER} ./configs /app && \
cat ./powerdnsadmin/default_config.py ./configs/docker_config.py > ./powerdnsadmin/docker_config.py
EXPOSE 80/tcp
USER ${USER}
HEALTHCHECK CMD ["wget","--output-document=-","--quiet","--tries=1","http://127.0.0.1/"]
HEALTHCHECK --interval=5s --timeout=5s --start-period=20s --retries=5 CMD wget --output-document=- --quiet --tries=1 http://127.0.0.1${SCRIPT_NAME:-/}
ENTRYPOINT ["entrypoint.sh"]
CMD ["gunicorn","powerdnsadmin:create_app()"]

@ -2,7 +2,7 @@
set -euo pipefail
cd /app
GUNICORN_TIMEOUT="${GUINCORN_TIMEOUT:-120}"
GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}"
GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}"
GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-info}"
BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:80}"

@ -1,105 +1,136 @@
### API Usage
#### Getting started with docker
1. Run docker image docker-compose up, go to UI http://localhost:9191, at http://localhost:9191/swagger is swagger API specification
2. Click to register user, type e.g. user: admin and password: admin
3. Login to UI in settings enable allow domain creation for users, now you can create and manage domains with admin account and also ordinary users
4. Encode your user and password to base64, in our example we have user admin and password admin so in linux cmd line we type:
4. Click on the API Keys menu then click on teh "Add Key" button to add a new Administrator Key
5. Keep the base64 encoded apikey somewhere safe as it won't be available in clear anymore
```
#### Accessing the API
PDA has its own API, that should not be confused with the PowerDNS API. Keep in mind that you have to enable PowerDNS API with a key that will be used by PDA to manage it. Therefore, you should use PDA created keys to browse PDA's API, on PDA's adress and port. They don't grant access to PowerDNS' API.
The PDA API consists of two distinct parts:
- The /powerdnsadmin endpoints manages PDA content (accounts, users, apikeys) and also allow domain creation/deletion
- The /server endpoints are proxying queries to the backend PowerDNS instance's API. PDA acts as a proxy managing several API Keys and permissions to the PowerDNS content.
The requests to the API needs two headers:
- The classic 'Content-Type: application/json' is required to all POST and PUT requests, though it's harmless to use it on each call
- The authentication header to provide either the login:password basic authentication or the Api Key authentication.
When you access the `/powerdnsadmin` endpoint, you must use the Basic Auth:
```bash
# Encode your user and password to base64
$ echo -n 'admin:admin'|base64
YWRtaW46YWRtaW4=
# Use the ouput as your basic auth header
curl -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X <method> <url>
```
we use generated output in basic authentication, we authenticate as user,
with basic authentication, we can create/delete/get zone and create/delete/get/update apikeys
creating domain:
When you access the `/server` endpoint, you must use the ApiKey
```bash
# Use the already base64 encoded key in your header
curl -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' -X <method> <url>
```
Finally, the `/sync_domains` endpoint accepts both basic and apikey authentication
#### Examples
Creating domain via `/powerdnsadmin`:
```bash
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/zones --data '{"name": "yourdomain.com.", "kind": "NATIVE", "nameservers": ["ns1.mydomain.com."]}'
```
creating apikey which has Administrator role, apikey can have also User role, when creating such apikey you have to specify also domain for which apikey is valid:
Creating an apikey which has the Administrator role:
```
```bash
# Create the key
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/apikeys --data '{"description": "masterkey","domains":[], "role": "Administrator"}'
```
Example response (don't forget to save the plain key from the output)
call above will return response like this:
```
[{"description": "samekey", "domains": [], "role": {"name": "Administrator", "id": 1}, "id": 2, "plain_key": "aGCthP3KLAeyjZI"}]
```json
[
{
"accounts": [],
"description": "masterkey",
"domains": [],
"role": {
"name": "Administrator",
"id": 1
},
"id": 2,
"plain_key": "aGCthP3KLAeyjZI"
}
]
```
we take plain_key and base64 encode it, this is the only time we can get API key in plain text and save it somewhere:
We can use the apikey for all calls to PowerDNS (don't forget to specify Content-Type):
```
$ echo -n 'aGCthP3KLAeyjZI'|base64
YUdDdGhQM0tMQWV5alpJ
```
Getting powerdns configuration (Administrator Key is needed):
We can use apikey for all calls specified in our API specification (it tries to follow powerdns API 1:1, only tsigkeys endpoints are not yet implemented), don't forget to specify Content-Type!
getting powerdns configuration:
```
```bash
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/config
```
creating and updating records:
Creating and updating records:
```
```bash
curl -X PATCH -H 'Content-Type: application/json' --data '{"rrsets": [{"name": "test1.yourdomain.com.","type": "A","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "192.0.2.5", "disabled": false} ]},{"name": "test2.yourdomain.com.","type": "AAAA","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "2001:db8::6", "disabled": false} ]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://127.0.0.1:9191/api/v1/servers/localhost/zones/yourdomain.com.
```
getting domain:
Getting a domain:
```
```bash
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
```
list zone records:
List a zone's records:
```
```bash
curl -H 'Content-Type: application/json' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
```
add new record:
Add a new record:
```
```bash
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.5.4", "disabled": false } ] } ] }' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
```
update record:
Update a record:
```
```bash
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.2.5", "disabled": false, "name": "test.yourdomain.com.", "ttl": 86400, "type": "A"}]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
```
delete record:
Delete a record:
```
```bash
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "DELETE"}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq
```
### Generate ER diagram
```
With docker
```bash
# Install build packages
apt-get install python-dev graphviz libgraphviz-dev pkg-config
```
```
# Get the required python libraries
pip install graphviz mysqlclient ERAlchemy
```
```
# Start the docker container
docker-compose up -d
```
```
# Set environment variables
source .env
```
```
# Generate the diagrams
eralchemy -i 'mysql://${PDA_DB_USER}:${PDA_DB_PASSWORD}@'$(docker inspect powerdns-admin-mysql|jq -jr '.[0].NetworkSettings.Networks.powerdnsadmin_default.IPAddress')':3306/powerdns_admin' -o /tmp/output.pdf
```

74
docs/CODE_OF_CONDUCT.md Normal file

@ -0,0 +1,74 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [admin@powerdnsadmin.org](mailto:admin@powerdnsadmin.org). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

107
docs/CONTRIBUTING.md Normal file

@ -0,0 +1,107 @@
# Contribution Guide
**Looking for help?** Try taking a look at the project's
[Support Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/.github/SUPPORT.md) or joining
our [Discord Server](https://discord.powerdnsadmin.org).
<div align="center">
<h3>
:bug: <a href="#bug-reporting-bugs">Report a bug</a> &middot;
:bulb: <a href="#bulb-feature-requests">Suggest a feature</a> &middot;
:arrow_heading_up: <a href="#arrow_heading_up-submitting-pull-requests">Submit a pull request</a>
</h3>
<h3>
:rescue_worker_helmet: <a href="#rescue_worker_helmet-become-a-maintainer">Become a maintainer</a> &middot;
:heart: <a href="#heart-other-ways-to-contribute">Other ideas</a>
</h3>
</div>
<h3></h3>
Some general tips for engaging here on GitHub:
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
## [Project Update - PLEASE READ!!!](https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions/1708)
## :bug: Reporting Bugs
* First, ensure that you're running the [latest stable version](https://github.com/PowerDNS-Admin/PowerDNS-Admin/releases) of PDA. If you're running an older version, there's a chance that the bug has already been fixed.
* Next, search our [issues list](https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated.
* If you can't find any existing issues (open or closed) that seem to match yours, you're welcome to [submit a new bug report](https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues/new/choose). Be sure to complete the entire report template, including detailed steps that someone triaging your issue can follow to confirm the reported behavior. (If we're not able to replicate the bug based on the information provided, we'll ask for additional detail.)
* Some other tips to keep in mind:
* Error messages and screenshots are especially helpful.
* Don't prepend your issue title with a label like `[Bug]`; the proper label will be assigned automatically.
* Verify that you have GitHub notifications enabled and are subscribed to your issue after submitting.
* We appreciate your patience as bugs are prioritized by their severity, impact, and difficulty to resolve.
## :bulb: Feature Requests
* First, check the GitHub [issues list](https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues?q=is%3Aissue) to see if the feature you have in mind has already been proposed. If you happen to find an open feature request that matches your idea, click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This ensures that the issue has a better chance of receiving attention. Also feel free to add a comment with any additional justification for the feature.
* If you have a rough idea that's not quite ready for formal submission yet, start a [GitHub discussion](https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions) instead. This is a great way to test the viability and narrow down the scope of a new feature prior to submitting a formal proposal, and can serve to generate interest in your idea from other community members.
* Once you're ready, submit a feature request [using this template](https://github.com/PowerDNS-Admin/PowerDNS-Admin/issues/choose). Be sure to provide sufficient context and detail to convey exactly what you're proposing and why. The stronger your use case, the better chance your proposal has of being accepted.
* Some other tips to keep in mind:
* Don't prepend your issue title with a label like `[Feature]`; the proper label will be assigned automatically.
* Try to anticipate any likely questions about your proposal and provide that information proactively.
* Verify that you have GitHub notifications enabled and are subscribed to your issue after submitting.
* You're welcome to volunteer to implement your FR, but don't submit a pull request until it has been approved.
## :arrow_heading_up: Submitting Pull Requests
* [Pull requests](https://docs.github.com/en/pull-requests) (a feature of GitHub) are used to propose changes to PDA's code base. Our process generally goes like this:
* A user opens a new issue (bug report or feature request)
* A maintainer triages the issue and may mark it as needing an owner
* The issue's author can volunteer to own it, or someone else can
* A maintainer assigns the issue to whomever volunteers
* The issue owner submits a pull request that will resolve the issue
* A maintainer reviews and merges the pull request, closing the issue
* It's very important that you not submit a pull request until a relevant issue has been opened **and** assigned to you. Otherwise, you risk wasting time on work that may ultimately not be needed.
* New pull requests should generally be based off of the `dev` branch, rather than `master`. The `dev` branch is used for ongoing development, while `master` is used for tracking stable releases.
* In most cases, it is not necessary to add a changelog entry: A maintainer will take care of this when the PR is merged. (This helps avoid merge conflicts resulting from multiple PRs being submitted simultaneously.)
* All code submissions should meet the following criteria (CI will eventually enforce these checks):
* Python syntax is valid
* PEP 8 compliance is enforced, with the exception that lines may be
greater than 80 characters in length
* Some other tips to keep in mind:
* If you'd like to volunteer for someone else's issue, please post a comment on that issue letting us know. (This will allow the maintainers to assign it to you.)
* All new functionality must include relevant tests where applicable.
## :rescue_worker_helmet: Become a Maintainer
We're always looking for motivated individuals to join the maintainers team and help drive PDA's long-term development. Some of our most sought-after skills include:
* Python development with a strong focus on the [Flask](https://flask.palletsprojects.com/) and [Django](https://www.djangoproject.com/) frameworks
* Expertise working with SQLite, MySQL, and/or PostgreSQL databases
* Javascript & TypeScript proficiency
* A knack for web application design (HTML & CSS)
* Familiarity with git and software development best practices
* Excellent attention to detail
* Working experience in the field of network operations as it relates to the use of DNS (Domain Name System) servers.
We generally ask that maintainers dedicate around four hours of work to the project each week on average, which includes both hands-on development and project management tasks such as issue triage.
We do maintain an active Mattermost instance for internal communication, but we also use GitHub issues for project management.
Some maintainers petition their employer to grant some of their paid time to work on PDA.
Interested? You can contact our lead maintainer, Matt Scott, at admin@powerdnsadmin.org. We'd love to have you on the team!
## :heart: Other Ways to Contribute
You don't have to be a developer to contribute to PDA: There are plenty of other ways you can add value to the community! Below are just a few examples:
* Help answer questions and provide feedback in our [GitHub discussions](https://github.com/PowerDNS-Admin/PowerDNS-Admin/discussions).
* Write a blog article or record a YouTube video demonstrating how PDA is used at your organization.

@ -0,0 +1,100 @@
# PDA Project Update
## Introduction
Hello PDA community members,
My name is Matt Scott, and I am the owner of [Azorian Solutions](https://azorian.solutions), a consultancy for the
Internet Service Provider (ISP) industry. I'm pleased to announce that I have taken ownership of the PDA project and
will be taking over the lead maintainer role, effective immediately.
Please always remember and thank both [Khanh Ngo](https://github.com/ngoduykhanh) and
[Jérôme Becot](https://github.com/jbe-dw) for their efforts in keeping this project alive thus far. Without the effort
of Khanh creating the PDA project and community, and the efforts of Jérôme for holding up the lead maintainer role after
Khanh had to step down, this project would not still be alive today.
With that being said, please read through all the following announcements as they are important if you're an active PDA
user or community member. I intend to make many great enhancements to the project, but it could be a bumpy road ahead.
### Project Maintenance
As it stands today, contributions to the project are at a low. At this point, there is a rather large backlog of issues
and feature requests in contrast to the current maintenance capacities. This is not to say you should lose hope though!
As part of this project transition, some additional contribution interest has been generated and I expect to attract
more with the changes I'm planning to make. In the near future, I may by-pass some usual maintenance processes in order
to expedite some changes to the project that have been outstanding for some time.
This is to say however that unless the project attracts a healthy new contribution base, issues may continue to pile up
as maintenance capacity is rather limited. This is further complicated by the fact that the current code base is harder
to follow naturally since it largely lacks uniformity and standards. This lack of uniformity has lead to a difficult
situation that makes implementing certain changes less effective. This status quo is not uncommon with projects born how
PDA was born, so it's unfortunate but not unexpected.
### Change of Direction
In order to reorganize the project and get it on a track to a future that allows it to contend with other commercial
quality products, I had to make many considerations to the proficiencies of two unique paths forward to achieve this
goal. One path forward is seemingly obvious, continue maintaining the current code base while overhauling it to shift it
towards the envisioned goal. The other path is a fresh solution design with a complete rebuild.
The answer to the aforementioned decision might seem obvious to those of you who typically favor the "don't reinvent the
wheel" mentality. I'm unclear of the details surrounding the original use-case that drove the development of this
project, but I don't believe it was on-par with some use-cases we see today which include operators handling many tens
of thousands of zones and/or records. There are many changes that have been (sometimes) haphazardly implemented which
has lead to the previously mentioned lack of uniformity among other issues. To put it simply, I'm not sure if the
project ever had a grand vision per se but instead was mostly reactionary to community requests.
I believe that the current project has served the community fairly well from what I can tell. I know the product has
certainly helped me in my professional efforts with many environments. I also believe that it's time to pivot so that
the project can realize it's true potential, considering the existing user base. For this reason, I am beginning the
planning phase of a project overhaul. This effort will involve a complete re-engineering of the project's contribution
standards and requirements, technology stack, and project structure.
This was not an easy decision to come to but one must appreciate that there aren't as many people that can get very
excited about working on the current project code base. The current project has many barriers to entry which I intend to
drastically impact with future changes. The reality is that it's easier to gain contribution participation with a new
build effort as it offers an opportunity to own a part of the project with impactful contributions.
### Project Enhancements
Since this is the beginning of a rebirth of the project so to speak, I want to implement a new operational tactic that
will hopefully drive contributions through incentive. Many of us understand that any project, needs a leader to stay on
track and organized. If everything were a democratic process, it would take too long and suffer unnecessary challenges.
With that being said, I do believe that there is plenty of opportunity through-out various development phases of the
project to allow for a democratic process where the community contributors and members can participate in the
decision-making.
The plan to achieve the aforementioned democratic goal is to centralize communications and define some basic structured
processes. To do this, more effective methods of communication have been implemented to allow those interested in
contributing to easily participate in fluid, open communication. This has already been proving to be quite effective for
exchanging ideas and visions while addressing the issue with contributors living in vastly different time zones. This is
effectively a private chat hosted by the PDA project using Mattermost (a Slack-like alternative).
Even if you aren't in a position to directly contribute work to the project, you can still contribute by participating
in these very important and early discussions that will impact the solution engineering. If the PDA project is an
important tool in your organization, I encourage you to join the conversation and contribute where applicable your
use-cases. Having more insight on the community use-cases will only benefit the future of this project.
If you're interested in joining the conversation, please email me at
[admin@powerdnsadmin.org](mailto:admin@powerdnsadmin.org) for an invitation.
### Re-branding
As part of this project transition, I will also be changing the naming scheme in order to support the future development
efforts toward a newly engineered solution. The current PDA project will ultimately become known as the "PDA Legacy"
application. This change will help facilitate the long-term solution to take the branding position of the existing
solution. Another effort I will be making is to get an app landing page online at the project's new domain:
[powerdnsadmin.org](https://powerdnsadmin.org). This will act as one more point of online exposure for the project which
will hopefully lend itself well to attracting additional community members.
### Contribution Requirements
Another big change that will be made with the new project, will be well-defined contribution requirements. I realize
these requirements can be demotivating for some, but they are a necessary evil to ensure the project actually achieves
its goals effectively. It's important to always remember that strict requirements are to everyone's benefit as they push
for order where chaos is quite destructive.
### Closing
I hope these announcements garner more participation in the PDA community. The project definitely needs more help to
achieve any goal at this point, so your participation is valued!

@ -0,0 +1,109 @@
# PDA Project Update
## Introduction
Hello PDA community members,
I know it has been quite awhile since the last formal announcement like this. Things have been quite busy and difficult
for me both professional and personally. While I try hard to never make my problems someone else's problems, I do
believe it's important to be transparent with the community. I'm not going to go into details, but I will say that I
have been dealing with some mental health issues that have been quite challenging. I'm not one to give up though,
so I'm pushing through and trying to get back on track.
With that being said, let's jump into the announcements.
### Project Maintenance
Granted I haven't been nearly as active on the project as I would like to be, I have been keeping an eye on things and
trying to keep up with the maintenance. I know there are a lot of issues and feature requests that have been piling up,
and I'm sorry for that. Even if I had been more active in recent months, it would have not changed the true root cause
of the issue.
This project was started out of a need for an individual's own use-case. I don't believe it was never intended to be a
commercial quality product nor a community project. It did however gain traction quickly and the community grew. This
is a great thing, but it also comes with some challenges. The biggest challenge is that the project was never designed
to be a community project. This means that the project lacks many of the things that are required to effectively manage
a community project. This is not to say that the project is doomed, but many of the fast-paced changes combined with
the lack of standards has lead to a difficult situation that makes implementing certain changes incredibly unproductive
and quite often, entirely counter-productive.
After many years of accepting contributions from those who are not professional developers, the project has become quite
difficult to maintain. This is not to say that I don't appreciate the contributions, but it's important to understand
that the state of the code-base for the project is not in a good place. This is not uncommon with projects born how PDA
was born, so it's unfortunate but not unexpected.
As of today, there are so many dependencies and a large amount of very poorly implemented features that it's difficult
to make any changes without breaking many other pieces. This is further complicated by the fact that the current code
base is harder to follow naturally since it largely lacks uniformity and standards. This lack of uniformity has lead to
a situation where automated regression testing is not possible. This is a very important aspect of any project that
expects to be able to make changes without breaking things. This is also a very important aspect of any project that
expects to be able to accept contributions from the community with minimum management resources.
The hard reality is that the majority of stakeholders in the project are not professional developers. This naturally
means the amount of people that can offer quality contributions is very limited. This problem is further aggravated by
the poor quality feature implementation which is very hard to follow, even for seasoned developers like myself. So many
seemingly small issues that have been reported, have lead to finding that the resolution is not as simple as it seems.
### New Direction
As I previously stated in my last formal announcement, we would be working towards a total replacement of the project.
Unfortunately, this is not a simple task, and it's not something that can be done quickly. Furthermore, with
increasingly limited capacity in our own lives to work on this, we are essentially drowning in a sea of technical debt
created by the past decisions of the project to accept all contributions. We have essentially reached a point where
far too much time and resources are being wasted just to attempt to meet the current demand of requests on the current
edition of PDA. This is a tragedy because the efforts that are invested into the current edition, really aren't
creating true progress for the project, but instead merely delaying the inevitable.
As I have stated before to many community members, one aspect of taking over management of this project to ultimately
save it and keep it alive, would involve making hard decisions that many will not agree with. It's unfortunate that
many of those who are less than supportive of these decisions, often lack the appropriate experience to understand the
importance of these decisions. I'm not saying that I'm always right, but I am saying that it's not hard to see where
this is headed without some drastic changes.
With all of that being said, it's time for me to make some hard decisions. I have decided that the best course of
action is to stop accepting contributions to the current edition of PDA. At this point, due to the aforementioned
issues that lead to breaking the application with seemingly simple changes, it's just not worth the effort to try to
keep up with the current edition. This is not to say that I'm giving up on the project, but instead I'm going to
re-focus my efforts on the new edition of PDA. This is the only way to ensure that the project will survive and
hopefully thrive in the future.
I will not abandon the current set of updates that were planned for the next release of `0.4.2` however. I have
re-scheduled that release to be out by the end of the year. This will be the last release of the current edition of
PDA. The consensus from some users is that the current edition is stable enough to be used in production environments.
I don't necessarily agree with that, but I do believe that it's stable enough to be used in production
environments with the understanding that it's not a commercial quality product.
### Future Contributions
For those of you wondering about contributions to the new edition of PDA, the answer for now is simple. I won't be
accepting any contributions to the new edition until I can achieve a stable release that delivers the core features of
the current edition. This is not to say that I won't be accepting any contributions at all, but instead that I will be
very selective about what contributions I accept. I believe this is the only way to ensure that a solid foundation not
only takes shape, but remains solid.
It is well understood that many developers have their own ways of doing things, but it's important to understand
that this project is not a personal project. This project is a community project and therefore must be treated as such.
This means that the project must be engineered in a way that allows for the community to participate in the development
process. This is not possible if the project is not engineered in a way that is easy to follow and understand.
### Project Enhancements
It should be understood that one of the greatest benefits of this pivot is that it will allow for a more structured
development process. As a result of that, the project could potentially see a future where it adopts a whole new set of
features that weren't previously imagined. One prime example of this could be integration with registrar APIs. This
could make easy work of tasks such as DNSSEC key rotation, which is currently a very manual process.
I am still working on final project requirements for additional phases of the new PDA edition, but these additions
won't receive any attention until the core features are implemented. I will be sure to make announcements as these
requirements are finalized. It is my intention to follow a request for proposal (RFP) process for these additional
features. This will allow the community to participate in the decision-making process for future expansion of the
project.
### Closing
I hope that by the time you have reached this point in the announcement, that I have elicited new hope for the
long-term future of the project. I know that many of you have been waiting for a long time for some of the features that have been
requested. I know that many of you have been waiting for a long time for some of the issues to be resolved, for
requested features to be implemented, and for the project to be more stable. It's unfortunate that it has taken this
long to get to this point, but this is the nature of life itself. I hope that you can understand that this is the only
reasonable gamble that the project survives and thrives in the future.

@ -17,4 +17,84 @@ Now you can enable the OAuth in PowerDNS-Admin.
* Replace the [tenantID] in the default URLs for authorize and token with your Tenant ID.
* Restart PowerDNS-Admin
This should allow you to log in using OAuth.
This should allow you to log in using OAuth.
#### Keycloak
To link to Keycloak for authentication, you need to create a new client in the Keycloak Administration Console.
* Log in to the Keycloak Administration Console
* Go to Clients > Create
* Enter a Client ID (for example 'powerdns-admin') and click 'Save'
* Scroll down to 'Access Type' and choose 'Confidential'.
* Scroll down to 'Valid Redirect URIs' and enter 'https://<pdnsa address>/oidc/authorized'
* Click 'Save'
* Go to the 'Credentials' tab and copy the Client Secret
* Log in to PowerDNS-Admin and go to 'Settings > Authentication > OpenID Connect OAuth'
* Enter the following details:
* Client key -> Client ID
* Client secret > Client secret copied from keycloak
* Scope: `profile`
* API URL: https://<keycloak url>/auth/realms/<realm>/protocol/openid-connect/
* Token URL: https://<keycloak url>/auth/realms/<realm>/protocol/openid-connect/token
* Authorize URL: https://<keycloak url>/auth/realms/<realm>/protocol/openid-connect/auth
* Logout URL: https://<keycloak url>/auth/realms/<realm>/protocol/openid-connect/logout
* Leave the rest default
* Save the changes and restart PowerDNS-Admin
* Use the new 'Sign in using OpenID Connect' button to log in.
#### OpenID Connect OAuth
To link to oidc service for authenticationregister your PowerDNS-Admin in the OIDC Provider. This requires your PowerDNS-Admin web interface to use an HTTPS URL.
Enable OpenID Connect OAuth option.
* Client key, The client ID
* Scope, The scope of the data.
* API URL, <oidc_provider_link>/auth (The ending can be different with each provider)
* Token URL, <oidc_provider_link>/token
* Authorize URL, <oidc_provider_link>/auth
* Metadata URL, <oidc_provider_link>/.well-known/openid-configuration
* Logout URL, <oidc_provider_link>/logout
* Username, This will be the claim that will be used as the username. (Usually preferred_username)
* First Name, This will be the firstname of the user. (Usually given_name)
* Last Name, This will be the lastname of the user. (Usually family_name)
* Email, This will be the email of the user. (Usually email)
#### To create accounts on oidc login use the following properties:
* Autoprovision Account Name Property, This property will set the name of the created account.
This property can be a string or a list.
* Autoprovision Account Description Property, This property will set the description of the created account.
This property can be a string or a list.
If we get a variable named "groups" and "groups_description" from our IdP.
This variable contains groups that the user is a part of.
We will put the variable name "groups" in the "Name Property" and "groups_description" in the "Description Property".
This will result in the following account being created:
Input we get from the Idp:
```
{
"preferred_username": "example_username",
"given_name": "example_firstame",
"family_name": "example_lastname",
"email": "example_email",
"groups": ["github", "gitlab"]
"groups_description": ["github.com", "gitlab.com"]
}
```
The user properties will be:
```
Username: customer_username
First Name: customer_firstame
Last Name: customer_lastname
Email: customer_email
Role: User
```
The groups properties will be:
```
Name: github Description: github.com Members: example_username
Name: gitlab Description: gitlab.com Members: example_username
```
If the option "delete_sso_accounts" is turned on the user will only be apart of groups the IdP provided and removed from all other accoubnts.

Binary file not shown.

After

(image error) Size: 196 KiB

50
docs/wiki/README.md Normal file

@ -0,0 +1,50 @@
# PowerDNS-Admin wiki
## Database Setup guides
- [MySQL / MariaDB](database-setup/Setup-MySQL-or-MariaDB.md)
- [PostgreSQL](database-setup/Setup-PostgreSQL.md)
## Installation guides
- [General (Read this first)](install/General.md)
- BSD:
- [Install on FreeBSD 12.1-RELEASE](install/Running-on-FreeBSD.md)
- Containers:
- [Install on Docker](install/Running-PowerDNS-Admin-on-Docker.md)
- Debian:
- [Install on Ubuntu or Debian](install/Running-PowerDNS-Admin-on-Ubuntu-or-Debian.md)
- Red-Hat:
- [Install on Centos 7](install/Running-PowerDNS-Admin-on-Centos-7.md)
- [Install on Fedora 23](install/Running-PowerDNS-Admin-on-Fedora-23.md)
- [Install on Fedora 30](install/Running-PowerDNS-Admin-on-Fedora-30.md)
### Post install Setup
- [Environment Variables](configuration/Environment-variables.md)
- [Getting started](configuration/Getting-started.md)
- SystemD:
- [Running PowerDNS-Admin as a service using Systemd](install/Running-PowerDNS-Admin-as-a-service-(Systemd).md)
### Web Server configuration
- [Supervisord](web-server/Supervisord-example.md)
- [Systemd](web-server/Systemd-example.md)
- [Systemd + Gunicorn + Nginx](web-server/Running-PowerDNS-Admin-with-Systemd-Gunicorn-and-Nginx.md)
- [Systemd + Gunicorn + Apache](web-server/Running-PowerDNS-Admin-with-Systemd,-Gunicorn-and-Apache.md)
- [uWSGI](web-server/uWSGI-example.md)
- [WSGI-Apache](web-server/WSGI-Apache-example.md)
- [Docker-ApacheReverseProxy](web-server/Running-Docker-Apache-Reverseproxy.md)
## Using PowerDNS-Admin
- Setting up a zone
- Adding a record
## Feature usage
- [DynDNS2](features/DynDNS2.md)
## Debugging
- [Debugging the build process](debug/build-process.md)

@ -0,0 +1,34 @@
Active Directory Setup - Tested with Windows Server 2012
1) Login as an admin to PowerDNS Admin
2) Go to Settings --> Authentication
3) Under Authentication, select LDAP
4) Click the Radio Button for Active Directory
5) Fill in the required info -
* LDAP URI - ldap://ip.of.your.domain.controller:389
* LDAP Base DN - dc=yourdomain,dc=com
* Active Directory domain - yourdomain.com
* Basic filter - (objectCategory=person)
* the brackets here are **very important**
* Username field - sAMAccountName
* GROUP SECURITY - Status - On
* Admin group - CN=Your_AD_Admin_Group,OU=Your_AD_OU,DC=yourdomain,DC=com
* Operator group - CN=Your_AD_Operator_Group,OU=Your_AD_OU,DC=yourdomain,DC=com
* User group - CN=Your_AD_User_Group,OU=Your_AD_OU,DC=yourdomain,DC=com
6) Click Save
7) Logout and re-login as an LDAP user from each of the above groups.
If you're having problems getting the correct information for your groups, the following tool can be useful -
https://docs.microsoft.com/en-us/sysinternals/downloads/adexplorer
In our testing, groups with spaces in the name did not work, we had to create groups with underscores to get everything operational.
YMMV

@ -0,0 +1,65 @@
# Supported environment variables
| Variable | Description | Required | Default value |
|--------------------------------|--------------------------------------------------------------------------|------------|---------------|
| BIND_ADDRESS |
| CSRF_COOKIE_SECURE |
| SESSION_TYPE | null | filesystem | sqlalchemy | | filesystem |
| LDAP_ENABLED |
| LOCAL_DB_ENABLED |
| LOG_LEVEL |
| MAIL_DEBUG |
| MAIL_DEFAULT_SENDER |
| MAIL_PASSWORD |
| MAIL_PORT |
| MAIL_SERVER |
| MAIL_USERNAME |
| MAIL_USE_SSL |
| MAIL_USE_TLS |
| OFFLINE_MODE |
| OIDC_OAUTH_API_URL | | | |
| OIDC_OAUTH_AUTHORIZE_URL |
| OIDC_OAUTH_TOKEN_URL | | | |
| OIDC_OAUTH_METADATA_URL | | | |
| PORT |
| SERVER_EXTERNAL_SSL | Forceful override of URL schema detection when using the url_for method. | False | None |
| REMOTE_USER_COOKIES |
| REMOTE_USER_LOGOUT_URL |
| SALT |
| SAML_ASSERTION_ENCRYPTED |
| SAML_ATTRIBUTE_ACCOUNT |
| SAML_ATTRIBUTE_ADMIN |
| SAML_ATTRIBUTE_EMAIL |
| SAML_ATTRIBUTE_GIVENNAME |
| SAML_ATTRIBUTE_GROUP |
| SAML_ATTRIBUTE_NAME |
| SAML_ATTRIBUTE_SURNAME |
| SAML_ATTRIBUTE_USERNAME |
| SAML_CERT |
| SAML_DEBUG |
| SAML_ENABLED |
| SAML_GROUP_ADMIN_NAME |
| SAML_GROUP_TO_ACCOUNT_MAPPING |
| SAML_IDP_SSO_BINDING |
| SAML_IDP_ENTITY_ID |
| SAML_KEY |
| SAML_LOGOUT |
| SAML_LOGOUT_URL |
| SAML_METADATA_CACHE_LIFETIME |
| SAML_METADATA_URL |
| SAML_NAMEID_FORMAT |
| SAML_PATH |
| SAML_SIGN_REQUEST |
| SAML_SP_CONTACT_MAIL |
| SAML_SP_CONTACT_NAME |
| SAML_SP_ENTITY_ID |
| SAML_WANT_MESSAGE_SIGNED |
| SECRET_KEY | Flask secret key [^1] | Y | no default |
| SESSION_COOKIE_SECURE |
| SIGNUP_ENABLED |
| SQLALCHEMY_DATABASE_URI | SQL Alchemy URI to connect to database | N | no default |
| SQLALCHEMY_TRACK_MODIFICATIONS |
| SQLALCHEMY_ENGINE_OPTIONS | json string. e.g. '{"pool_recycle":600,"echo":1}' [^2] |
[^1]: Flask secret key (see https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY for how to generate)
[^2]: See Flask-SQLAlchemy Documentation for all engine options.

@ -0,0 +1,16 @@
# Getting started with PowerDNS-Admin
In your FLASK_CONF (check the installation directions for where yours is) file, make sure you have the database URI filled in (in some previous documentation this was called config.py):
For MySQL / MariaDB:
```
SQLALCHEMY_DATABASE_URI = 'mysql://username:password@127.0.0.1/db_name'
```
For Postgres:
```
SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin:powerdnsadmin@127.0.0.1/powerdnsadmindb'
```
Open your web browser and go to `http://localhost:9191` to visit PowerDNS-Admin web interface. Register a user. The first user will be in the Administrator role.

@ -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.

@ -0,0 +1,4 @@
# Database setup guides
- [MySQL / MariaDB](Setup-MySQL-or-MariaDB.md)
- [PostgreSQL](Setup-PostgreSQL.md)

@ -0,0 +1,56 @@
# Setup MySQL database for PowerDNS-Admin
This guide will show you how to prepare a MySQL or MariaDB database for PowerDNS-Admin.
We assume the database is installed per your platform's directions (apt, yum, etc). Directions to do this can be found below:
- MariaDB:
- https://mariadb.com/kb/en/getting-installing-and-upgrading-mariadb/
- https://www.digitalocean.com/community/tutorials/how-to-install-mariadb-on-ubuntu-20-04
- MySQL:
- https://dev.mysql.com/downloads/mysql/
- https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04
The following directions assume a default configuration and for productions setups `mysql_secure_installation` has been run.
## Setup database:
Connect to the database (Usually using `mysql -u root -p` if a password has been set on the root database user or `sudo mysql` if not), then enter the following:
```
CREATE DATABASE `powerdnsadmin` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON `powerdnsadmin`.* TO 'pdnsadminuser'@'localhost' IDENTIFIED BY 'YOUR_PASSWORD_HERE';
FLUSH PRIVILEGES;
```
- If your database server is located on a different machine then change 'localhost' to '%'
- Replace YOUR_PASSWORD_HERE with a secure password.
Once there are no errors you can type `quit` in the mysql shell to exit from it.
## Install required packages:
### Red-hat based systems:
```
yum install MariaDB-shared mariadb-devel mysql-community-devel
```
### Debian based systems:
```
apt install libmysqlclient-dev
```
### Install python packages:
```
pip3 install mysqlclient==2.0.1
```
## Known issues:
Problem: If you plan to manage large zones, you may encounter some issues while applying changes. This is due to PowerDNS-Admin trying to insert the entire modified zone into the column history.detail.
Using MySQL/MariaDB, this column is created by default as TEXT and thus limited to 65,535 characters.
Solution: Convert the column to MEDIUMTEXT:
1. Connect to the database shell as described in the setup database section:
2. Execute the following commands:
```
USE powerdnsadmin;
ALTER TABLE history MODIFY detail MEDIUMTEXT;
```

@ -0,0 +1,79 @@
# Setup Postgres database for PowerDNS-Admin
This guide will show you how to prepare a PostgreSQL database for PowerDNS-Admin.
We assume the database is installed per your platform's directions (apt, yum, etc). Directions to do this can be found below:
- https://www.postgresql.org/download/
- https://www.digitalocean.com/community/tutorials/how-to-install-postgresql-on-ubuntu-22-04-quickstart
We assume a default configuration and only the postgres user existing.
## Setup database
The below will create a database called powerdnsadmindb and a user of powerdnsadmin.
```
$ sudo su - postgres
$ createuser powerdnsadmin
$ createdb -E UTF8 -l en_US.UTF-8 -O powerdnsadmin -T template0 powerdnsadmindb 'The database for PowerDNS-Admin'
$ psql
postgres=# ALTER ROLE powerdnsadmin WITH PASSWORD 'powerdnsadmin_password';
```
Note:
- Please change the information above (db, user, password) to fit your setup.
### Setup Remote access to database:
If your database is on a different server postgres does not allow remote connections by default.
To change this follow the below directions:
```
[root@host ~]$ sudo su - postgres
# Edit /var/lib/pgsql/data/postgresql.conf
# Change the following line:
listen_addresses = 'localhost'
# to:
listen_addresses = '*'
# Edit /var/lib/pgsql/data/pg_hba.conf
# Add the following lines to the end of the
host all all 0.0.0.0/0 md5
host all all ::/0 md5
[postgres@host ~]$ exit
[root@host ~]$ sudo systemctl restart postgresql
```
On debian based systems these files are located in:
```
/etc/postgresql/<version>/main/
```
## Install required packages:
### Red-hat based systems:
TODO: confirm this is correct
```
sudo yum install postgresql-libs
```
### Debian based systems:
```
apt install python3-psycopg2
```
## Known Issues:
** To fill in **
## Docker (TODO: to move to docker docs)
TODO: Setup a local Docker postgres database ready to go (should probably move to the top).
```
docker run --name pdnsadmin-test -e BIND_ADDRESS=0.0.0.0
-e SECRET_KEY='a-very-secret-key'
-e PORT='9191'
-e SQLA_DB_USER='powerdns_admin_user'
-e SQLA_DB_PASSWORD='exceptionallysecure'
-e SQLA_DB_HOST='192.168.0.100'
-e SQLA_DB_NAME='powerdns_admin_test'
-v /data/node_modules:/var/www/powerdns-admin/node_modules -d -p 9191:9191 ixpict/powerdns-admin-pgsql:latest
```

@ -0,0 +1,61 @@
This discribes how to debug the buildprocess
docker-compose.yml
```
version: "3"
services:
app:
image: powerdns/custom
container_name: powerdns
restart: always
build:
context: git
dockerfile: docker/Dockerfile
network_mode: "host"
logging:
driver: json-file
options:
max-size: 50m
environment:
- BIND_ADDRESS=127.0.0.1:8082
- SECRET_KEY='VerySecret'
- SQLALCHEMY_DATABASE_URI=mysql://pdnsadminuser:password@127.0.0.1/powerdnsadmin
- GUNICORN_TIMEOUT=60
- GUNICORN_WORKERS=2
- GUNICORN_LOGLEVEL=DEBUG
- OFFLINE_MODE=False
- CSRF_COOKIE_SECURE=False
```
Create a git folder in the location of the `docker-compose.yml` and clone the repo into it
```
mkdir git
cd git
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git .
```
In case you are behind an SSL Filter like me, you can add the following to each stage of the `git/docker/Dockerfile`
This installs the command `update-ca-certificates` from the alpine repo and adds an ssl cert to the trust chain, make sure you are getting the right version in case the base image version changes
```
RUN mkdir /tmp-pkg && cd /tmp-pkg && wget http://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/ca-certificates-20220614-r4.apk && apk add --allow-untrusted --no-network --no-cache /tmp-pkg/ca-certificates-20220614-r4.apk || true
RUN rm -rf /tmp/pkg
COPY MyCustomCerts.crt /usr/local/share/ca-certificates/MyCustomCerts.crt
RUN update-ca-certificates
COPY pip.conf /etc/pip.conf
```
`MyCustomCerts.crt` and `pip.conf` have to be placed inside the `git` folder.
The content of `pip.conf` is:
```
[global]
cert = /usr/local/share/ca-certificates/MyCustomCerts.crt
```
For easier debugging you can change the `CMD` of the `Dockerfile` to `CMD ["tail","-f", "/dev/null"]` though I expect you to be fluent in Docker in case you wish to debug

@ -0,0 +1,16 @@
Usage:
IPv4: http://user:pass@yournameserver.yoursite.tld/nic/update?hostname=record.domain.tld&myip=127.0.0.1
IPv6: http://user:pass@yournameserver.yoursite.tld/nic/update?hostname=record.domain.tld&myip=::1
Multiple IPs: http://user:pass@yournameserver.yoursite.tld/nic/update?hostname=record.domain.tld&myip=127.0.0.1,127.0.0.2,::1,::2
Notes:
- user needs to be a LOCAL user, not LDAP etc
- user must have already logged-in
- user needs to be added to Domain Access Control list of domain.tld - admin status (manage all) does not suffice
- record has to exist already - unless on-demand creation is allowed
- ipv4 address in myip field will change A record
- ipv6 address in myip field will change AAAA record
- use commas to separate multiple IP addresses in the myip field, mixing v4 & v6 is allowed
DynDNS also works without authentication header (user:pass@) when already authenticated via session cookie from /login, even with external auth like LDAP.
However Domain Access Control restriction still applies.

Binary file not shown.

After

(image error) Size: 69 KiB

Binary file not shown.

After

(image error) Size: 113 KiB

Binary file not shown.

After

(image error) Size: 60 KiB

Binary file not shown.

After

(image error) Size: 15 KiB

Binary file not shown.

After

(image error) Size: 32 KiB

Binary file not shown.

After

(image error) Size: 27 KiB

Binary file not shown.

After

(image error) Size: 27 KiB

Binary file not shown.

After

(image error) Size: 8.4 KiB

@ -0,0 +1,32 @@
# General installation
## PowerDNS-Admin Architecture
![PowerDNS-Admin Component Layout](Architecture.png)
A PowerDNS-Admin installation includes four main components:
- PowerDNS-Admin Database
- PowerDNS-Admin Application Server
- PowerDNS-Admin Frontend Web server
- PowerDNS server that
All 3 components can be installed on one server or if your installation is large enough or for security reasons can be split across multiple servers.
## Requirements for PowerDNS-Admin:
- A linux based system. Others (Arch-based for example) may work but are currently not tested.
- Ubuntu versions tested:
- To fill in
- Red hat versions tested:
- To fill in
- Python versions tested:
- 3.6
- 3.7
- 3.8
- 3.9
- 3.10
- 3.11 - Failing due to issue with python3-saml later than 1.12.0
- A database for PowerDNS-Admin, if you are using a database for PowerDNS itself this must be separate to that database. The currently supported databases are:
- MySQL
- PostgreSQL
- SQLite
- A PowerDNS server that PowerDNS-Admin will manage.

@ -0,0 +1,72 @@
***
**WARNING**
This just uses the development server for testing purposes. For production environments you should probably go with a more robust solution, like [gunicorn](web-server/Running-PowerDNS-Admin-with-Systemd,-Gunicorn--and--Nginx.md) or a WSGI server.
***
### Following example shows a systemd unit file that can run PowerDNS-Admin
You shouldn't run PowerDNS-Admin as _root_, so let's start of with the user/group creation that will later run PowerDNS-Admin:
Create a new group for PowerDNS-Admin:
> sudo groupadd powerdnsadmin
Create a user for PowerDNS-Admin:
> sudo useradd --system -g powerdnsadmin powerdnsadmin
_`--system` creates a user without login-shell and password, suitable for running system services._
Create new systemd service file:
> sudo vim /etc/systemd/system/powerdns-admin.service
General example:
```
[Unit]
Description=PowerDNS-Admin
After=network.target
[Service]
Type=simple
User=powerdnsadmin
Group=powerdnsadmin
ExecStart=/opt/web/powerdns-admin/flask/bin/python ./run.py
WorkingDirectory=/opt/web/powerdns-admin
Restart=always
[Install]
WantedBy=multi-user.target
```
Debian example:
```
[Unit]
Description=PowerDNS-Admin
After=network.target
[Service]
Type=simple
User=powerdnsadmin
Group=powerdnsadmin
Environment=PATH=/opt/web/powerdns-admin/flask/bin
ExecStart=/opt/web/powerdns-admin/flask/bin/python /opt/web/powerdns-admin/run.py
WorkingDirectory=/opt/web/powerdns-admin
Restart=always
[Install]
WantedBy=multi-user.target
```
Before starting the service, we need to make sure that the new user can work on the files in the PowerDNS-Admin folder:
> chown -R powerdnsadmin:powerdnsadmin /opt/web/powerdns-admin
After saving the file, we need to reload the systemd daemon:
> sudo systemctl daemon-reload
We can now try to start the service:
> sudo systemctl start powerdns-admin
If you would like to start PowerDNS-Admin automagically at startup enable the service:
> systemctl enable powerdns-admin
Should the service not be up by now, consult your syslog. Generally this will be a file permission issue, or python not finding it's modules. See the Debian unit example to see how you can use systemd in a python `virtualenv`

@ -0,0 +1,83 @@
# Installing PowerDNS-Admin on CentOS 7
```
NOTE: If you are logged in as User and not root, add "sudo", or get root by sudo -i.
```
## Install required packages:
### Install needed repositories:
```
yum install epel-release
yum install https://repo.ius.io/ius-release-el7.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
```
### Install Python 3.6 and tools:
First remove python 3.4 if installed
```
yum remove python34*
yum autoremove
```
```
yum install python3 python3-devel python3-pip
pip3.6 install -U pip
pip install -U virtualenv
```
### Install required packages for building python libraries from requirements.txt file:
```
yum install gcc openldap-devel xmlsec1-devel xmlsec1-openssl libtool-ltdl-devel
```
### Install yarn to build asset files + Nodejs 14:
```
curl -sL https://rpm.nodesource.com/setup_14.x | bash -
curl -sL https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
yum install yarn
```
### Checkout source code and create virtualenv:
NOTE: Please adjust `/opt/web/powerdns-admin` to your local web application directory
```
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git /opt/web/powerdns-admin
cd /opt/web/powerdns-admin
virtualenv -p python3 flask
```
Activate your python3 environment and install libraries:
```
. ./flask/bin/activate
pip install python-dotenv
pip install -r requirements.txt
```
## Running PowerDNS-Admin:
NOTE: The default config file is located at `./powerdnsadmin/default_config.py`. If you want to load another one, please set the `FLASK_CONF` environment variable. E.g.
```bash
export FLASK_CONF=../configs/development.py
```
### Create the database schema:
```
export FLASK_APP=powerdnsadmin/__init__.py
flask db upgrade
```
**Also, we should generate asset files:**
```
yarn install --pure-lockfile
flask assets build
```
**Now you can run PowerDNS-Admin by command:**
```
./run.py
```
Open your web browser and access to `http://localhost:9191` to visit PowerDNS-Admin web interface. Register an user. The first user will be in Administrator role.
At the first time you login into the PDA UI, you will be redirected to setting page to configure the PDNS API information.
_**Note:**_ For production environment, i would recommend you to run PowerDNS-Admin with gunicorn or uwsgi instead of flask's built-in web server, take a look at WIKI page to see how to configure them.

@ -0,0 +1,14 @@
# Installation on docker
The Docker image is powerdnsadmin/pda-legacy available on [DockerHub](https://hub.docker.com/r/powerdnsadmin/pda-legacy)
The supported environment variables to configure the container are located [here](../configuration/Environment-variables.md).
You can run the container and expose the web server on port 9191 using:
```bash
docker run -d \
-e SECRET_KEY='a-very-secret-key' \
-v pda-data:/data \
-p 9191:80 \
powerdnsadmin/pda-legacy:latest
```

@ -0,0 +1 @@
Please refer to CentOS guide: [Running-PowerDNS-Admin-on-Centos-7](Running-PowerDNS-Admin-on-Centos-7.md)

@ -0,0 +1,82 @@
```
NOTE: If you are logged in as User and not root, add "sudo", or get root by sudo -i.
Normally under centos you are anyway mostly root.
```
<br>
## Install required packages
**Install Python and requirements**
```bash
dnf install python37 python3-devel python3-pip
```
**Install Backend and Environment prerequisites**
```bash
dnf install mariadb-devel mariadb-common openldap-devel xmlsec1-devel xmlsec1-openssl libtool-ltdl-devel
```
**Install Development tools**
```bash
dnf install gcc gc make
```
**Install PIP**
```bash
pip3.7 install -U pip
```
**Install Virtual Environment**
```bash
pip install -U virtualenv
```
**Install Yarn for building NodeJS asset files:**
```bash
dnf install npm
npm install yarn -g
```
## Clone the PowerDNS-Admin repository to the installation path:
```bash
cd /opt/web/
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git powerdns-admin
```
**Prepare the Virtual Environment:**
```bash
cd /opt/web/powerdns-admin
virtualenv -p python3 flask
```
**Activate the Python Environment and install libraries**
```bash
. ./flask/bin/activate
pip install python-dotenv
pip install -r requirements.txt
```
## Running PowerDNS-Admin
NOTE: The default config file is located at `./powerdnsadmin/default_config.py`. If you want to load another one, please set the `FLASK_CONF` environment variable. E.g.
```bash
export FLASK_CONF=../configs/development.py
```
**Then create the database schema by running:**
```
(flask) [khanh@localhost powerdns-admin] export FLASK_APP=powerdnsadmin/__init__.py
(flask) [khanh@localhost powerdns-admin] flask db upgrade
```
**Also, we should generate asset files:**
```
(flask) [khanh@localhost powerdns-admin] yarn install --pure-lockfile
(flask) [khanh@localhost powerdns-admin] flask assets build
```
**Now you can run PowerDNS-Admin by command:**
```
(flask) [khanh@localhost powerdns-admin] ./run.py
```
Open your web browser and access to `http://localhost:9191` to visit PowerDNS-Admin web interface. Register an user. The first user will be in Administrator role.
At the first time you login into the PDA UI, you will be redirected to setting page to configure the PDNS API information.
_**Note:**_ For production environment, i recommend to run PowerDNS-Admin with WSGI over Apache instead of flask's built-in web server...
Take a look at [WSGI Apache Example](web-server/WSGI-Apache-example#fedora) WIKI page to see how to configure it.

@ -0,0 +1,90 @@
# Installing PowerDNS-Admin on Ubuntu or Debian based systems
First setup your database accordingly:
[Database Setup](../database-setup/README.md)
## Install required packages:
### Install required packages for building python libraries from requirements.txt file
For Debian 11 (bullseye) and above:
```bash
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
```bash
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
sudo apt install -y nodejs
```
### 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
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
sudo apt update -y
sudo apt install -y yarn
```
### Checkout source code and create virtualenv
_**Note:**_ Please adjust `/opt/web/powerdns-admin` to your local web application directory
```bash
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git /opt/web/powerdns-admin
cd /opt/web/powerdns-admin
python3 -mvenv ./venv
```
Activate your python3 environment and install libraries:
```bash
source ./venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```
## Running PowerDNS-Admin
Create PowerDNS-Admin config file and make the changes necessary for your use case. Make sure to change `SECRET_KEY` to a long random string that you generated yourself ([see Flask docs](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY)), do not use the pre-defined one. E.g.:
```bash
cp /opt/web/powerdns-admin/configs/development.py /opt/web/powerdns-admin/configs/production.py
vim /opt/web/powerdns-admin/configs/production.py
export FLASK_CONF=../configs/production.py
```
Do the DB migration
```bash
export FLASK_APP=powerdnsadmin/__init__.py
flask db upgrade
```
Then generate asset files
```bash
yarn install --pure-lockfile
flask assets build
```
Now you can run PowerDNS-Admin by command
```bash
./run.py
```
This is good for testing, but for production usage, you should use gunicorn or uwsgi. See [Running PowerDNS Admin with Systemd, Gunicorn and Nginx](../web-server/Running-PowerDNS-Admin-with-Systemd-Gunicorn-and-Nginx.md) for instructions.
From here you can now follow the [Getting started guide](../configuration/Getting-started.md).

@ -0,0 +1,102 @@
On [FreeBSD](https://www.freebsd.org/), most software is installed using `pkg`. You can always build from source with the Ports system. This method uses as many binary ports as possible, and builds some python packages from source. It installs all the required runtimes in the global system (e.g., python, node, yarn) and then builds a virtual python environment in `/opt/python`. Likewise, it installs powerdns-admin in `/opt/powerdns-admin`.
### Build an area to host files
```bash
mkdir -p /opt/python
```
### Install prerequisite runtimes: python, node, yarn
```bash
sudo pkg install git python3 curl node12 yarn-node12
sudo pkg install libxml2 libxslt pkgconf py37-xmlsec py37-cffi py37-ldap
```
## Check Out Source Code
_**Note:**_ Please adjust `/opt/powerdns-admin` to your local web application directory
```bash
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git /opt/powerdns-admin
cd /opt/powerdns-admin
```
## Make Virtual Python Environment
Make a virtual environment for python. Activate your python3 environment and install libraries. It's easier to install some python libraries as system packages, so we add the `--system-site-packages` option to pull those in.
> Note: I couldn't get `python-ldap` to install correctly, and I don't need it. I commented out the `python-ldap` line in `requirements.txt` and it all built and installed correctly. If you don't intend to use LDAP authentication, you'll be fine. If you need LDAP authentication, it probably won't work.
```bash
python3 -m venv /web/python --system-site-packages
source /web/python/bin/activate
/web/python/bin/python3 -m pip install --upgrade pip wheel
# this command comments out python-ldap
perl -pi -e 's,^python-ldap,\# python-ldap,' requirements.txt
pip3 install -r requirements.txt
```
## Configuring PowerDNS-Admin
NOTE: The default config file is located at `./powerdnsadmin/default_config.py`. If you want to load another one, please set the `FLASK_CONF` environment variable. E.g.
```bash
cp configs/development.py /opt/powerdns-admin/production.py
export FLASK_CONF=/opt/powerdns-admin/production.py
```
### Update the Flask config
Edit your flask python configuration. Insert values for the database server, user name, password, etc.
```bash
vim $FLASK_CONF
```
Edit the values below to something sensible
```python
### BASIC APP CONFIG
SALT = '[something]'
SECRET_KEY = '[something]'
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
OFFLINE_MODE = False
### DATABASE CONFIG
SQLA_DB_USER = 'pda'
SQLA_DB_PASSWORD = 'changeme'
SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'pda'
SQLALCHEMY_TRACK_MODIFICATIONS = True
```
Be sure to uncomment one of the lines like `SQLALCHEMY_DATABASE_URI`.
### Initialise the database
```bash
export FLASK_APP=powerdnsadmin/__init__.py
flask db upgrade
```
### Build web assets
```bash
yarn install --pure-lockfile
flask assets build
```
## Running PowerDNS-Admin
Now you can run PowerDNS-Admin by command
```bash
./run.py
```
Open your web browser and go to `http://localhost:9191` to visit PowerDNS-Admin web interface. Register a user. The first user will be in the Administrator role.
### Running at startup
This is good for testing, but for production usage, you should use gunicorn or uwsgi. See [Running PowerDNS Admin with Systemd, Gunicorn and Nginx](../web-server/Running-PowerDNS-Admin-with-Systemd,-Gunicorn--and--Nginx.md) for instructions.
The right approach long-term is to create a startup script in `/usr/local/etc/rc.d` and enable it through `/etc/rc.conf`.

@ -0,0 +1,73 @@
This describes how to run Apache2 on the host system with a reverse proxy directing to the docker container
This is usually used to add ssl certificates and prepend a subdirectory
The network_mode host settings is not neccessary but used for ldap availability in this case
docker-compose.yml
```
version: "3"
services:
app:
image: powerdnsadmin/pda-legacy:latest
container_name: powerdns
restart: always
network_mode: "host"
logging:
driver: json-file
options:
max-size: 50m
environment:
- BIND_ADDRESS=127.0.0.1:8082
- SECRET_KEY='NotVerySecret'
- SQLALCHEMY_DATABASE_URI=mysql://pdnsadminuser:password@127.0.0.1/powerdnsadmin
- GUNICORN_TIMEOUT=60
- GUNICORN_WORKERS=2
- GUNICORN_LOGLEVEL=DEBUG
- OFFLINE_MODE=False
- CSRF_COOKIE_SECURE=False
- SCRIPT_NAME=/powerdns
```
After running the Container create the static directory and populate
```
mkdir -p /var/www/powerdns
docker cp powerdns:/app/powerdnsadmin/static /var/www/powerdns/
chown -R root:www-data /var/www/powerdns
```
Adjust the static reference, static/assets/css has a hardcoded reference
```
sed -i 's/\/static/\/powerdns\/static/' /var/www/powerdns/static/assets/css/*
```
Apache Config:
You can set the SCRIPT_NAME environment using Apache as well, once is sufficient though
```
<Location /powerdns>
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader set SCRIPT_NAME "/powerdns"
ProxyPreserveHost On
</Location>
ProxyPass /powerdns/static !
ProxyPass /powerdns http://127.0.0.1:8082/powerdns
ProxyPassReverse /powerdns http://127.0.0.1:8082/powerdns
Alias /powerdns/static "/var/www/powerdns/static"
<Directory "/var/www/powerdns/static">
Options None
#Options +Indexes
AllowOverride None
Order allow,deny
Allow from all
</Directory>
```

@ -0,0 +1,97 @@
Following is an example showing how to run PowerDNS-Admin with systemd, gunicorn and Apache:
The systemd and gunicorn setup are the same as for with nginx. This set of configurations assumes you have installed your PowerDNS-Admin under /opt/powerdns-admin and are running with a package-installed gunicorn.
## Configure systemd service
`$ sudo vim /etc/systemd/system/powerdns-admin.service`
```
[Unit]
Description=PowerDNS web administration service
Requires=powerdns-admin.socket
Wants=network.target
After=network.target mysqld.service postgresql.service slapd.service mariadb.service
[Service]
PIDFile=/run/powerdns-admin/pid
User=pdnsa
Group=pdnsa
WorkingDirectory=/opt/powerdns-admin
ExecStart=/usr/bin/gunicorn-3.6 --workers 4 --log-level info --pid /run/powerdns-admin/pid --bind unix:/run/powerdns-admin/socket "powerdnsadmin:create_app(config='config.py')"
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
Restart=on-failure
RestartSec=10
StartLimitInterval=0
[Install]
WantedBy=multi-user.target
```
`$ sudo vim /etc/systemd/system/powerdns-admin.socket`
```
[Unit]
Description=PowerDNS-Admin socket
[Socket]
ListenStream=/run/powerdns-admin/socket
[Install]
WantedBy=sockets.target
```
`$ sudo vim /etc/tmpfiles.d/powerdns-admin.conf`
```
d /run/powerdns-admin 0755 pdnsa pdnsa -
```
Then `sudo systemctl daemon-reload; sudo systemctl start powerdns-admin.socket; sudo systemctl enable powerdns-admin.socket` to start the Powerdns-Admin service and make it run on boot.
## Sample Apache configuration
This includes SSL redirect.
```
<VirtualHost *:80>
ServerName dnsadmin.company.com
DocumentRoot "/opt/powerdns-admin"
<Directory "/opt/powerdns-admin">
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
Redirect permanent / https://dnsadmin.company.com/
</VirtualHost>
<VirtualHost *:443>
ServerName dnsadmin.company.com
DocumentRoot "/opt/powerdns-admin/powerdnsadmin"
## Alias declarations for resources outside the DocumentRoot
Alias /static/ "/opt/powerdns-admin/powerdnsadmin/static/"
Alias /favicon.ico "/opt/powerdns-admin/powerdnsadmin/static/favicon.ico"
<Directory "/opt/powerdns-admin">
AllowOverride None
Require all granted
</Directory>
## Proxy rules
ProxyRequests Off
ProxyPreserveHost On
ProxyPass /static/ !
ProxyPass /favicon.ico !
ProxyPass / unix:/var/run/powerdns-admin/socket|http://%{HTTP_HOST}/
ProxyPassReverse / unix:/var/run/powerdns-admin/socket|http://%{HTTP_HOST}/
## SSL directives
SSLEngine on
SSLCertificateFile "/etc/pki/tls/certs/dnsadmin.company.com.crt"
SSLCertificateKeyFile "/etc/pki/tls/private/dnsadmin.company.com.key"
</VirtualHost>
```
## Notes
* The above assumes your installation is under /opt/powerdns-admin
* The hostname is assumed as dnsadmin.company.com
* gunicorn is installed in /usr/bin via a package (as in the case with CentOS/Redhat 7) and you have Python 3.6 installed. If you prefer to use flask then see the systemd configuration for nginx.
* On Ubuntu / Debian systems, you may need to enable the "proxy_http" module with `a2enmod proxy_http`

@ -0,0 +1,181 @@
Following is an example showing how to run PowerDNS-Admin with systemd, gunicorn and nginx:
## Configure PowerDNS-Admin
Create PowerDNS-Admin config file and make the changes necessary for your use case. Make sure to change `SECRET_KEY` to a long random string that you generated yourself ([see Flask docs](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY)), do not use the pre-defined one.
```
$ cp /opt/web/powerdns-admin/configs/development.py /opt/web/powerdns-admin/configs/production.py
$ vim /opt/web/powerdns-admin/configs/production.py
```
## Configure systemd service
`$ sudo vim /etc/systemd/system/powerdns-admin.service`
```
[Unit]
Description=PowerDNS-Admin
Requires=powerdns-admin.socket
After=network.target
[Service]
PIDFile=/run/powerdns-admin/pid
User=pdns
Group=pdns
WorkingDirectory=/opt/web/powerdns-admin
ExecStartPre=+mkdir -p /run/powerdns-admin/
ExecStartPre=+chown pdns:pdns -R /run/powerdns-admin/
ExecStart=/usr/local/bin/gunicorn --pid /run/powerdns-admin/pid --bind unix:/run/powerdns-admin/socket 'powerdnsadmin:create_app()'
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
```
`$ sudo systemctl edit powerdns-admin.service`
```
[Service]
Environment="FLASK_CONF=../configs/production.py"
```
`$ sudo vim /etc/systemd/system/powerdns-admin.socket`
```
[Unit]
Description=PowerDNS-Admin socket
[Socket]
ListenStream=/run/powerdns-admin/socket
[Install]
WantedBy=sockets.target
```
`$ sudo vim /etc/tmpfiles.d/powerdns-admin.conf`
```
d /run/powerdns-admin 0755 pdns pdns -
```
Then `sudo systemctl daemon-reload; sudo systemctl start powerdns-admin.socket; sudo systemctl enable powerdns-admin.socket` to start the Powerdns-Admin service and make it run on boot.
## Sample nginx configuration
```
server {
listen *:80;
server_name powerdns-admin.local www.powerdns-admin.local;
index index.html index.htm index.php;
root /opt/web/powerdns-admin;
access_log /var/log/nginx/powerdns-admin.local.access.log combined;
error_log /var/log/nginx/powerdns-admin.local.error.log;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_redirect off;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
proxy_buffer_size 8k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_headers_hash_bucket_size 64;
location ~ ^/static/ {
include /etc/nginx/mime.types;
root /opt/web/powerdns-admin/powerdnsadmin;
location ~* \.(jpg|jpeg|png|gif)$ {
expires 365d;
}
location ~* ^.+.(css|js)$ {
expires 7d;
}
}
location / {
proxy_pass http://unix:/run/powerdns-admin/socket;
proxy_read_timeout 120;
proxy_connect_timeout 120;
proxy_redirect off;
}
}
```
<details>
<summary>Sample Nginx-Configuration for SSL</summary>
* Im binding this config to every dns-name with default_server...
* but you can remove it and set your server_name.
```
server {
listen 80 default_server;
server_name "";
return 301 https://$http_host$request_uri;
}
server {
listen 443 ssl http2 default_server;
server_name _;
index index.html index.htm;
error_log /var/log/nginx/error_powerdnsadmin.log error;
access_log off;
ssl_certificate path_to_your_fullchain_or_cert;
ssl_certificate_key path_to_your_key;
ssl_dhparam path_to_your_dhparam.pem;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_session_cache shared:SSL:10m;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_redirect off;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
proxy_buffer_size 8k;
proxy_set_header Host $http_host;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_headers_hash_bucket_size 64;
location ~ ^/static/ {
include mime.types;
root /opt/web/powerdns-admin/powerdnsadmin;
location ~* \.(jpg|jpeg|png|gif)$ { expires 365d; }
location ~* ^.+.(css|js)$ { expires 7d; }
}
location ~ ^/upload/ {
include mime.types;
root /opt/web/powerdns-admin;
location ~* \.(jpg|jpeg|png|gif)$ { expires 365d; }
location ~* ^.+.(css|js)$ { expires 7d; }
}
location / {
proxy_pass http://unix:/run/powerdns-admin/socket;
proxy_read_timeout 120;
proxy_connect_timeout 120;
proxy_redirect http:// $scheme://;
}
}
```
</details>
## Note
* `/opt/web/powerdns-admin` is the path to your powerdns-admin web directory
* Make sure you have installed gunicorn in flask virtualenv already.
* `powerdns-admin.local` just an example of your web domain name.

@ -0,0 +1,18 @@
Following is an example showing how to run PowerDNS-Admin with supervisord
Create supervisord program config file
```
$ sudo vim /etc/supervisor.d/powerdnsadmin.conf
```
```
[program:powerdnsadmin]
command=/opt/web/powerdns-admin/flask/bin/python ./run.py
stdout_logfile=/var/log/supervisor/program_powerdnsadmin.log
stderr_logfile=/var/log/supervisor/program_powerdnsadmin.error
autostart=true
autorestart=true
directory=/opt/web/powerdns-admin
```
Then `sudo supervisorctl start powerdnsadmin` to start the Powerdns-Admin service.

@ -0,0 +1,50 @@
## Configure systemd service
This example uses package-installed gunicorn (instead of flask-installed) and PowerDNS-Admin installed under /opt/powerdns-admin
`$ sudo vim /etc/systemd/system/powerdns-admin.service`
```
[Unit]
Description=PowerDNS web administration service
Requires=powerdns-admin.socket
Wants=network.target
After=network.target mysqld.service postgresql.service slapd.service mariadb.service
[Service]
PIDFile=/run/powerdns-admin/pid
User=pdnsa
Group=pdnsa
WorkingDirectory=/opt/powerdns-admin
ExecStart=/usr/bin/gunicorn-3.6 --workers 4 --log-level info --pid /run/powerdns-admin/pid --bind unix:/run/powerdns-admin/socket "powerdnsadmin:create_app(config='config.py')"
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
Restart=on-failure
RestartSec=10
StartLimitInterval=0
[Install]
WantedBy=multi-user.target
```
`$ sudo vim /etc/systemd/system/powerdns-admin.socket`
```
[Unit]
Description=PowerDNS-Admin socket
[Socket]
ListenStream=/run/powerdns-admin/socket
[Install]
WantedBy=sockets.target
```
`$ sudo vim /etc/tmpfiles.d/powerdns-admin.conf`
```
d /run/powerdns-admin 0755 pdns pdns -
```
Then `sudo systemctl daemon-reload; sudo systemctl start powerdns-admin.socket; sudo systemctl enable powerdns-admin.socket` to start the Powerdns-Admin service and make it run on boot.

@ -0,0 +1,100 @@
How to run PowerDNS-Admin via WSGI and Apache2.4 using mod_wsgi.
**Note**: You must install mod_wsgi by using pip3 instead of system default mod_wsgi!!!
### Ubuntu/Debian
```shell
# apt install apache2-dev
# virtualenv -p python3 flask
# source ./flask/bin/activate
(flask) # pip3 install mod-wsgi
(flask) # mod_wsgi-express install-module > /etc/apache2/mods-available/wsgi.load
(flask) # a2enmod wsgi
(flask) # systemctl restart apache2
```
### CentOS
```shell
# yum install httpd-devel
# virtualenv -p python3 flask
# source ./flask/bin/activate
(flask) # pip3 install mod-wsgi
(flask) # mod_wsgi-express install-module > /etc/httpd/conf.modules.d/02-wsgi.conf
(flask) # systemctl restart httpd
```
### Fedora
```bash
# Install Apache's Development interfaces and package requirements
dnf install httpd-devel gcc gc make
virtualenv -p python3 flask
source ./flask/bin/activate
# Install WSGI for HTTPD
pip install mod_wsgi-httpd
# Install WSGI
pip install mod-wsgi
# Enable the module in Apache:
mod_wsgi-express install-module > /etc/httpd/conf.modules.d/02-wsgi.conf
systemctl restart httpd
```
Apache vhost configuration;
```apache
<VirtualHost *:443>
ServerName superawesomedns.foo.bar
ServerAlias [fe80::1]
ServerAdmin webmaster@foo.bar
SSLEngine On
SSLCertificateFile /some/path/ssl/certs/cert.pem
SSLCertificateKeyFile /some/path/ssl/private/cert.key
ErrorLog /var/log/apache2/error-superawesomedns.foo.bar.log
CustomLog /var/log/apache2/access-superawesomedns.foo.bar.log combined
DocumentRoot /srv/vhosts/superawesomedns.foo.bar/
WSGIDaemonProcess pdnsadmin user=pdnsadmin group=pdnsadmin threads=5
WSGIScriptAlias / /srv/vhosts/superawesomedns.foo.bar/powerdnsadmin.wsgi
# pass BasicAuth on to the WSGI process
WSGIPassAuthorization On
<Directory "/srv/vhosts/superawesomedns.foo.bar/">
WSGIProcessGroup pdnsadmin
WSGIApplicationGroup %{GLOBAL}
AllowOverride None
Options +ExecCGI +FollowSymLinks
SSLRequireSSL
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
```
**In Fedora, you might want to change the following line:**
```apache
WSGIDaemonProcess pdnsadmin socket-user=apache user=pdnsadmin group=pdnsadmin threads=5
```
**And you should add the following line to `/etc/httpd/conf/httpd.conf`:**
```apache
WSGISocketPrefix /var/run/wsgi
```
Content of `/srv/vhosts/superawesomedns.foo.bar/powerdnsadmin.wsgi`;
```python
#!/usr/bin/env python3
import sys
sys.path.insert(0, '/srv/vhosts/superawesomedns.foo.bar')
from app import app as application
```
Starting from 0.2 version, the `powerdnsadmin.wsgi` file is slighty different :
```python
#!/usr/bin/env python3
import sys
sys.path.insert(0, '/srv/vhosts/superawesomedns.foo.bar')
from powerdnsadmin import create_app
application = create_app()
```
(this implies that the pdnsadmin user/group exists, and that you have mod_wsgi loaded)

@ -0,0 +1,56 @@
# uWSGI Example
This guide will show you how to run PowerDNS-Admin via uWSGI and nginx. This guide was written using Debian 8 with the following software versions:
- nginx 1.6.2
- uwsgi 2.0.7-debian
- python 2.7.9
## Software installation:
1. apt install the following packages:
- `uwsgi`
- `uwsgi-plugin-python`
- `nginx`
## Step-by-step instructions
1. Create a uWSGI .ini in `/etc/uwsgi/apps-enabled` with the following contents, making sure to replace the chdir, pythonpath and virtualenv directories with where you've installed PowerDNS-Admin:
```ini
[uwsgi]
plugins = python27
uid=www-data
gid=www-data
chdir = /opt/pdns-admin/PowerDNS-Admin/
pythonpath = /opt/pdns-admin/PowerDNS-Admin/
virtualenv = /opt/pdns-admin/PowerDNS-Admin/flask
mount = /pdns=powerdnsadmin:create_app()
manage-script-name = true
vacuum = true
harakiri = 20
buffer-size = 32768
post-buffering = 8192
socket = /run/uwsgi/app/%n/%n.socket
chown-socket = www-data
pidfile = /run/uwsgi/app/%n/%n.pid
daemonize = /var/log/uwsgi/app/%n.log
enable-threads
```
2. Add the following configuration to your nginx config:
```nginx
location / { try_files $uri @pdns_admin; }
location @pdns_admin {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/app/pdns-admin/pdns-admin.socket;
}
location /pdns/static/ {
alias /opt/pdns-admin/PowerDNS-Admin/app/static/;
}
```
3. Restart nginx and uwsgi.
4. You're done and PowerDNS-Admin will now be available via nginx.

@ -19,7 +19,7 @@ logger = logging.getLogger('alembic.env')
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
current_app.config.get('SQLALCHEMY_DATABASE_URI').replace("%","%%"))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,

@ -0,0 +1,41 @@
"""add apikey account mapping table
Revision ID: 0967658d9c0d
Revises: 0d3d93f1c2e0
Create Date: 2021-11-13 22:28:46.133474
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0967658d9c0d'
down_revision = '0d3d93f1c2e0'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('apikey_account',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('apikey_id', sa.Integer(), nullable=False),
sa.Column('account_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['account_id'], ['account.id'], ),
sa.ForeignKeyConstraint(['apikey_id'], ['apikey.id'], ),
sa.PrimaryKeyConstraint('id')
)
with op.batch_alter_table('history', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_history_created_on'), ['created_on'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('history', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_history_created_on'))
op.drop_table('apikey_account')
# ### end Alembic commands ###

@ -0,0 +1,34 @@
"""Add domain_id to history table
Revision ID: 0d3d93f1c2e0
Revises: 3f76448bb6de
Create Date: 2021-02-15 17:23:05.688241
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0d3d93f1c2e0'
down_revision = '3f76448bb6de'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('history', schema=None) as batch_op:
batch_op.add_column(sa.Column('domain_id', sa.Integer(), nullable=True))
batch_op.create_foreign_key('fk_domain_id', 'domain', ['domain_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('history', schema=None) as batch_op:
batch_op.drop_constraint('fk_domain_id', type_='foreignkey')
batch_op.drop_column('domain_id')
# ### end Alembic commands ###

@ -18,8 +18,12 @@ depends_on = None
def upgrade():
with op.batch_alter_table('user') as batch_op:
batch_op.add_column(
sa.Column('confirmed', sa.Boolean(), nullable=False,
sa.Column('confirmed', sa.Boolean(), nullable=True,
default=False))
with op.batch_alter_table('user') as batch_op:
user = sa.sql.table('user', sa.sql.column('confirmed'))
batch_op.execute(user.update().values(confirmed=False))
batch_op.alter_column('confirmed', nullable=False, existing_type=sa.Boolean(), existing_nullable=True, existing_server_default=False)
def downgrade():

@ -0,0 +1,46 @@
"""Fix typo in history detail
Revision ID: 6ea7dc05f496
Revises: fbc7cf864b24
Create Date: 2022-05-10 10:16:58.784497
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6ea7dc05f496'
down_revision = 'fbc7cf864b24'
branch_labels = None
depends_on = None
history_table = sa.sql.table('history',
sa.Column('detail', sa.Text),
)
def upgrade():
op.execute(
history_table.update()
.where(history_table.c.detail.like('%"add_rrests":%'))
.values({
'detail': sa.func.replace(
sa.func.replace(history_table.c.detail, '"add_rrests":', '"add_rrsets":'),
'"del_rrests":', '"del_rrsets":'
)
})
)
def downgrade():
op.execute(
history_table.update()
.where(history_table.c.detail.like('%"add_rrsets":%'))
.values({
'detail': sa.func.replace(
sa.func.replace(history_table.c.detail, '"add_rrsets":', '"add_rrests":'),
'"del_rrsets":', '"del_rrests":'
)
})
)

@ -56,9 +56,9 @@ def seed_data():
op.bulk_insert(template_table,
[
{id: 1, 'name': 'basic_template_1', 'description': 'Basic Template #1'},
{id: 2, 'name': 'basic_template_2', 'description': 'Basic Template #2'},
{id: 3, 'name': 'basic_template_3', 'description': 'Basic Template #3'}
{'id': 1, 'name': 'basic_template_1', 'description': 'Basic Template #1'},
{'id': 2, 'name': 'basic_template_2', 'description': 'Basic Template #2'},
{'id': 3, 'name': 'basic_template_3', 'description': 'Basic Template #3'}
]
)

@ -0,0 +1,24 @@
"""Add unique index to settings table keys
Revision ID: b24bf17725d2
Revises: f41520e41cee
Create Date: 2023-02-18 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b24bf17725d2'
down_revision = 'f41520e41cee'
branch_labels = None
depends_on = None
def upgrade():
op.create_index(op.f('ix_setting_name'), 'setting', ['name'], unique=True)
def downgrade():
op.drop_index(op.f('ix_setting_name'), table_name='setting')

@ -0,0 +1,31 @@
"""update domain type length
Revision ID: f41520e41cee
Revises: 6ea7dc05f496
Create Date: 2023-01-10 11:56:28.538485
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f41520e41cee'
down_revision = '6ea7dc05f496'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('domain') as batch_op:
batch_op.alter_column('type',
existing_type=sa.String(length=6),
type_=sa.String(length=8))
def downgrade():
with op.batch_alter_table('domain') as batch_op:
batch_op.alter_column('type',
existing_type=sa.String(length=8),
type_=sa.String(length=6))

@ -0,0 +1,47 @@
"""update history detail quotes
Revision ID: fbc7cf864b24
Revises: 0967658d9c0d
Create Date: 2022-05-04 19:49:54.054285
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fbc7cf864b24'
down_revision = '0967658d9c0d'
branch_labels = None
depends_on = None
def upgrade():
history_table = sa.sql.table(
'history',
sa.Column('id', sa.Integer),
sa.Column('msg', sa.String),
sa.Column('detail', sa.Text),
sa.Column('created_by', sa.String),
sa.Column('created_on', sa.DateTime),
sa.Column('domain_id', sa.Integer)
)
op.execute(
history_table.update().where(
sa.and_(
history_table.c.detail.like("%'%"),
history_table.c.detail.notlike("%rrests%"),
history_table.c.detail.notlike("%rrsets%")
)
).values({
'detail': sa.func.replace(
history_table.c.detail,
"'",
'"'
)
})
)
def downgrade():
pass

@ -1,14 +1,22 @@
{
"dependencies": {
"admin-lte": "2.4.9",
"bootstrap": "^3.4.1",
"@fortawesome/fontawesome-free": "6.3.0",
"admin-lte": "3.2.0",
"bootstrap": "4.6.2",
"bootstrap-datepicker": "^1.9.0",
"bootstrap-validator": "^0.11.9",
"datatables.net-plugins": "^1.10.19",
"datatables.net-plugins": "^1.13.1",
"icheck": "^1.0.2",
"jquery-slimscroll": "^1.3.8",
"jquery-ui-dist": "^1.12.1",
"jquery-sparkline": "^2.4.0",
"jquery-ui-dist": "^1.13.2",
"jquery.quicksearch": "^2.4.0",
"jtimeout": "^3.1.0",
"jquery-validation": "^1.19.5",
"jtimeout": "^3.2.0",
"knockout": "^3.5.1",
"multiselect": "^0.9.12"
},
"resolutions": {
"admin-lte/@fortawesome/fontawesome-free": "6.3.0"
}
}

@ -1,14 +1,14 @@
import os
import logging
from flask import Flask
from flask_seasurf import SeaSurf
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__)
@ -32,29 +32,6 @@ def create_app(config=None):
# Proxy
app.wsgi_app = ProxyFix(app.wsgi_app)
# CSRF protection
csrf = SeaSurf(app)
csrf.exempt(routes.index.dyndns_checkip)
csrf.exempt(routes.index.dyndns_update)
csrf.exempt(routes.index.saml_authorized)
csrf.exempt(routes.api.api_login_create_zone)
csrf.exempt(routes.api.api_login_delete_zone)
csrf.exempt(routes.api.api_generate_apikey)
csrf.exempt(routes.api.api_delete_apikey)
csrf.exempt(routes.api.api_update_apikey)
csrf.exempt(routes.api.api_zone_subpath_forward)
csrf.exempt(routes.api.api_zone_forward)
csrf.exempt(routes.api.api_create_zone)
csrf.exempt(routes.api.api_create_account)
csrf.exempt(routes.api.api_delete_account)
csrf.exempt(routes.api.api_update_account)
csrf.exempt(routes.api.api_create_user)
csrf.exempt(routes.api.api_delete_user)
csrf.exempt(routes.api.api_update_user)
csrf.exempt(routes.api.api_list_account_users)
csrf.exempt(routes.api.api_add_account_user)
csrf.exempt(routes.api.api_remove_account_user)
# Load config from env variables if using docker
if os.path.exists(os.path.join(app.root_path, 'docker_config.py')):
app.config.from_object('powerdnsadmin.docker_config')
@ -66,18 +43,32 @@ def create_app(config=None):
if 'FLASK_CONF' in os.environ:
app.config.from_envvar('FLASK_CONF')
# Load app sepecified configuration
# Load app specified configuration
if config is not None:
if isinstance(config, dict):
app.config.update(config)
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
_sslify = SSLify(app) # lgtm [py/unused-local-variable]
# Load Flask-Session
app.config['SESSION_TYPE'] = app.config.get('SESSION_TYPE')
if 'SESSION_TYPE' in os.environ:
app.config['SESSION_TYPE'] = os.environ.get('SESSION_TYPE')
sess = Session(app)
# create sessions table if using sqlalchemy backend
if os.environ.get('SESSION_TYPE') == 'sqlalchemy':
sess.app.session_interface.db.create_all()
# SMTP
app.mail = Mail(app)
@ -91,12 +82,12 @@ def create_app(config=None):
app.jinja_env.filters['display_record_name'] = utils.display_record_name
app.jinja_env.filters['display_master_name'] = utils.display_master_name
app.jinja_env.filters['display_second_to_time'] = utils.display_time
app.jinja_env.filters[
'email_to_gravatar_url'] = utils.email_to_gravatar_url
app.jinja_env.filters[
'display_setting_state'] = utils.display_setting_state
app.jinja_env.filters['display_setting_state'] = utils.display_setting_state
app.jinja_env.filters['pretty_domain_name'] = utils.pretty_domain_name
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
@ -109,9 +100,4 @@ def create_app(config=None):
setting = Setting()
return dict(SETTING=setting)
@app.context_processor
def inject_mode():
setting = app.config.get('OFFLINE_MODE', False)
return dict(OFFLINE_MODE=setting)
return app

@ -4,62 +4,65 @@ from flask_assets import Bundle, Environment, Filter
class ConcatFilter(Filter):
"""
Filter that merges files, placing a semicolon between them.
Fixes issues caused by missing semicolons at end of JS assets, for example
with last statement of jquery.pjax.js.
"""
def concat(self, out, hunks, **kw):
out.write(';'.join([h.data() for h, info in hunks]))
css_login = Bundle('node_modules/bootstrap/dist/css/bootstrap.css',
'node_modules/font-awesome/css/font-awesome.css',
'node_modules/ionicons/dist/css/ionicons.css',
'node_modules/icheck/skins/square/blue.css',
'node_modules/admin-lte/dist/css/AdminLTE.css',
filters=('cssmin', 'cssrewrite'),
output='generated/login.css')
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'),
output='generated/login.css')
js_login = Bundle('node_modules/jquery/dist/jquery.js',
'node_modules/bootstrap/dist/js/bootstrap.js',
'node_modules/icheck/icheck.js',
filters=(ConcatFilter, 'jsmin'),
output='generated/login.js')
js_login = Bundle(
'node_modules/jquery/dist/jquery.js',
'node_modules/bootstrap/dist/js/bootstrap.js',
'node_modules/icheck/icheck.js',
'node_modules/knockout/build/output/knockout-latest.js',
'custom/js/custom.js',
filters=(ConcatFilter, 'rjsmin'),
output='generated/login.js')
js_validation = Bundle('node_modules/bootstrap-validator/dist/validator.js',
output='generated/validation.js')
js_validation = Bundle(
'node_modules/bootstrap-validator/dist/validator.js',
output='generated/validation.js')
css_main = Bundle(
'node_modules/bootstrap/dist/css/bootstrap.css',
'node_modules/font-awesome/css/font-awesome.css',
'node_modules/ionicons/dist/css/ionicons.css',
'node_modules/datatables.net-bs/css/dataTables.bootstrap.css',
'node_modules/@fortawesome/fontawesome-free/css/all.css',
'node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css',
'node_modules/icheck/skins/square/blue.css',
'node_modules/multiselect/css/multi-select.css',
'node_modules/admin-lte/dist/css/AdminLTE.css',
'node_modules/admin-lte/dist/css/skins/_all-skins.css',
'node_modules/admin-lte/dist/css/adminlte.css',
'custom/css/custom.css',
filters=('cssmin', 'cssrewrite'),
'node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.css',
filters=('rcssmin', 'cssrewrite'),
output='generated/main.css')
js_main = Bundle('node_modules/jquery/dist/jquery.js',
'node_modules/jquery-ui-dist/jquery-ui.js',
'node_modules/bootstrap/dist/js/bootstrap.js',
'node_modules/datatables.net/js/jquery.dataTables.js',
'node_modules/datatables.net-bs/js/dataTables.bootstrap.js',
'node_modules/jquery-sparkline/jquery.sparkline.js',
'node_modules/jquery-slimscroll/jquery.slimscroll.js',
'node_modules/icheck/icheck.js',
'node_modules/fastclick/lib/fastclick.js',
'node_modules/moment/moment.js',
'node_modules/admin-lte/dist/js/adminlte.js',
'node_modules/multiselect/js/jquery.multi-select.js',
'node_modules/datatables.net-plugins/sorting/natural.js',
'node_modules/jtimeout/src/jTimeout.js',
'node_modules/jquery.quicksearch/src/jquery.quicksearch.js',
'custom/js/custom.js',
filters=(ConcatFilter, 'jsmin'),
output='generated/main.js')
js_main = Bundle(
'node_modules/jquery/dist/jquery.js',
'node_modules/jquery-ui-dist/jquery-ui.js',
'node_modules/bootstrap/dist/js/bootstrap.bundle.js',
'node_modules/datatables.net/js/jquery.dataTables.js',
'node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js',
'node_modules/jquery-sparkline/jquery.sparkline.js',
'node_modules/jquery-slimscroll/jquery.slimscroll.js',
'node_modules/jquery-validation/dist/jquery.validate.js',
'node_modules/icheck/icheck.js',
'node_modules/fastclick/lib/fastclick.js',
'node_modules/moment/moment.js',
'node_modules/admin-lte/dist/js/adminlte.js',
'node_modules/multiselect/js/jquery.multi-select.js',
'node_modules/datatables.net-plugins/sorting/natural.js',
'node_modules/jtimeout/src/jTimeout.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',
'node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
filters=(ConcatFilter, 'rjsmin'),
output='generated/main.js')
assets = Environment()
assets.register('js_login', js_login)

@ -1,18 +1,19 @@
import base64
import binascii
from functools import wraps
from flask import g, request, abort, current_app, render_template
from flask import g, request, abort, current_app, Response
from flask_login import current_user
from .models import User, ApiKey, Setting, Domain, Setting
from .lib.errors import RequestIsNotJSON, NotEnoughPrivileges
from .lib.errors import DomainAccessForbidden
from .lib.errors import RequestIsNotJSON, NotEnoughPrivileges, RecordTTLNotAllowed, RecordTypeNotAllowed
from .lib.errors import DomainAccessForbidden, DomainOverrideForbidden
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']:
@ -35,6 +37,22 @@ def operator_role_required(f):
return decorated_function
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 [
'Administrator', 'Operator'
] and not Setting().get('allow_user_view_history'):
abort(403)
return f(*args, **kwargs)
return decorated_function
def can_access_domain(f):
"""
Grant access if:
@ -42,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']:
@ -68,10 +87,11 @@ 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 [
'Administrator', 'Operator'
'Administrator', 'Operator'
] and Setting().get('dnssec_admins_only'):
abort(403)
@ -80,16 +100,35 @@ 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 [
'Administrator', 'Operator'
] and not Setting().get('allow_user_remove_domain'):
abort(403)
return f(*args, **kwargs)
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 [
'Administrator', 'Operator'
'Administrator', 'Operator'
] and not Setting().get('allow_user_create_domain'):
abort(403)
return f(*args, **kwargs)
@ -101,50 +140,64 @@ def api_basic_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if auth_header:
auth_header = auth_header.replace('Basic ', '', 1)
try:
auth_header = str(base64.b64decode(auth_header), 'utf-8')
username, password = auth_header.split(":")
except binascii.Error as e:
current_app.logger.error(
'Invalid base64-encoded of credential. Error {0}'.format(
e))
abort(401)
except TypeError as e:
current_app.logger.error('Error: {0}'.format(e))
abort(401)
user = User(username=username,
password=password,
plain_text_password=password)
try:
if Setting().get('verify_user_email') and user.email and not user.confirmed:
current_app.logger.warning(
'Basic authentication failed for user {} because of unverified email address'
.format(username))
abort(401)
auth_method = request.args.get('auth_method', 'LOCAL')
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
auth = user.is_validate(method=auth_method,
src_ip=request.remote_addr)
if not auth:
current_app.logger.error('Checking user password failed')
abort(401)
else:
user = User.query.filter(User.username == username).first()
current_user = user # lgtm [py/unused-local-variable]
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
abort(401)
else:
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:]
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 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(
'Invalid base64-encoded of credential. Error {0}'.format(
e))
abort(401)
except TypeError as e:
current_app.logger.error('Error: {0}'.format(e))
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,
password=password,
plain_text_password=password)
try:
if Setting().get('verify_user_email') and user.email and not user.confirmed:
current_app.logger.warning(
'Basic authentication failed for user {} because of unverified email address'
.format(username))
abort(401)
auth_method = request.args.get('auth_method', 'LOCAL')
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
auth = user.is_validate(method=auth_method, src_ip=request.remote_addr)
if not auth:
current_app.logger.error('Checking user password failed')
abort(401)
else:
user = User.query.filter(User.username == username).first()
current_user = user # lgtm [py/unused-local-variable]
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
abort(401)
return f(*args, **kwargs)
return decorated_function
@ -161,6 +214,27 @@ def is_json(f):
return decorated_function
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):
check_current_http_method = not http_methods or request.method in http_methods
if (check_current_http_method and
set(request.get_json(force=True).keys()).intersection(set(keys))
):
callback(*args, **kwargs)
return f(*args, **kwargs)
return decorated_function
return decorator
def api_role_can(action, roles=None, allow_self=False):
"""
Grant access if:
@ -183,16 +257,18 @@ def api_role_can(action, roles=None, allow_self=False):
except:
username = None
if (
(current_user.role.name in roles) or
(allow_self and user_id and current_user.id == user_id) or
(allow_self and username and current_user.username == username)
(current_user.role.name in roles) or
(allow_self and user_id and current_user.id == user_id) or
(allow_self and username and current_user.username == username)
):
return f(*args, **kwargs)
msg = (
"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
@ -202,27 +278,92 @@ 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 [
'Administrator', 'Operator'
'Administrator', 'Operator'
] 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))
raise NotEnoughPrivileges()
if Setting().get('deny_domain_override'):
req = request.get_json(force=True)
domain = Domain()
if req['name'] and domain.is_overriding(req['name']):
raise DomainOverrideForbidden()
return f(*args, **kwargs)
return decorated_function
def apikey_can_create_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_create_domain is on
and
- 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 [
'Administrator', 'Operator'
] and not Setting().get('allow_user_create_domain'):
msg = "ApiKey #{0} does not have enough privileges to create zone"
current_app.logger.error(msg.format(g.apikey.id))
raise NotEnoughPrivileges()
if Setting().get('deny_domain_override'):
req = request.get_json(force=True)
domain = Domain()
if req['name'] and domain.is_overriding(req['name']):
raise DomainOverrideForbidden()
return f(*args, **kwargs)
return decorated_function
def apikey_can_remove_domain(http_methods=[]):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_remove_domain is on
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
check_current_http_method = not http_methods or request.method in http_methods
if (check_current_http_method and
g.apikey.role.name not in ['Administrator', 'Operator'] and
not Setting().get('allow_user_remove_domain')
):
msg = "ApiKey #{0} does not have enough privileges to remove zone"
current_app.logger.error(msg.format(g.apikey.id))
raise NotEnoughPrivileges()
return f(*args, **kwargs)
return decorated_function
return decorator
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':
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))
raise NotEnoughPrivileges()
return f(*args, **kwargs)
@ -231,21 +372,112 @@ def apikey_is_admin(f):
def apikey_can_access_domain(f):
"""
Grant access if:
- user has Operator role or higher, or
- user has explicitly been granted access to domain
"""
@wraps(f)
def decorated_function(*args, **kwargs):
apikey = g.apikey
if g.apikey.role.name not in ['Administrator', 'Operator']:
domains = apikey.domains
zone_id = kwargs.get('zone_id')
domain_names = [item.name for item in domains]
zone_id = kwargs.get('zone_id').rstrip(".")
domain_names = [item.name for item in g.apikey.domains]
if zone_id not in domain_names:
accounts = g.apikey.accounts
accounts_domains = [domain.name for a in accounts for domain in a.domains]
allowed_domains = set(domain_names + accounts_domains)
if zone_id not in allowed_domains:
raise DomainAccessForbidden()
return f(*args, **kwargs)
return decorated_function
def apikey_can_configure_dnssec(http_methods=[]):
"""
Grant access if:
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
def decorator(f=None):
@wraps(f)
def decorated_function(*args, **kwargs):
check_current_http_method = not http_methods or request.method in http_methods
if (check_current_http_method and
g.apikey.role.name not in ['Administrator', 'Operator'] and
Setting().get('dnssec_admins_only')
):
msg = "ApiKey #{0} does not have enough privileges to configure dnssec"
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):
if request.method in ['GET', 'DELETE', 'PUT']:
return f(*args, **kwargs)
if g.apikey.role.name in ['Administrator', 'Operator']:
return f(*args, **kwargs)
records_allowed_to_edit = Setting().get_records_allow_to_edit()
content = request.get_json()
try:
for record in content['rrsets']:
if 'type' not in record:
raise RecordTypeNotAllowed()
if record['type'] not in records_allowed_to_edit:
current_app.logger.error(f"Error: Record type not allowed: {record['type']}")
raise RecordTypeNotAllowed(message=f"Record type not allowed: {record['type']}")
except (TypeError, KeyError) as e:
raise e
return f(*args, **kwargs)
return decorated_function
def allowed_record_ttl(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not Setting().get('enforce_api_ttl'):
return f(*args, **kwargs)
if request.method == 'GET':
return f(*args, **kwargs)
if g.apikey.role.name in ['Administrator', 'Operator']:
return f(*args, **kwargs)
allowed_ttls = Setting().get_ttl_options()
allowed_numeric_ttls = [ttl[0] for ttl in allowed_ttls]
content = request.get_json()
try:
for record in content['rrsets']:
if 'ttl' not in record:
raise RecordTTLNotAllowed()
if record['ttl'] not in allowed_numeric_ttls:
current_app.logger.error(f"Error: Record TTL not allowed: {record['ttl']}")
raise RecordTTLNotAllowed(message=f"Record TTL not allowed: {record['ttl']}")
except (TypeError, KeyError) as e:
raise e
return f(*args, **kwargs)
return decorated_function
def apikey_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -253,10 +485,8 @@ def apikey_auth(f):
if auth_header:
try:
apikey_val = str(base64.b64decode(auth_header), 'utf-8')
except binascii.Error as e:
current_app.logger.error(
'Invalid base64-encoded of credential. Error {0}'.format(
e))
except (binascii.Error, UnicodeDecodeError) as e:
current_app.logger.error('Invalid base64-encoded X-API-KEY. Error {0}'.format(e))
abort(401)
except TypeError as e:
current_app.logger.error('Error: {0}'.format(e))
@ -287,7 +517,19 @@ def dyndns_login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.is_authenticated is False:
return render_template('dyndns.html', response='badauth'), 200
return Response(headers={'WWW-Authenticate': 'Basic'}, status=401)
return f(*args, **kwargs)
return decorated_function
def apikey_or_basic_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
api_auth_header = request.headers.get('X-API-KEY')
if api_auth_header:
return apikey_auth(f)(*args, **kwargs)
else:
return api_basic_auth(f)(*args, **kwargs)
return decorated_function

@ -1,27 +1,32 @@
import os
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)))
### BASIC APP CONFIG
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'
SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
basedir = os.path.abspath(os.path.dirname(__file__))
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
CAPTCHA_ENABLE = True
CAPTCHA_HEIGHT = 60
CAPTCHA_LENGTH = 6
CAPTCHA_SESSION_KEY = 'captcha_image'
CAPTCHA_WIDTH = 160
CSRF_COOKIE_HTTPONLY = True
HSTS_ENABLED = False
OFFLINE_MODE = False
### DATABASE CONFIG
SQLA_DB_USER = 'pda'
SQLA_DB_PASSWORD = 'changeme'
SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'pda'
SQLALCHEMY_TRACK_MODIFICATIONS = True
### DATABASE - MySQL
SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'+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
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
# 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
# )

@ -1,48 +1,58 @@
from OpenSSL import crypto
from datetime import datetime
import pytz
import datetime
import os
CRYPT_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../")
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.x509.oid import NameOID
CRYPT_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../")
CERT_FILE = CRYPT_PATH + "/saml_cert.crt"
KEY_FILE = CRYPT_PATH + "/saml_cert.key"
def check_certificate():
if not os.path.isfile(CERT_FILE):
return False
st_cert = open(CERT_FILE, 'rt').read()
cert = crypto.load_certificate(crypto.FILETYPE_PEM, st_cert)
now = datetime.now(pytz.utc)
begin = datetime.strptime(cert.get_notBefore(), "%Y%m%d%H%M%SZ").replace(tzinfo=pytz.UTC)
begin_ok = begin < now
end = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ").replace(tzinfo=pytz.UTC)
end_ok = end > now
if begin_ok and end_ok:
return True
return False
def create_self_signed_cert():
""" Generate a new self-signed RSA-2048-SHA256 x509 certificate. """
# Generate our key
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# create a key pair
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 2048)
# Write our key to disk for safe keeping
with open(KEY_FILE, "wb") as key_file:
key_file.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
))
# create a self-signed cert
cert = crypto.X509()
cert.get_subject().C = "DE"
cert.get_subject().ST = "NRW"
cert.get_subject().L = "Dortmund"
cert.get_subject().O = "Dummy Company Ltd"
cert.get_subject().OU = "Dummy Company Ltd"
cert.get_subject().CN = "PowerDNS-Admin"
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10*365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(k)
cert.sign(k, 'sha256')
# Various details about who we are. For a self-signed certificate the
# subject and issuer are always the same.
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "NRW"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Dortmund"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Dummy Company Ltd"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Dummy Company Ltd"),
x509.NameAttribute(NameOID.COMMON_NAME, "PowerDNS-Admin"),
])
open(CERT_FILE, "bw").write(
crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
open(KEY_FILE, "bw").write(
crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=10*365)
).sign(key, hashes.SHA256())
# Write our certificate out to disk.
with open(CERT_FILE, "wb") as cert_file:
cert_file.write(cert.public_bytes(serialization.Encoding.PEM))

@ -21,7 +21,7 @@ class StructuredException(Exception):
class DomainNotExists(StructuredException):
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)
self.message = message
self.name = name
@ -30,7 +30,7 @@ class DomainNotExists(StructuredException):
class DomainAlreadyExists(StructuredException):
status_code = 409
def __init__(self, name=None, message="Domain already exists"):
def __init__(self, name=None, message="Zone already exists"):
StructuredException.__init__(self)
self.message = message
self.name = name
@ -39,11 +39,18 @@ class DomainAlreadyExists(StructuredException):
class DomainAccessForbidden(StructuredException):
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)
self.message = message
self.name = name
class DomainOverrideForbidden(StructuredException):
status_code = 409
def __init__(self, name=None, message="Zone override of record not allowed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class ApiKeyCreateFail(StructuredException):
status_code = 500
@ -60,7 +67,8 @@ class ApiKeyNotUsable(StructuredException):
def __init__(
self,
name=None,
message="Api key must have domains or have administrative role"):
message=("Api key must have zones or accounts"
" or an administrative role")):
StructuredException.__init__(self)
self.message = message
self.name = name
@ -93,6 +101,15 @@ class AccountCreateFail(StructuredException):
self.name = name
class AccountCreateDuplicate(StructuredException):
status_code = 409
def __init__(self, name=None, message="Creation of account failed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class AccountUpdateFail(StructuredException):
status_code = 500
@ -111,6 +128,22 @@ class AccountDeleteFail(StructuredException):
self.name = name
class AccountNotExists(StructuredException):
status_code = 404
def __init__(self, name=None, message="Account does not exist"):
StructuredException.__init__(self)
self.message = message
self.name = name
class InvalidAccountNameException(StructuredException):
status_code = 400
def __init__(self, name=None, message="The account name is invalid"):
StructuredException.__init__(self)
self.message = message
self.name = name
class UserCreateFail(StructuredException):
status_code = 500
@ -119,6 +152,13 @@ class UserCreateFail(StructuredException):
self.message = message
self.name = name
class UserCreateDuplicate(StructuredException):
status_code = 409
def __init__(self, name=None, message="Creation of user failed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class UserUpdateFail(StructuredException):
status_code = 500
@ -128,6 +168,13 @@ class UserUpdateFail(StructuredException):
self.message = message
self.name = name
class UserUpdateFailEmail(StructuredException):
status_code = 409
def __init__(self, name=None, message="Update of user failed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class UserDeleteFail(StructuredException):
status_code = 500
@ -136,3 +183,19 @@ class UserDeleteFail(StructuredException):
StructuredException.__init__(self)
self.message = message
self.name = name
class RecordTypeNotAllowed(StructuredException):
status_code = 400
def __init__(self, name=None, message="Record type not allowed or does not present"):
StructuredException.__init__(self)
self.message = message
self.name = name
class RecordTTLNotAllowed(StructuredException):
status_code = 400
def __init__(self, name=None, message="Record TTL not allowed or does not present"):
StructuredException.__init__(self)
self.message = message
self.name = name

@ -14,9 +14,9 @@ def forward_request():
msg_str = "Sending request to powerdns API {0}"
if request.method != 'GET' and request.method != 'DELETE':
msg = msg_str.format(request.get_json(force=True))
msg = msg_str.format(request.get_json(force=True, silent=True))
current_app.logger.debug(msg)
data = request.get_json(force=True)
data = request.get_json(force=True, silent=True)
verify = False

@ -11,10 +11,21 @@ class RoleSchema(Schema):
name = fields.String()
class AccountSummarySchema(Schema):
id = fields.Integer()
name = fields.String()
domains = fields.Embed(schema=DomainSchema, many=True)
class ApiKeySummarySchema(Schema):
id = fields.Integer()
description = fields.String()
class ApiKeySchema(Schema):
id = fields.Integer()
role = fields.Embed(schema=RoleSchema)
domains = fields.Embed(schema=DomainSchema, many=True)
accounts = fields.Embed(schema=AccountSummarySchema, many=True)
description = fields.String()
key = fields.String()
@ -23,6 +34,7 @@ class ApiPlainKeySchema(Schema):
id = fields.Integer()
role = fields.Embed(schema=RoleSchema)
domains = fields.Embed(schema=DomainSchema, many=True)
accounts = fields.Embed(schema=AccountSummarySchema, many=True)
description = fields.String()
plain_key = fields.String()
@ -35,6 +47,14 @@ class UserSchema(Schema):
email = fields.String()
role = fields.Embed(schema=RoleSchema)
class UserDetailedSchema(Schema):
id = fields.Integer()
username = fields.String()
firstname = fields.String()
lastname = fields.String()
email = fields.String()
role = fields.Embed(schema=RoleSchema)
accounts = fields.Embed(schema=AccountSummarySchema, many=True)
class AccountSchema(Schema):
id = fields.Integer()
@ -43,3 +63,4 @@ class AccountSchema(Schema):
contact = fields.String()
mail = fields.String()
domains = fields.Embed(schema=DomainSchema, many=True)
apikeys = fields.Embed(schema=ApiKeySummarySchema, many=True)

@ -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)

@ -2,8 +2,8 @@ import logging
import re
import json
import requests
import hashlib
import ipaddress
import idna
from collections.abc import Iterable
from distutils.version import StrictVersion
@ -103,6 +103,13 @@ def fetch_json(remote_url,
data = None
try:
data = json.loads(r.content.decode('utf-8'))
except UnicodeDecodeError:
# If the decoding fails, switch to slower but probably working .json()
try:
logging.warning("UTF-8 content.decode failed, switching to slower .json method")
data = r.json()
except Exception as e:
raise e
except Exception as e:
raise RuntimeError(
'Error while loading JSON data from {0}'.format(remote_url)) from e
@ -125,6 +132,16 @@ def display_master_name(data):
return ", ".join(matches)
def format_zone_type(data):
"""Formats the given zone type for modern social standards."""
data = str(data).lower()
if data == 'master':
data = 'primary'
elif data == 'slave':
data = 'secondary'
return data.title()
def display_time(amount, units='s', remove_seconds=True):
"""
Convert timestamp to normal time format
@ -177,17 +194,6 @@ def pdns_api_extended_uri(version):
return ""
def email_to_gravatar_url(email="", size=100):
"""
AD doesn't necessarily have email
"""
if email is None:
email = ""
hash_string = hashlib.md5(email.encode('utf-8')).hexdigest()
return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size)
def display_setting_state(value):
if value == 1:
return "ON"
@ -221,10 +227,49 @@ def ensure_list(l):
yield from l
class customBoxes:
boxes = {
"reverse": (" ", " "),
"ip6arpa": ("ip6", "%.ip6.arpa"),
"inaddrarpa": ("in-addr", "%.in-addr.arpa")
}
order = ["reverse", "ip6arpa", "inaddrarpa"]
def pretty_domain_name(domain_name):
# Add a debugging statement to print out the domain name
print("Received zone name:", domain_name)
# Check if the domain name is encoded using Punycode
if domain_name.endswith('.xn--'):
try:
# Decode the domain name using the idna library
domain_name = idna.decode(domain_name)
except Exception as e:
# If the decoding fails, raise an exception with more information
raise Exception('Cannot decode IDN zone: {}'.format(e))
# Return the "pretty" version of the zone name
return domain_name
def to_idna(value, action):
splits = value.split('.')
result = []
if action == 'encode':
for split in splits:
try:
# Try encoding to idna
if not split.startswith('_') and not split.startswith('-'):
result.append(idna.encode(split).decode())
else:
result.append(split)
except idna.IDNAError:
result.append(split)
elif action == 'decode':
for split in splits:
if not split.startswith('_') and not split.startswith('--'):
result.append(idna.decode(split))
else:
result.append(split)
else:
raise Exception('No valid action received')
return '.'.join(result)
def format_datetime(value, format_str="%Y-%m-%d %I:%M %p"):
"""Format a date time to (Default): YYYY-MM-DD HH:MM P"""
if value is None:
return ""
return value.strftime(format_str)

@ -8,6 +8,7 @@ from .account_user import AccountUser
from .server import Server
from .history import History
from .api_key import ApiKey
from .api_key_account import ApiKeyAccount
from .setting import Setting
from .domain import Domain
from .domain_setting import DomainSetting

@ -3,6 +3,7 @@ from flask import current_app
from urllib.parse import urljoin
from ..lib import utils
from ..lib.errors import InvalidAccountNameException
from .base import db
from .setting import Setting
from .user import User
@ -17,9 +18,12 @@ class Account(db.Model):
contact = db.Column(db.String(128))
mail = db.Column(db.String(128))
domains = db.relationship("Domain", back_populates="account")
apikeys = db.relationship("ApiKey",
secondary="apikey_account",
back_populates="accounts")
def __init__(self, name=None, description=None, contact=None, mail=None):
self.name = name
self.name = Account.sanitize_name(name) if name is not None else name
self.description = description
self.contact = contact
self.mail = mail
@ -30,9 +34,30 @@ class Account(db.Model):
self.PDNS_VERSION = Setting().get('pdns_version')
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
if self.name is not None:
self.name = ''.join(c for c in self.name.lower()
if c in "abcdefghijklmnopqrstuvwxyz0123456789")
@staticmethod
def sanitize_name(name):
"""
Formats the provided name to fit into the constraint
"""
if not isinstance(name, str):
raise InvalidAccountNameException("Account name must be a string")
allowed_characters = "abcdefghijklmnopqrstuvwxyz0123456789"
if Setting().get('account_name_extra_chars'):
allowed_characters += "_-."
sanitized_name = ''.join(c for c in name.lower() if c in allowed_characters)
if len(sanitized_name) > Account.name.type.length:
current_app.logger.error("Account name {0} too long. Truncated to: {1}".format(
sanitized_name, sanitized_name[:Account.name.type.length]))
if not sanitized_name:
raise InvalidAccountNameException("Empty string is not a valid account name")
return sanitized_name[:Account.name.type.length]
def __repr__(self):
return '<Account {0}r>'.format(self.name)
@ -65,11 +90,9 @@ class Account(db.Model):
"""
Create a new account
"""
# Sanity check - account name
if self.name == "":
return {'status': False, 'msg': 'No account name specified'}
self.name = Account.sanitize_name(self.name)
# check that account name is not already used
# Check that account name is not already used
account = Account.query.filter(Account.name == self.name).first()
if account:
return {'status': False, 'msg': 'Account already exists'}

@ -1,12 +1,12 @@
import random
import secrets
import string
import bcrypt
from flask import current_app
from .base import db, domain_apikey
from .base import db
from ..models.role import Role
from ..models.domain import Domain
from ..models.account import Account
class ApiKey(db.Model):
__tablename__ = "apikey"
@ -16,17 +16,21 @@ class ApiKey(db.Model):
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
role = db.relationship('Role', back_populates="apikeys", lazy=True)
domains = db.relationship("Domain",
secondary=domain_apikey,
secondary="domain_apikey",
back_populates="apikeys")
accounts = db.relationship("Account",
secondary="apikey_account",
back_populates="apikeys")
def __init__(self, key=None, desc=None, role_name=None, domains=[]):
def __init__(self, key=None, desc=None, role_name=None, domains=[], accounts=[]):
self.id = None
self.description = desc
self.role_name = role_name
self.domains[:] = domains
self.accounts[:] = accounts
if not key:
rand_key = ''.join(
random.choice(string.ascii_letters + string.digits)
secrets.choice(string.ascii_letters + string.digits)
for _ in range(15))
self.plain_key = rand_key
self.key = self.get_hashed_password(rand_key).decode('utf-8')
@ -54,27 +58,33 @@ class ApiKey(db.Model):
db.session.rollback()
raise e
def update(self, role_name=None, description=None, domains=None):
def update(self, role_name=None, description=None, domains=None, accounts=None):
try:
if role_name:
role = Role.query.filter(Role.name == role_name).first()
self.role_id = role.id
if role_name:
role = Role.query.filter(Role.name == role_name).first()
self.role_id = role.id
if description:
self.description = description
if description:
self.description = description
if domains:
domain_object_list = Domain.query \
.filter(Domain.name.in_(domains)) \
.all()
self.domains[:] = domain_object_list
if domains is not None:
domain_object_list = Domain.query \
.filter(Domain.name.in_(domains)) \
.all()
self.domains[:] = domain_object_list
db.session.commit()
if accounts is not None:
account_object_list = Account.query \
.filter(Account.name.in_(accounts)) \
.all()
self.accounts[:] = account_object_list
db.session.commit()
except Exception as e:
msg_str = 'Update of apikey failed. Error: {0}'
current_app.logger.error(msg_str.format(e))
db.session.rollback
raise e
msg_str = 'Update of apikey failed. Error: {0}'
current_app.logger.error(msg_str.format(e))
db.session.rollback() # fixed line
raise e
def get_hashed_password(self, plain_text_password=None):
# Hash a password for the first time
@ -87,6 +97,15 @@ class ApiKey(db.Model):
else:
pw = self.plain_text_password
# The salt value is currently re-used here intentionally because
# the implementation relies on just the API key's value itself
# for database lookup: ApiKey.is_validate() would have no way of
# discerning whether any given key is valid if bcrypt.gensalt()
# was used. As far as is known, this is fine as long as the
# value of new API keys is randomly generated in a
# cryptographically secure fashion, as this then makes
# expendable as an exception the otherwise vital protection of
# proper salting as provided by bcrypt.gensalt().
return bcrypt.hashpw(pw.encode('utf-8'),
current_app.config.get('SALT').encode('utf-8'))
@ -112,3 +131,12 @@ class ApiKey(db.Model):
raise Exception("Unauthorized")
return apikey
def associate_account(self, account):
return True
def dissociate_account(self, account):
return True
def get_accounts(self):
return True

@ -0,0 +1,20 @@
from .base import db
class ApiKeyAccount(db.Model):
__tablename__ = 'apikey_account'
id = db.Column(db.Integer, primary_key=True)
apikey_id = db.Column(db.Integer,
db.ForeignKey('apikey.id'),
nullable=False)
account_id = db.Column(db.Integer,
db.ForeignKey('account.id'),
nullable=False)
db.UniqueConstraint('apikey_id', 'account_id', name='uniq_apikey_account')
def __init__(self, apikey_id, account_id):
self.apikey_id = apikey_id
self.account_id = account_id
def __repr__(self):
return '<ApiKey_Account {0} {1}>'.format(self.apikey_id, self.account_id)

@ -1,6 +1,8 @@
import json
import re
import traceback
from flask import current_app
from flask_login import current_user
from urllib.parse import urljoin
from distutils.util import strtobool
@ -19,7 +21,7 @@ class Domain(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), index=True, unique=True)
master = db.Column(db.String(128))
type = db.Column(db.String(6), nullable=False)
type = db.Column(db.String(8), nullable=False)
serial = db.Column(db.BigInteger)
notified_serial = db.Column(db.BigInteger)
last_check = db.Column(db.Integer)
@ -66,13 +68,13 @@ class Domain(db.Model):
return True
except Exception as e:
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))
return False
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}
jdata = utils.fetch_json(urljoin(
@ -86,7 +88,7 @@ class Domain(db.Model):
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}
jdata = utils.fetch_json(
@ -106,17 +108,33 @@ class Domain(db.Model):
return domain.id
except Exception as e:
current_app.logger.error(
'Domain does not exist. ERROR: {0}'.format(e))
'Zone does not exist. ERROR: {0}'.format(e))
return None
def search_idn_domains(self, search_string):
"""
Search for IDN zones using the provided search string.
"""
# Compile the regular expression pattern for matching IDN zone names
idn_pattern = re.compile(r'^xn--')
# Search for zone names that match the IDN pattern
idn_domains = [
domain for domain in self.get_domains() if idn_pattern.match(domain)
]
# Filter the search results based on the provided search string
return [domain for domain in idn_domains if search_string in domain]
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()
list_db_domain = [d.name for d 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)))
headers = {'X-API-Key': self.PDNS_API_KEY}
try:
@ -131,20 +149,31 @@ class Domain(db.Model):
"Found {} zones in PowerDNS server".format(len(list_jdomain)))
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(
set(list_db_domain).difference(list_jdomain))
for domain_name in should_removed_db_domain:
self.delete_domain_from_pdnsadmin(domain_name, do_commit=False)
except Exception as e:
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())
# update/add new domain
# update/add new zone
account_cache = {}
for data in jdata:
if 'account' in data:
account_id = Account().get_id_by_name(data['account'])
# if no account is set don't try to query db
if data['account'] == '':
find_account_id = None
else:
find_account_id = account_cache.get(data['account'])
# if account was not queried in the past and hence not in cache
if find_account_id is None:
find_account_id = Account().get_id_by_name(data['account'])
# add to cache
account_cache[data['account']] = find_account_id
account_id = find_account_id
else:
current_app.logger.debug(
"No 'account' data found in API result - Unsupported PowerDNS version?"
@ -158,16 +187,16 @@ class Domain(db.Model):
self.add_domain_to_powerdns_admin(domain=data, do_commit=False)
db.session.commit()
current_app.logger.info('Update domain finished')
current_app.logger.info('Update zone finished')
return {
'status': 'ok',
'msg': 'Domain table has been updated successfully'
'msg': 'Zone table has been updated successfully'
}
except Exception as e:
db.session.rollback()
current_app.logger.error(
'Cannot update domain table. Error: {0}'.format(e))
return {'status': 'error', 'msg': 'Cannot update domain table'}
'Cannot update zone table. Error: {0}'.format(e))
return {'status': 'error', 'msg': 'Cannot update zone table'}
def update_pdns_admin_domain(self, domain, account_id, data, do_commit=True):
# existing domain, only update if something actually has changed
@ -189,11 +218,11 @@ class Domain(db.Model):
try:
if do_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))
except Exception as e:
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))
raise
@ -205,10 +234,10 @@ class Domain(db.Model):
domain_master_ips=[],
account_name=None):
"""
Add a domain to power dns
Add a zone to power dns
"""
headers = {'X-API-Key': self.PDNS_API_KEY}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
domain_name = domain_name + '.'
domain_ns = [ns + '.' for ns in domain_ns]
@ -240,23 +269,23 @@ class Domain(db.Model):
if 'error' in jdata.keys():
current_app.logger.error(jdata['error'])
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']}
else:
current_app.logger.info(
'Added domain successfully to PowerDNS: {0}'.format(
'Added zone successfully to PowerDNS: {0}'.format(
domain_name))
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:
current_app.logger.error('Cannot add domain {0} {1}'.format(
current_app.logger.error('Cannot add zone {0} {1}'.format(
domain_name, e))
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):
"""
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}
if not domain:
@ -270,7 +299,7 @@ class Domain(db.Model):
timeout=int(Setting().get('pdns_api_timeout')),
verify=Setting().get('verify_ssl_connections'))
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.debug(traceback.format_exc())
@ -296,22 +325,22 @@ class Domain(db.Model):
if do_commit:
db.session.commit()
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 {
'status': 'ok',
'msg': 'Added Domain successfully to PowerDNS-Admin'
'msg': 'Added zone successfully to PowerDNS-Admin'
}
except Exception as e:
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
def update_soa_setting(self, domain_name, soa_edit_api):
domain = Domain.query.filter(Domain.name == domain_name).first()
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}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
if soa_edit_api not in ["DEFAULT", "INCREASE", "EPOCH", "OFF"]:
soa_edit_api = 'DEFAULT'
@ -336,7 +365,7 @@ class Domain(db.Model):
return {'status': 'error', 'msg': jdata['error']}
else:
current_app.logger.info(
'soa-edit-api changed for domain {0} successfully'.format(
'soa-edit-api changed for zone {0} successfully'.format(
domain_name))
return {
'status': 'ok',
@ -346,11 +375,11 @@ class Domain(db.Model):
current_app.logger.debug(e)
current_app.logger.debug(traceback.format_exc())
current_app.logger.error(
'Cannot change soa-edit-api for domain {0}'.format(
'Cannot change soa-edit-api for zone {0}'.format(
domain_name))
return {
'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=[]):
@ -359,9 +388,9 @@ class Domain(db.Model):
"""
domain = Domain.query.filter(Domain.name == domain_name).first()
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}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
post_data = {"kind": kind, "masters": masters}
@ -380,26 +409,26 @@ class Domain(db.Model):
return {'status': 'error', 'msg': jdata['error']}
else:
current_app.logger.info(
'Update domain kind for {0} successfully'.format(
'Update zone kind for {0} successfully'.format(
domain_name))
return {
'status': 'ok',
'msg': 'Domain kind changed successfully'
'msg': 'Zone kind changed successfully'
}
except Exception as e:
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))
current_app.logger.debug(traceback.format_exc())
return {
'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):
"""
Check the existing reverse lookup domain,
Check the existing reverse lookup zone,
if not exists create a new one automatically
"""
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
@ -419,9 +448,9 @@ class Domain(db.Model):
result = self.add(domain_reverse_name, 'Master', 'DEFAULT', [], [])
self.update()
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),
detail=str({
detail=json.dumps({
'domain_type': 'Master',
'domain_master_ips': ''
}),
@ -430,7 +459,7 @@ class Domain(db.Model):
else:
return {
'status': 'error',
'msg': 'Adding reverse lookup domain failed'
'msg': 'Adding reverse lookup zone failed'
}
domain_user_ids = self.get_user()
if len(domain_user_ids) > 0:
@ -440,13 +469,13 @@ class Domain(db.Model):
'status':
'ok',
'msg':
'New reverse lookup domain created with granted privileges'
'New reverse lookup zone created with granted privileges'
}
return {
'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):
c = 1
@ -475,22 +504,22 @@ class Domain(db.Model):
def delete(self, domain_name):
"""
Delete a single domain name from powerdns
Delete a single zone name from powerdns
"""
try:
self.delete_domain_from_powerdns(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:
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.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):
"""
Delete a single domain name from powerdns
Delete a single zone name from powerdns
"""
headers = {'X-API-Key': self.PDNS_API_KEY}
@ -502,12 +531,12 @@ class Domain(db.Model):
method='DELETE',
verify=Setting().get('verify_ssl_connections'))
current_app.logger.info(
'Deleted domain successfully from PowerDNS: {0}'.format(
'Deleted zone successfully from PowerDNS: {0}'.format(
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):
# Revoke permission before deleting domain
# Revoke permission before deleting zone
domain = Domain.query.filter(Domain.name == domain_name).first()
domain_user = DomainUser.query.filter(
DomainUser.domain_id == domain.id)
@ -519,17 +548,25 @@ class Domain(db.Model):
domain_setting.delete()
domain.apikeys[:] = []
# then remove domain
# Remove history for zone
if not Setting().get('preserve_history'):
domain_history = History.query.filter(
History.domain_id == domain.id
)
if domain_history:
domain_history.delete()
# then remove zone
Domain.query.filter(Domain.name == domain_name).delete()
if do_commit:
db.session.commit()
current_app.logger.info(
"Deleted domain successfully from pdnsADMIN: {}".format(
"Deleted zone successfully from pdnsADMIN: {}".format(
domain_name))
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 = []
query = db.session.query(
@ -559,7 +596,7 @@ class Domain(db.Model):
except Exception as e:
db.session.rollback()
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))
current_app.logger.debug(print(traceback.format_exc()))
@ -571,14 +608,43 @@ class Domain(db.Model):
except Exception as e:
db.session.rollback()
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))
current_app.logger.debug(print(traceback.format_exc()))
def revoke_privileges_by_id(self, user_id):
"""
Remove a single user from privilege list based on user_id
"""
new_uids = [u for u in self.get_user() if u != user_id]
users = []
for uid in new_uids:
users.append(User(id=uid).get_user_info_by_id().username)
self.grant_privileges(users)
def add_user(self, user):
"""
Add a single user to zone by User
"""
try:
du = DomainUser(self.id, user.id)
db.session.add(du)
db.session.commit()
return True
except Exception as e:
db.session.rollback()
current_app.logger.error(
'Cannot add user privileges on zone {0}. DETAIL: {1}'.
format(self.name, e))
return False
def update_from_master(self, domain_name):
"""
Update records from Master DNS server
"""
import urllib.parse
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {'X-API-Key': self.PDNS_API_KEY}
@ -586,7 +652,7 @@ class Domain(db.Model):
r = utils.fetch_json(urljoin(
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
'/servers/localhost/zones/{0}/axfr-retrieve'.format(
domain.name)),
urllib.parse.quote_plus(domain.name))),
headers=headers,
timeout=int(
Setting().get('pdns_api_timeout')),
@ -603,12 +669,14 @@ class Domain(db.Model):
'There was something wrong, please contact administrator'
}
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):
"""
Get domain DNSSEC information
Get zone DNSSEC information
"""
import urllib.parse
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {'X-API-Key': self.PDNS_API_KEY}
@ -617,7 +685,7 @@ class Domain(db.Model):
urljoin(
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
'/servers/localhost/zones/{0}/cryptokeys'.format(
domain.name)),
urllib.parse.quote_plus(domain.name))),
headers=headers,
timeout=int(Setting().get('pdns_api_timeout')),
method='GET',
@ -625,13 +693,13 @@ class Domain(db.Model):
if 'error' in jdata:
return {
'status': 'error',
'msg': 'DNSSEC is not enabled for this domain'
'msg': 'DNSSEC is not enabled for this zone'
}
else:
return {'status': 'ok', 'dnssec': jdata}
except Exception as e:
current_app.logger.error(
'Cannot get domain dnssec. DETAIL: {0}'.format(e))
'Cannot get zone dnssec. DETAIL: {0}'.format(e))
return {
'status':
'error',
@ -639,22 +707,26 @@ class Domain(db.Model):
'There was something wrong, please contact administrator'
}
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):
"""
Enable domain DNSSEC
Enable zone DNSSEC
"""
import urllib.parse
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {'X-API-Key': self.PDNS_API_KEY}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
try:
# Enable API-RECTIFY for domain, BEFORE activating DNSSEC
post_data = {"api_rectify": True}
jdata = utils.fetch_json(
urljoin(
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
'/servers/localhost/zones/{0}'.format(domain.name)),
'/servers/localhost/zones/{0}'.format(
urllib.parse.quote_plus(domain.name)
)),
headers=headers,
timeout=int(Setting().get('pdns_api_timeout')),
method='PUT',
@ -664,7 +736,7 @@ class Domain(db.Model):
return {
'status': 'error',
'msg':
'API-RECTIFY could not be enabled for this domain',
'API-RECTIFY could not be enabled for this zone',
'jdata': jdata
}
@ -674,7 +746,8 @@ class Domain(db.Model):
urljoin(
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
'/servers/localhost/zones/{0}/cryptokeys'.format(
domain.name)),
urllib.parse.quote_plus(domain.name)
)),
headers=headers,
timeout=int(Setting().get('pdns_api_timeout')),
method='POST',
@ -685,7 +758,7 @@ class Domain(db.Model):
'status':
'error',
'msg':
'Cannot enable DNSSEC for this domain. Error: {0}'.
'Cannot enable DNSSEC for this zone. Error: {0}'.
format(jdata['error']),
'jdata':
jdata
@ -705,22 +778,24 @@ class Domain(db.Model):
}
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):
"""
Remove keys DNSSEC
"""
import urllib.parse
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {'X-API-Key': self.PDNS_API_KEY}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
try:
# Deactivate DNSSEC
jdata = utils.fetch_json(
urljoin(
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
'/servers/localhost/zones/{0}/cryptokeys/{1}'.format(
domain.name, key_id)),
urllib.parse.quote_plus(domain.name), key_id)),
headers=headers,
timeout=int(Setting().get('pdns_api_timeout')),
method='DELETE',
@ -730,13 +805,13 @@ class Domain(db.Model):
'status':
'error',
'msg':
'Cannot disable DNSSEC for this domain. Error: {0}'.
'Cannot disable DNSSEC for this zone. Error: {0}'.
format(jdata['error']),
'jdata':
jdata
}
# Disable API-RECTIFY for domain, AFTER deactivating DNSSEC
# Disable API-RECTIFY for zone, AFTER deactivating DNSSEC
post_data = {"api_rectify": False}
jdata = utils.fetch_json(
urljoin(
@ -751,7 +826,7 @@ class Domain(db.Model):
return {
'status': 'error',
'msg':
'API-RECTIFY could not be disabled for this domain',
'API-RECTIFY could not be disabled for this zone',
'jdata': jdata
}
@ -770,25 +845,26 @@ class Domain(db.Model):
}
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):
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
# Sanity check - 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
domain = Domain.query.filter(Domain.name == domain_name).first()
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}
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
account_name_old = Account().get_name_by_id(domain.account_id)
account_name = Account().get_name_by_id(account_id)
post_data = {"account": account_name}
@ -808,24 +884,32 @@ class Domain(db.Model):
current_app.logger.error(jdata['error'])
return {'status': 'error', 'msg': jdata['error']}
else:
self.update()
msg_str = 'Account changed for domain {0} successfully'
if update:
self.update()
msg_str = 'Account changed for zone {0} successfully'
current_app.logger.info(msg_str.format(domain_name))
history = History(msg='Update zone {0} associate account {1}'.format(domain.name, 'none' if account_name == '' else account_name),
detail = json.dumps({
'assoc_account': 'None' if account_name == '' else account_name,
'dissoc_account': 'None' if account_name_old == '' else account_name_old
}),
created_by=current_user.username)
history.add()
return {'status': 'ok', 'msg': 'account changed successfully'}
except Exception as e:
current_app.logger.debug(e)
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))
return {
'status': 'error',
'msg': 'Cannot change account for this domain.'
'msg': 'Cannot change account for this zone.'
}
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()
@ -834,7 +918,7 @@ class Domain(db.Model):
def is_valid_access(self, user_id):
"""
Check if the user is allowed to access this
domain name
zone name
"""
return db.session.query(Domain) \
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
@ -845,3 +929,18 @@ class Domain(db.Model):
DomainUser.user_id == user_id,
AccountUser.user_id == user_id
)).filter(Domain.id == self.id).first()
# Return None if this zone does not exist as record,
# Return the parent zone that hold the record if exist
def is_overriding(self, domain_name):
upper_domain_name = '.'.join(domain_name.split('.')[1:])
while upper_domain_name != '':
if self.get_id_by_name(upper_domain_name.rstrip('.')) != None:
upper_domain = self.get_domain_info(upper_domain_name)
if 'rrsets' in upper_domain:
for r in upper_domain['rrsets']:
if domain_name.rstrip('.') in r['name'].rstrip('.'):
current_app.logger.error('Zone already exists as a record: {} under zone: {}'.format(r['name'].rstrip('.'), upper_domain_name))
return upper_domain_name
upper_domain_name = '.'.join(upper_domain_name.split('.')[1:])
return None

Some files were not shown because too many files have changed in this diff Show More