diff --git a/app/__init__.py b/app/__init__.py
index 00c510c..3747067 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -3,7 +3,7 @@ from flask import Flask, request, session, redirect, url_for
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
@@ -29,7 +29,7 @@ login_manager = LoginManager()
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 116053f..fd540ed 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1875,10 +1875,17 @@ class Setting(db.Model):
'google_oauth_enabled': False,
'google_oauth_client_id':'',
'google_oauth_client_secret':'',
- 'google_token_url': 'https://accounts.google.com/o/oauth2/token',
- '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/',
+ 'google_token_url': 'https://oauth2.googleapis.com/token',
+ '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/',
+ '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..a578341 100644
--- a/app/oauth.py
+++ b/app/oauth.py
@@ -1,44 +1,44 @@
from ast import literal_eval
from flask import request, session, redirect, url_for
-from app import app, oauth_client
+from app import app, authlib_oauth_client
from app.models import Setting
# TODO:
-# - Replace Flask-OAuthlib by authlib
# - Fix github/google enabling (Currently need to reload the flask app)
def github_oauth():
if not Setting().get('github_oauth_enabled'):
return None
- github = oauth_client.remote_app(
+ def fetch_github_token():
+ return session.get('github_token')
+
+ github = authlib_oauth_client.register(
'github',
- consumer_key = Setting().get('github_oauth_key'),
- consumer_secret = Setting().get('github_oauth_secret'),
+ client_id = Setting().get('github_oauth_key'),
+ client_secret = Setting().get('github_oauth_secret'),
request_token_params = {'scope': Setting().get('github_oauth_scope')},
- base_url = Setting().get('github_oauth_api_url'),
+ api_base_url = Setting().get('github_oauth_api_url'),
request_token_url = None,
- access_token_method = 'POST',
access_token_url = Setting().get('github_oauth_token_url'),
- authorize_url = Setting().get('github_oauth_authorize_url')
+ authorize_url = Setting().get('github_oauth_authorize_url'),
+ client_kwargs={'scope': Setting().get('github_oauth_scope')},
+ fetch_token=fetch_github_token,
)
@app.route('/github/authorized')
def github_authorized():
session['github_oauthredir'] = url_for('.github_authorized', _external=True)
- resp = github.authorized_response()
- if resp is None:
+ token = github.authorize_access_token()
+ if token is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
- session['github_token'] = (resp['access_token'], '')
+ session['github_token'] = (token)
return redirect(url_for('.login'))
- @github.tokengetter
- def get_github_oauth_token():
- return session.get('github_token')
return github
@@ -47,31 +47,65 @@ def google_oauth():
if not Setting().get('google_oauth_enabled'):
return None
- google = oauth_client.remote_app(
+ def fetch_google_token():
+ return session.get('google_token')
+ print("afkafna")
+
+ google = authlib_oauth_client.register(
'google',
- consumer_key=Setting().get('google_oauth_client_id'),
- consumer_secret=Setting().get('google_oauth_client_secret'),
- request_token_params=literal_eval(Setting().get('google_token_params')),
- base_url=Setting().get('google_base_url'),
+ client_id=Setting().get('google_oauth_client_id'),
+ client_secret=Setting().get('google_oauth_client_secret'),
+ api_base_url=Setting().get('google_base_url'),
request_token_url=None,
- access_token_method='POST',
access_token_url=Setting().get('google_token_url'),
authorize_url=Setting().get('google_authorize_url'),
+ client_kwargs={'scope': Setting().get('google_oauth_scope')},
+ fetch_token=fetch_google_token,
)
@app.route('/google/authorized')
def google_authorized():
- resp = google.authorized_response()
- if resp is None:
+ session['google_oauthredir'] = url_for('.google_authorized', _external=True)
+ token = google.authorize_access_token()
+ if token is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
- session['google_token'] = (resp['access_token'], '')
+ session['google_token'] = (token)
return redirect(url_for('.login'))
- @google.tokengetter
- def get_google_oauth_token():
- 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 8bb629b..9ad623f 100644
--- a/app/templates/admin_setting_authentication.html
+++ b/app/templates/admin_setting_authentication.html
@@ -52,6 +52,7 @@
LDAP
Google OAuth
Github OAuth
+ OpenID Connect OAuth
+
+
+
+
+
+
+
Fill in all the fields in the left form.
+
-
@@ -499,14 +555,14 @@
$('#google_oauth_client_id').prop('required', true);
$('#google_oauth_client_secret').prop('required', true);
$('#google_token_url').prop('required', true);
- $('#google_token_params').prop('required', true);
+ $('#google_oauth_scope').prop('required', true);
$('#google_authorize_url').prop('required', true);
$('#google_base_url').prop('required', true);
} else {
$('#google_oauth_client_id').prop('required', false);
$('#google_oauth_client_secret').prop('required', false);
$('#google_token_url').prop('required', false);
- $('#google_token_params').prop('required', false);
+ $('#google_oauth_scope').prop('required', false);
$('#google_authorize_url').prop('required', false);
$('#google_base_url').prop('required', false);
}
@@ -517,7 +573,7 @@
$('#google_oauth_client_id').prop('required', true);
$('#google_oauth_client_secret').prop('required', true);
$('#google_token_url').prop('required', true);
- $('#google_token_params').prop('required', true);
+ $('#google_oauth_scope').prop('required', true);
$('#google_authorize_url').prop('required', true);
$('#google_base_url').prop('required', true);
{% endif %}
@@ -546,9 +602,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);
@@ -558,5 +613,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 d839cc7..1d51559 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
@@ -160,7 +163,8 @@ def google_login():
logging.error('Google OAuth is disabled or you have not yet reloaded the pda application after enabling.')
return abort(400)
else:
- return google.authorize(callback=url_for('google_authorized', _external=True))
+ redirect_uri = url_for('google_authorized', _external=True)
+ return google.authorize_redirect(redirect_uri)
@app.route('/github/login')
@@ -169,8 +173,18 @@ def github_login():
logging.error('Github OAuth is disabled or you have not yet reloaded the pda application after enabling.')
return abort(400)
else:
- return github.authorize(callback=url_for('github_authorized', _external=True))
+ redirect_uri = url_for('github_authorized', _external=True)
+ return github.authorize_redirect(redirect_uri)
+@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():
@@ -294,11 +308,13 @@ def login():
return redirect(url_for('dashboard'))
if 'google_token' in session:
- user_data = google.get('userinfo').data
+ user_data = json.loads(google.get('userinfo').text)
first_name = user_data['given_name']
surname = user_data['family_name']
email = user_data['email']
user = User.query.filter_by(username=email).first()
+ if user == None:
+ user = User.query.filter_by(email=email).first()
if not user:
user = User(username=email,
firstname=first_name,
@@ -317,13 +333,14 @@ def login():
return redirect(url_for('index'))
if 'github_token' in session:
- me = github.get('user').data
-
+ me = json.loads(github.get('user').text)
github_username = me['login']
github_name = me['name']
github_email = me['email']
user = User.query.filter_by(username=github_username).first()
+ if user == None:
+ user = User.query.filter_by(email=github_email).first()
if not user:
user = User(username=github_username,
plain_text_password=None,
@@ -341,6 +358,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)
@@ -1496,7 +1538,7 @@ def admin_setting_authentication():
Setting().set('google_oauth_client_id', request.form.get('google_oauth_client_id'))
Setting().set('google_oauth_client_secret', request.form.get('google_oauth_client_secret'))
Setting().set('google_token_url', request.form.get('google_token_url'))
- Setting().set('google_token_params', request.form.get('google_token_params'))
+ Setting().set('google_oauth_scope', request.form.get('google_oauth_scope'))
Setting().set('google_authorize_url', request.form.get('google_authorize_url'))
Setting().set('google_base_url', request.form.get('google_base_url'))
result = {'status': True, 'msg': 'Saved successfully. Please reload PDA to take effect.'}
@@ -1509,6 +1551,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..245d0d0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,6 @@
Flask==1.0.2
Flask-Assets==0.12
Flask-Login==0.4.1
-Flask-OAuthlib==0.9.4
Flask-SQLAlchemy==2.3.2
Flask-Migrate==2.2.1
SQLAlchemy==1.2.5
@@ -9,7 +8,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 +18,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)