diff --git a/app/__init__.py b/app/__init__.py
index 00c510c..fe9004b 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -4,6 +4,7 @@ from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy as SA
from flask_migrate import Migrate
from flask_oauthlib.client import OAuth
+from authlib.flask.client import OAuth as AuthlibOAuth
from sqlalchemy.exc import OperationalError
# subclass SQLAlchemy to enable pool_pre_ping
@@ -30,6 +31,7 @@ login_manager.init_app(app)
db = SQLAlchemy(app) # database
migrate = Migrate(app, db) # flask-migrate
oauth_client = OAuth(app) # oauth
+authlib_oauth_client = AuthlibOAuth(app) # authlib oauth
if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
from app.lib import certutil
diff --git a/app/models.py b/app/models.py
index 2710080..c2306f8 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1840,6 +1840,13 @@ class Setting(db.Model):
'google_token_params': {'scope': 'email profile'},
'google_authorize_url':'https://accounts.google.com/o/oauth2/auth',
'google_base_url':'https://www.googleapis.com/oauth2/v1/',
+ 'oidc_oauth_enabled': False,
+ 'oidc_oauth_key': '',
+ 'oidc_oauth_secret': '',
+ 'oidc_oauth_scope': 'email',
+ 'oidc_oauth_api_url': '',
+ 'oidc_oauth_token_url': '',
+ 'oidc_oauth_authorize_url': '',
'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, '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, '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},
}
diff --git a/app/oauth.py b/app/oauth.py
index 17fd045..bb8e7a9 100644
--- a/app/oauth.py
+++ b/app/oauth.py
@@ -1,7 +1,7 @@
from ast import literal_eval
from flask import request, session, redirect, url_for
-from app import app, oauth_client
+from app import app, oauth_client, authlib_oauth_client
from app.models import Setting
# TODO:
@@ -75,3 +75,36 @@ def google_oauth():
return session.get('google_token')
return google
+
+def oidc_oauth():
+ if not Setting().get('oidc_oauth_enabled'):
+ return None
+
+ def fetch_oidc_token():
+ return session.get('oidc_token')
+
+ oidc = authlib_oauth_client.register(
+ 'oidc',
+ client_id = Setting().get('oidc_oauth_key'),
+ client_secret = Setting().get('oidc_oauth_secret'),
+ api_base_url = Setting().get('oidc_oauth_api_url'),
+ request_token_url = None,
+ access_token_url = Setting().get('oidc_oauth_token_url'),
+ authorize_url = Setting().get('oidc_oauth_authorize_url'),
+ client_kwargs={'scope': Setting().get('oidc_oauth_scope')},
+ fetch_token=fetch_oidc_token,
+ )
+
+ @app.route('/oidc/authorized')
+ def oidc_authorized():
+ session['oidc_oauthredir'] = url_for('.oidc_authorized', _external=True)
+ token = oidc.authorize_access_token()
+ if token is None:
+ return 'Access denied: reason=%s error=%s' % (
+ request.args['error'],
+ request.args['error_description']
+ )
+ session['oidc_token'] = (token)
+ return redirect(url_for('.login'))
+
+ return oidc
\ No newline at end of file
diff --git a/app/templates/admin_setting_authentication.html b/app/templates/admin_setting_authentication.html
index 87c06a2..605f3de 100644
--- a/app/templates/admin_setting_authentication.html
+++ b/app/templates/admin_setting_authentication.html
@@ -37,6 +37,7 @@
LDAP
Google OAuth
Github OAuth
+ OpenID Connect OAuth
@@ -325,8 +326,63 @@
Fill in all the fields in the left form.
+
+
+
+
+
+
+
+
Fill in all the fields in the left form.
+
-
@@ -487,9 +543,8 @@
$('#github_oauth_authorize_url').prop('required', false);
}
});
-
// init validation requirement at first time page load
- {% if SETTING.get('google_oauth_enabled') %}
+ {% if SETTING.get('github_oauth_enabled') %}
$('#github_oauth_key').prop('required', true);
$('#github_oauth_secret').prop('required', true);
$('#github_oauth_scope').prop('required', true);
@@ -499,5 +554,38 @@
{% endif %}
// END: Github tab js
+ // START: OIDC tab js
+ $('#oidc_oauth_enabled').iCheck({
+ checkboxClass : 'icheckbox_square-blue',
+ increaseArea : '20%'
+ }).on('ifChanged', function(e) {
+ var is_enabled = e.currentTarget.checked;
+ if (is_enabled){
+ $('#oidc_oauth_key').prop('required', true);
+ $('#oidc_oauth_secret').prop('required', true);
+ $('#oidc_oauth_scope').prop('required', true);
+ $('#oidc_oauth_api_url').prop('required', true);
+ $('#oidc_oauth_token_url').prop('required', true);
+ $('#oidc_oauth_authorize_url').prop('required', true);
+ } else {
+ $('#oidc_oauth_key').prop('required', false);
+ $('#oidc_oauth_secret').prop('required', false);
+ $('#oidc_oauth_scope').prop('required', false);
+ $('#oidc_oauth_api_url').prop('required', false);
+ $('#oidc_oauth_token_url').prop('required', false);
+ $('#oidc_oauth_authorize_url').prop('required', false);
+ }
+ });
+ // init validation requirement at first time page load
+ {% if SETTING.get('oidc_oauth_enabled') %}
+ $('#oidc_oauth_key').prop('required', true);
+ $('#oidc_oauth_secret').prop('required', true);
+ $('#oidc_oauth_scope').prop('required', true);
+ $('#oidc_oauth_api_url').prop('required', true);
+ $('#oidc_oauth_token_url').prop('required', true);
+ $('#oidc_oauth_authorize_url').prop('required', true);
+ {% endif %}
+ //END: OIDC Tab JS
+
{% endblock %}
diff --git a/app/templates/login.html b/app/templates/login.html
index 54c24bc..46ae8c0 100644
--- a/app/templates/login.html
+++ b/app/templates/login.html
@@ -83,14 +83,17 @@
- {% if SETTING.get('google_oauth_enabled') or SETTING.get('github_oauth_enabled') %}
+ {% if SETTING.get('google_oauth_enabled') or SETTING.get('github_oauth_enabled') or SETTING.get('oidc_oauth_enabled') %}
- OR -
+ {% if SETTING.get('oidc_oauth_enabled') %}
+
Sign in using
+ OpenID Connect
+ {% endif %}
{% if SETTING.get('github_oauth_enabled') %}
Sign in using
Github
{% endif %}
-
{% if SETTING.get('google_oauth_enabled') %}
Sign in using
Google
diff --git a/app/views.py b/app/views.py
index 7cc7687..d1fd98a 100644
--- a/app/views.py
+++ b/app/views.py
@@ -4,6 +4,7 @@ import os
import traceback
import re
import datetime
+import json
from distutils.util import strtobool
from distutils.version import StrictVersion
from functools import wraps
@@ -19,7 +20,7 @@ from werkzeug import secure_filename
from .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager
from app.lib import utils
-from app.oauth import github_oauth, google_oauth
+from app.oauth import github_oauth, google_oauth, oidc_oauth
from app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain
if app.config['SAML_ENABLED']:
@@ -53,8 +54,10 @@ def inject_setting():
def register_modules():
global google
global github
+ global oidc
google = google_oauth()
github = github_oauth()
+ oidc = oidc_oauth()
# START USER AUTHENTICATION HANDLER
@@ -171,6 +174,15 @@ def github_login():
else:
return github.authorize(callback=url_for('github_authorized', _external=True))
+@app.route('/oidc/login')
+def oidc_login():
+ if not Setting().get('oidc_oauth_enabled') or oidc is None:
+ logging.error('OIDC OAuth is disabled or you have not yet reloaded the pda application after enabling.')
+ return abort(400)
+ else:
+ redirect_uri = url_for('oidc_authorized', _external=True)
+ return oidc.authorize_redirect(redirect_uri)
+
@app.route('/saml/login')
def saml_login():
@@ -341,6 +353,31 @@ def login():
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"]
+ oidc_givenname = me["name"]
+ oidc_familyname = ""
+ oidc_email = me["email"]
+
+ user = User.query.filter_by(username=oidc_username).first()
+ if not user:
+ user = User(username=oidc_username,
+ plain_text_password=None,
+ firstname=oidc_givenname,
+ lastname=oidc_familyname,
+ email=oidc_email)
+
+ result = user.create_local_user()
+ if not result['status']:
+ session.pop('oidc_token', None)
+ return redirect(url_for('login'))
+
+ session['user_id'] = user.id
+ session['authentication_type'] = 'OAuth'
+ login_user(user, remember = False)
+ return redirect(url_for('index'))
+
if request.method == 'GET':
return render_template('login.html', saml_enabled=SAML_ENABLED)
@@ -1508,6 +1545,15 @@ def admin_setting_authentication():
Setting().set('github_oauth_token_url', request.form.get('github_oauth_token_url'))
Setting().set('github_oauth_authorize_url', request.form.get('github_oauth_authorize_url'))
result = {'status': True, 'msg': 'Saved successfully. Please reload PDA to take effect.'}
+ elif conf_type == 'oidc':
+ Setting().set('oidc_oauth_enabled', True if request.form.get('oidc_oauth_enabled') else False)
+ Setting().set('oidc_oauth_key', request.form.get('oidc_oauth_key'))
+ Setting().set('oidc_oauth_secret', request.form.get('oidc_oauth_secret'))
+ Setting().set('oidc_oauth_scope', request.form.get('oidc_oauth_scope'))
+ Setting().set('oidc_oauth_api_url', request.form.get('oidc_oauth_api_url'))
+ Setting().set('oidc_oauth_token_url', request.form.get('oidc_oauth_token_url'))
+ Setting().set('oidc_oauth_authorize_url', request.form.get('oidc_oauth_authorize_url'))
+ result = {'status': True, 'msg': 'Saved successfully. Please reload PDA to take effect.'}
else:
return abort(400)
diff --git a/requirements.txt b/requirements.txt
index 00c142d..04eaa3b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,7 +9,7 @@ mysqlclient==1.3.12
configobj==5.0.6
bcrypt==3.1.4
requests==2.18.4
-python-ldap==3.0.0
+python-ldap==3.1.0
pyotp==2.2.6
qrcode==6.0
dnspython==1.15.0
@@ -19,3 +19,4 @@ pyOpenSSL>=0.15
pytz>=2017.3
cssmin==0.2.0
jsmin==2.2.2
+Authlib==0.10
\ No newline at end of file
diff --git a/run.py b/run.py
index fc7d651..588a642 100755
--- a/run.py
+++ b/run.py
@@ -4,4 +4,4 @@ from config import PORT
from config import BIND_ADDRESS
if __name__ == '__main__':
- app.run(debug = True, host=BIND_ADDRESS, port=PORT)
+ app.run(debug = True, host=BIND_ADDRESS, port=PORT, use_reloader=False)