mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2024-12-04 19:15:30 +00:00
Resolve the conflicts and add adjustment to #591
This commit is contained in:
commit
80b6ca19ac
@ -12,7 +12,7 @@ A PowerDNS web interface with advanced features.
|
||||
- User access management based on domain
|
||||
- User activity logging
|
||||
- Support Local DB / SAML / LDAP / Active Directory user authentication
|
||||
- Support Google / Github / OpenID OAuth
|
||||
- Support Google / Github / Azure / OpenID OAuth
|
||||
- Support Two-factor authentication (TOTP)
|
||||
- Dashboard and pdns service statistics
|
||||
- DynDNS 2 protocol support
|
||||
@ -39,4 +39,4 @@ You can now access PowerDNS-Admin at url http://localhost:9191
|
||||
**NOTE:** For other methods to run PowerDNS-Admin, please take look at WIKI pages.
|
||||
|
||||
### Screenshots
|
||||
![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png)
|
||||
![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png)
|
||||
|
20
docs/oauth.md
Normal file
20
docs/oauth.md
Normal file
@ -0,0 +1,20 @@
|
||||
### OAuth Authentication
|
||||
|
||||
#### Microsoft Azure
|
||||
|
||||
To link to Azure for authentication, you need to register PowerDNS-Admin in Azure. This requires your PowerDNS-Admin web interface to use an HTTPS URL.
|
||||
|
||||
* Under the Azure Active Directory, select App Registrations, and create a new one. Give it any name you want, and the Redirect URI shoule be type 'Web' and of the format https://powerdnsadmin/azure/authorized (replace the host name approriately).
|
||||
* Select the newly-created registration
|
||||
* On the Overview page, the Application ID is your new Client ID to use with PowerDNS-Admin
|
||||
* On the Overview page, make a note of your Directory/Tenant ID - you need it for the API URLs later
|
||||
* Ensure Access Tokens are enabled in the Authentication section
|
||||
* Under Certificates and Secrets, create a new Client Secret. Note this secret as it is the new Client Secret to use with PowerDNS-Admin
|
||||
* Under API Permissions, you need to add permissions. Add permissions for Graph API, Delegated. Add email, openid, profile, User.Read and possibly User.Read.All. You then need to grant admin approval for your organisation.
|
||||
|
||||
Now you can enable the OAuth in PowerDNS-Admin.
|
||||
* For the Scope, use 'User.Read openid mail profile'
|
||||
* 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.
|
@ -62,6 +62,15 @@ class Setting(db.Model):
|
||||
'google_oauth_scope': 'openid email profile',
|
||||
'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
'google_base_url': 'https://www.googleapis.com/oauth2/v3/',
|
||||
'azure_oauth_enabled': False,
|
||||
'azure_oauth_key': '',
|
||||
'azure_oauth_secret': '',
|
||||
'azure_oauth_scope': 'User.Read openid email profile',
|
||||
'azure_oauth_api_url': 'https://graph.microsoft.com/v1.0/',
|
||||
'azure_oauth_token_url':
|
||||
'https://login.microsoftonline.com/[tenancy]/oauth2/v2.0/token',
|
||||
'azure_oauth_authorize_url':
|
||||
'https://login.microsoftonline.com/[tenancy]/oauth2/v2.0/authorize',
|
||||
'oidc_oauth_enabled': False,
|
||||
'oidc_oauth_key': '',
|
||||
'oidc_oauth_secret': '',
|
||||
|
@ -696,6 +696,26 @@ def setting_authentication():
|
||||
'status': True,
|
||||
'msg': 'Saved successfully. Please reload PDA to take effect.'
|
||||
}
|
||||
elif conf_type == 'azure':
|
||||
Setting().set(
|
||||
'azure_oauth_enabled',
|
||||
True if request.form.get('azure_oauth_enabled') else False)
|
||||
Setting().set('azure_oauth_key',
|
||||
request.form.get('azure_oauth_key'))
|
||||
Setting().set('azure_oauth_secret',
|
||||
request.form.get('azure_oauth_secret'))
|
||||
Setting().set('azure_oauth_scope',
|
||||
request.form.get('azure_oauth_scope'))
|
||||
Setting().set('azure_oauth_api_url',
|
||||
request.form.get('azure_oauth_api_url'))
|
||||
Setting().set('azure_oauth_token_url',
|
||||
request.form.get('azure_oauth_token_url'))
|
||||
Setting().set('azure_oauth_authorize_url',
|
||||
request.form.get('azure_oauth_authorize_url'))
|
||||
result = {
|
||||
'status': True,
|
||||
'msg': 'Saved successfully. Please reload PDA to take effect.'
|
||||
}
|
||||
elif conf_type == 'oidc':
|
||||
Setting().set(
|
||||
'oidc_oauth_enabled',
|
||||
|
@ -25,10 +25,12 @@ from ..models.setting import Setting
|
||||
from ..models.history import History
|
||||
from ..services.google import google_oauth
|
||||
from ..services.github import github_oauth
|
||||
from ..services.azure import azure_oauth
|
||||
from ..services.oidc import oidc_oauth
|
||||
|
||||
google = None
|
||||
github = None
|
||||
azure = None
|
||||
oidc = None
|
||||
|
||||
index_bp = Blueprint('index',
|
||||
@ -41,9 +43,11 @@ index_bp = Blueprint('index',
|
||||
def register_modules():
|
||||
global google
|
||||
global github
|
||||
global azure
|
||||
global oidc
|
||||
google = google_oauth()
|
||||
github = github_oauth()
|
||||
azure = azure_oauth()
|
||||
oidc = oidc_oauth()
|
||||
|
||||
|
||||
@ -97,6 +101,20 @@ def github_login():
|
||||
return github.authorize_redirect(redirect_uri)
|
||||
|
||||
|
||||
@index_bp.route('/azure/login')
|
||||
def azure_login():
|
||||
if not Setting().get('azure_oauth_enabled') or azure is None:
|
||||
current_app.logger.error(
|
||||
'Microsoft OAuth is disabled or you have not yet reloaded the pda application after enabling.'
|
||||
)
|
||||
abort(400)
|
||||
else:
|
||||
redirect_uri = url_for('azure_authorized',
|
||||
_external=True,
|
||||
_scheme='https')
|
||||
return azure.authorize_redirect(redirect_uri)
|
||||
|
||||
|
||||
@index_bp.route('/oidc/login')
|
||||
def oidc_login():
|
||||
if not Setting().get('oidc_oauth_enabled') or oidc is None:
|
||||
@ -167,6 +185,44 @@ def login():
|
||||
login_user(user, remember=False)
|
||||
return redirect(url_for('index.index'))
|
||||
|
||||
if 'azure_token' in session:
|
||||
me = json.loads(azure.get('me').text)
|
||||
azure_username = me["userPrincipalName"]
|
||||
azure_givenname = me["givenName"]
|
||||
azure_familyname = me["surname"]
|
||||
if "email" in me:
|
||||
azure_email = me["email"]
|
||||
else:
|
||||
azure_email = ""
|
||||
if not azure_email:
|
||||
azure_email = me["userPrincipalName"]
|
||||
# Handle foreign principals such as guest users
|
||||
azure_email = re.sub(r"#.*$", "", azure_email)
|
||||
azure_username = re.sub(r"#.*$", "", azure_username)
|
||||
|
||||
user = User.query.filter_by(username=azure_username).first()
|
||||
if not user:
|
||||
user = User(username=azure_username,
|
||||
plain_text_password=None,
|
||||
firstname=azure_givenname,
|
||||
lastname=azure_familyname,
|
||||
email=azure_email)
|
||||
|
||||
result = user.create_local_user()
|
||||
if not result['status']:
|
||||
current_app.logger.warning('Unable to create ' + azure_username)
|
||||
session.pop('azure_token', None)
|
||||
# note: a redirect to login results in an endless loop, so render the login page instead
|
||||
return render_template('login.html',
|
||||
saml_enabled=SAML_ENABLED,
|
||||
error=('User ' + azure_username +
|
||||
' cannot be created.'))
|
||||
|
||||
session['user_id'] = user.id
|
||||
session['authentication_type'] = 'OAuth'
|
||||
login_user(user, remember=False)
|
||||
return redirect(url_for('index.index'))
|
||||
|
||||
if 'oidc_token' in session:
|
||||
me = json.loads(oidc.get('userinfo').text)
|
||||
oidc_username = me["preferred_username"]
|
||||
|
38
powerdnsadmin/services/azure.py
Normal file
38
powerdnsadmin/services/azure.py
Normal file
@ -0,0 +1,38 @@
|
||||
from flask import request, session, redirect, url_for, current_app
|
||||
|
||||
from .base import authlib_oauth_client
|
||||
from ..models.setting import Setting
|
||||
|
||||
|
||||
def azure_oauth():
|
||||
if not Setting().get('azure_oauth_enabled'):
|
||||
return None
|
||||
|
||||
def fetch_azure_token():
|
||||
return session.get('azure_token')
|
||||
|
||||
azure = authlib_oauth_client.register(
|
||||
'azure',
|
||||
client_id=Setting().get('azure_oauth_key'),
|
||||
client_secret=Setting().get('azure_oauth_secret'),
|
||||
api_base_url=Setting().get('azure_oauth_api_url'),
|
||||
request_token_url=None,
|
||||
access_token_url=Setting().get('azure_oauth_token_url'),
|
||||
authorize_url=Setting().get('azure_oauth_authorize_url'),
|
||||
client_kwargs={'scope': Setting().get('azure_oauth_scope')},
|
||||
fetch_token=fetch_azure_token,
|
||||
)
|
||||
|
||||
@current_app.route('/azure/authorized')
|
||||
def azure_authorized():
|
||||
session['azure_oauthredir'] = url_for('.azure_authorized',
|
||||
_external=True,
|
||||
_scheme='https')
|
||||
token = azure.authorize_access_token()
|
||||
if token is None:
|
||||
return 'Access denied: reason=%s error=%s' % (
|
||||
request.args['error'], request.args['error_description'])
|
||||
session['azure_token'] = (token)
|
||||
return redirect(url_for('.login', _external=True, _scheme='https'))
|
||||
|
||||
return azure
|
@ -52,6 +52,7 @@
|
||||
<li class="active"><a href="#tabs-ldap" data-toggle="tab">LDAP</a></li>
|
||||
<li><a href="#tabs-google" data-toggle="tab">Google OAuth</a></li>
|
||||
<li><a href="#tabs-github" data-toggle="tab">Github OAuth</a></li>
|
||||
<li><a href="#tabs-azure" data-toggle="tab">Microsoft OAuth</a></li>
|
||||
<li><a href="#tabs-oidc" data-toggle="tab">OpenID Connect OAuth</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
@ -359,6 +360,63 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-azure">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form role="form" method="post" data-toggle="validator">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" value="azure" name="config_tab" />
|
||||
<fieldset>
|
||||
<legend>GENERAL</legend>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="azure_oauth_enabled" name="azure_oauth_enabled" class="checkbox" {% if SETTING.get('azure_oauth_enabled') %}checked{% endif %}>
|
||||
<label for="azure_oauth_enabled">Enable Microsoft Azure OAuth</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_key">Client key</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_key" id="azure_oauth_key" placeholder="Azure OAuth client ID" data-error="Please input Client key" value="{{ SETTING.get('azure_oauth_key') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_secret">Client secret</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_secret" id="azure_oauth_secret" placeholder="Azure OAuth client secret" data-error="Please input Client secret" value="{{ SETTING.get('azure_oauth_secret') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>ADVANCE</legend>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_scope">Scope</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_scope" id="azure_oauth_scope" placeholder="e.g. email" data-error="Please input scope - e.g. User.Read" value="{{ SETTING.get('azure_oauth_scope') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_api_url">API URL</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_api_url" id="azure_oauth_api_url" placeholder="e.g. https://graph.microsoft.com/v1.0/" data-error="Please input API URL" value="{{ SETTING.get('azure_oauth_api_url') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_token_url">Token URL</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_token_url" id="azure_oauth_token_url" placeholder="e.g. https://login.microsoftonline.com/[tenancyID]/oauth2/v2.0/token" data-error="Please input Token URL" value="{{ SETTING.get('azure_oauth_token_url') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="azure_oauth_authorize_url">Authorize URL</label>
|
||||
<input type="text" class="form-control" name="azure_oauth_authorize_url" id="azure_oauth_authorize_url" placeholder="e.g. https://login.microsoftonline.com/[tenancyID]/oauth2/v2.0/authorize" data-error="Please input Authorize URL" value="{{ SETTING.get('azure_oauth_authorize_url') }}">
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-flat btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<legend>Help</legend>
|
||||
<p>Fill in all the fields in the left form.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-oidc">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
@ -618,6 +676,40 @@
|
||||
{% endif %}
|
||||
// END: Github tab js
|
||||
|
||||
// START: Azure tab js
|
||||
// update validation requirement when checkbox is togged
|
||||
$('#azure_oauth_enabled').iCheck({
|
||||
checkboxClass : 'icheckbox_square-blue',
|
||||
increaseArea : '20%'
|
||||
}).on('ifChanged', function(e) {
|
||||
var is_enabled = e.currentTarget.checked;
|
||||
if (is_enabled){
|
||||
$('#azure_oauth_key').prop('required', true);
|
||||
$('#azure_oauth_secret').prop('required', true);
|
||||
$('#azure_oauth_scope').prop('required', true);
|
||||
$('#azure_oauth_api_url').prop('required', true);
|
||||
$('#azure_oauth_token_url').prop('required', true);
|
||||
$('#azure_oauth_authorize_url').prop('required', true);
|
||||
} else {
|
||||
$('#azure_oauth_key').prop('required', false);
|
||||
$('#azure_oauth_secret').prop('required', false);
|
||||
$('#azure_oauth_scope').prop('required', false);
|
||||
$('#azure_oauth_api_url').prop('required', false);
|
||||
$('#azure_oauth_token_url').prop('required', false);
|
||||
$('#azure_oauth_authorize_url').prop('required', false);
|
||||
}
|
||||
});
|
||||
// init validation requirement at first time page load
|
||||
{% if SETTING.get('azure_oauth_enabled') %}
|
||||
$('#azure_oauth_key').prop('required', true);
|
||||
$('#azure_oauth_secret').prop('required', true);
|
||||
$('#azure_oauth_scope').prop('required', true);
|
||||
$('#azure_oauth_api_url').prop('required', true);
|
||||
$('#azure_oauth_token_url').prop('required', true);
|
||||
$('#azure_oauth_authorize_url').prop('required', true);
|
||||
{% endif %}
|
||||
// END: Azure tab js
|
||||
|
||||
// START: OIDC tab js
|
||||
$('#oidc_oauth_enabled').iCheck({
|
||||
checkboxClass : 'icheckbox_square-blue',
|
||||
|
@ -87,7 +87,7 @@
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
</form>
|
||||
{% if SETTING.get('google_oauth_enabled') or SETTING.get('github_oauth_enabled') or SETTING.get('oidc_oauth_enabled') %}
|
||||
{% if SETTING.get('google_oauth_enabled') or SETTING.get('github_oauth_enabled') or SETTING.get('oidc_oauth_enabled') or SETTING.get('azure_oauth_enabled') %}
|
||||
<div class="social-auth-links text-center">
|
||||
<p>- OR -</p>
|
||||
{% if SETTING.get('oidc_oauth_enabled') %}
|
||||
@ -102,9 +102,14 @@
|
||||
{% endif %}
|
||||
{% if SETTING.get('google_oauth_enabled') %}
|
||||
<a href="{{ url_for('index.google_login') }}" class="btn btn-block btn-social btn-google btn-flat"><i
|
||||
class="fa fa-google-plus"></i> Sign in using
|
||||
class="fa fa-google"></i> Sign in using
|
||||
Google</a>
|
||||
{% endif %}
|
||||
{% if SETTING.get('azure_oauth_enabled') %}
|
||||
<a href="{{ url_for('index.azure_login') }}" class="btn btn-block btn-social btn-microsoft btn-flat"><i
|
||||
class="fa fa-windows"></i> Sign in using
|
||||
Microsoft Azure</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user