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_oauth_scope': 'openid email profile',
'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth', 'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth',
'google_base_url': 'https://www.googleapis.com/oauth2/v3/', '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_enabled': False,
'oidc_oauth_key': '', 'oidc_oauth_key': '',
'oidc_oauth_secret': '', 'oidc_oauth_secret': '',

View file

@ -74,6 +74,39 @@ def google_oauth():
return google 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(): def oidc_oauth():
if not Setting().get('oidc_oauth_enabled'): if not Setting().get('oidc_oauth_enabled'):
return None return None
@ -105,4 +138,4 @@ def oidc_oauth():
session['oidc_token'] = (token) session['oidc_token'] = (token)
return redirect(url_for('.login')) return redirect(url_for('.login'))
return oidc return oidc

View file

@ -52,6 +52,7 @@
<li class="active"><a href="#tabs-ldap" data-toggle="tab">LDAP</a></li> <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-google" data-toggle="tab">Google OAuth</a></li>
<li><a href="#tabs-github" data-toggle="tab">Github 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> <li><a href="#tabs-oidc" data-toggle="tab">OpenID Connect OAuth</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -359,6 +360,63 @@
</div> </div>
</div> </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="tab-pane" id="tabs-oidc">
<div class="row"> <div class="row">
<div class="col-md-4"> <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 .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 import app, login_manager, csrf
from app.lib import utils 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 app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain
from yaml import Loader, load from yaml import Loader, load
@ -57,9 +57,11 @@ def register_modules():
global google global google
global github global github
global oidc global oidc
global azure
google = google_oauth() google = google_oauth()
github = github_oauth() github = github_oauth()
oidc = oidc_oauth() oidc = oidc_oauth()
azure = azure_oauth()
# START USER AUTHENTICATION HANDLER # START USER AUTHENTICATION HANDLER
@ -223,6 +225,15 @@ def github_login():
redirect_uri = url_for('github_authorized', _external=True) redirect_uri = url_for('github_authorized', _external=True)
return github.authorize_redirect(redirect_uri) 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') @app.route('/oidc/login')
def oidc_login(): def oidc_login():
if not Setting().get('oidc_oauth_enabled') or oidc is None: if not Setting().get('oidc_oauth_enabled') or oidc is None:
@ -450,6 +461,42 @@ def login():
login_user(user, remember = False) login_user(user, remember = False)
return redirect(url_for('index')) 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: if 'oidc_token' in session:
me = json.loads(oidc.get('userinfo').text) me = json.loads(oidc.get('userinfo').text)
oidc_username = me["preferred_username"] oidc_username = me["preferred_username"]