Add Azure as an explicit OAuth provider

This commit is contained in:
Steve Shipway 2019-12-05 13:21:50 +13:00
parent 0b2eb0fbf8
commit 1662944867
4 changed files with 147 additions and 2 deletions

View File

@ -2078,6 +2078,13 @@ 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',
'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': '',

View File

@ -74,6 +74,39 @@ def google_oauth():
return google
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,
)
@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
def oidc_oauth():
if not Setting().get('oidc_oauth_enabled'):
return None

View File

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

View File

@ -21,7 +21,7 @@ from werkzeug import secure_filename
from .models import User, Account, AccountUser, Domain, Record, RecordEntry, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager, csrf
from app.lib import utils
from app.oauth import github_oauth, google_oauth, oidc_oauth
from app.oauth import github_oauth, google_oauth, oidc_oauth, azure_oauth
from app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain
from yaml import Loader, load
@ -57,9 +57,11 @@ def register_modules():
global google
global github
global oidc
global azure
google = google_oauth()
github = github_oauth()
oidc = oidc_oauth()
azure = azure_oauth()
# START USER AUTHENTICATION HANDLER
@ -223,6 +225,15 @@ def github_login():
redirect_uri = url_for('github_authorized', _external=True)
return github.authorize_redirect(redirect_uri)
@app.route('/azure/login')
def azure_login():
if not Setting().get('azure_oauth_enabled') or azure is None:
logging.error('Microsoft OAuth is disabled or you have not yet reloaded the pda application after enabling.')
return abort(400)
else:
redirect_uri = url_for('azure_authorized', _external=True, _scheme='https')
return azure.authorize_redirect(redirect_uri)
@app.route('/oidc/login')
def oidc_login():
if not Setting().get('oidc_oauth_enabled') or oidc is None:
@ -450,6 +461,42 @@ def login():
login_user(user, remember = False)
return redirect(url_for('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']:
logging.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'))
if 'oidc_token' in session:
me = json.loads(oidc.get('userinfo').text)
oidc_username = me["preferred_username"]