Merge remote-tracking branch 'chriscpritchard/master'

This commit is contained in:
Khanh Ngo 2018-10-25 15:26:42 +07:00
commit e273921195
8 changed files with 236 additions and 53 deletions

View File

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

View File

@ -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},
}

View File

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

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-oidc" data-toggle="tab">OpenID Connect OAuth</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabs-general">
@ -271,8 +272,8 @@
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="google_token_params">Token params</label>
<input type="text" class="form-control" name="google_token_params" id="google_token_params" placeholder="e.g. {'scope': 'email profile'}" data-error="Please input token params" value="{{ SETTING.get('google_token_params') }}">
<label for="google_oauth_scope">Scope</label>
<input type="text" class="form-control" name="google_oauth_scope" id="google_oauth_scope" placeholder="e.g. email profile" data-error="Please input scope" value="{{ SETTING.get('google_oauth_scope') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
@ -353,7 +354,62 @@
<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">
<form role="form" method="post" data-toggle="validator">
<input type="hidden" value="oidc" name="config_tab" />
<fieldset>
<legend>GENERAL</legend>
<div class="form-group">
<input type="checkbox" id="oidc_oauth_enabled" name="oidc_oauth_enabled" class="checkbox" {% if SETTING.get('oidc_oauth_enabled') %}checked{% endif %}>
<label for="oidc_oauth_enabled">Enable OpenID Connect OAuth</label>
</div>
<div class="form-group">
<label for="oidc_oauth_key">Client key</label>
<input type="text" class="form-control" name="oidc_oauth_key" id="oidc_oauth_key" placeholder="OIDC OAuth client ID" data-error="Please input Client key" value="{{ SETTING.get('oidc_oauth_key') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="oidc_oauth_secret">Client secret</label>
<input type="text" class="form-control" name="oidc_oauth_secret" id="oidc_oauth_secret" placeholder="OIDC OAuth client secret" data-error="Please input Client secret" value="{{ SETTING.get('oidc_oauth_secret') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<fieldset>
<legend>ADVANCE</legend>
<div class="form-group">
<label for="oidc_oauth_scope">Scope</label>
<input type="text" class="form-control" name="oidc_oauth_scope" id="oidc_oauth_scope" placeholder="e.g. email" data-error="Please input scope" value="{{ SETTING.get('oidc_oauth_scope') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="oidc_oauth_api_url">API URL</label>
<input type="text" class="form-control" name="oidc_oauth_api_url" id="oidc_oauth_api_url" placeholder="e.g. https://api.oidc.com/user" data-error="Please input API URL" value="{{ SETTING.get('oidc_oauth_api_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="oidc_oauth_token_url">Token URL</label>
<input type="text" class="form-control" name="oidc_oauth_token_url" id="oidc_oauth_token_url" placeholder="e.g. https://oidc.com/login/oauth/access_token" data-error="Please input Token URL" value="{{ SETTING.get('oidc_oauth_token_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="oidc_oauth_authorize_url">Authorize URL</label>
<input type="text" class="form-control" name="oidc_oauth_authorize_url" id="oidc_oauth_authorize_url" placeholder="e.g. https://oidc.com/login/oauth/authorize" data-error="Plesae input Authorize URL" value="{{ SETTING.get('oidc_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>
</div>
@ -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
</script>
{% endblock %}

View File

@ -83,14 +83,17 @@
<!-- /.col -->
</div>
</form>
{% 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') %}
<div class="social-auth-links text-center">
<p>- OR -</p>
{% if SETTING.get('oidc_oauth_enabled') %}
<a href="{{ url_for('oidc_login') }}" class="btn btn-block btn-social btn-openid btn-flat"><i class="fa fa-openid"></i> Sign in using
OpenID Connect</a>
{% endif %}
{% if SETTING.get('github_oauth_enabled') %}
<a href="{{ url_for('github_login') }}" class="btn btn-block btn-social btn-github btn-flat"><i class="fa fa-github"></i> Sign in using
Github</a>
{% endif %}
{% if SETTING.get('google_oauth_enabled') %}
<a href="{{ url_for('google_login') }}" class="btn btn-block btn-social btn-google btn-flat"><i class="fa fa-google-plus"></i> Sign in using
Google</a>

View File

@ -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,7 +173,17 @@ 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')
@ -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)

View File

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

2
run.py
View File

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