From 1662944867c998e72186bed0bfea38e06d2e27e2 Mon Sep 17 00:00:00 2001 From: Steve Shipway Date: Thu, 5 Dec 2019 13:21:50 +1300 Subject: [PATCH] Add Azure as an explicit OAuth provider --- app/models.py | 7 +++ app/oauth.py | 35 ++++++++++- .../admin_setting_authentication.html | 58 +++++++++++++++++++ app/views.py | 49 +++++++++++++++- 4 files changed, 147 insertions(+), 2 deletions(-) diff --git a/app/models.py b/app/models.py index 721231e..b5012ed 100644 --- a/app/models.py +++ b/app/models.py @@ -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': '', diff --git a/app/oauth.py b/app/oauth.py index e42bbfb..afc7858 100644 --- a/app/oauth.py +++ b/app/oauth.py @@ -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 @@ -105,4 +138,4 @@ def oidc_oauth(): session['oidc_token'] = (token) return redirect(url_for('.login')) - return oidc \ No newline at end of file + return oidc diff --git a/app/templates/admin_setting_authentication.html b/app/templates/admin_setting_authentication.html index 6e634b1..165f688 100644 --- a/app/templates/admin_setting_authentication.html +++ b/app/templates/admin_setting_authentication.html @@ -52,6 +52,7 @@
  • LDAP
  • Google OAuth
  • Github OAuth
  • +
  • Microsoft OAuth
  • OpenID Connect OAuth
  • @@ -359,6 +360,63 @@
    +
    +
    +
    +
    + + +
    + GENERAL +
    + + +
    +
    + + + +
    +
    + + + +
    +
    +
    + ADVANCE +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    + +
    +
    +
    +
    + Help +

    Fill in all the fields in the left form.

    +
    +
    +
    diff --git a/app/views.py b/app/views.py index b94cbff..b869c4a 100755 --- a/app/views.py +++ b/app/views.py @@ -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"]