Merge pull request #103 from timfeirg/master

support github oauth2 login
This commit is contained in:
Khanh Ngo 2016-08-14 09:28:15 +07:00 committed by GitHub
commit 7ef76484d0
6 changed files with 143 additions and 64 deletions

1
.gitignore vendored
View file

@ -27,3 +27,4 @@ logfile.log
db_repository/* db_repository/*
upload/avatar/* upload/avatar/*
tmp/* tmp/*
.ropeproject

View file

@ -1,7 +1,7 @@
from werkzeug.contrib.fixers import ProxyFix from werkzeug.contrib.fixers import ProxyFix
from flask import Flask from flask import Flask, request, session, redirect, url_for
from flask.ext.login import LoginManager from flask_login import LoginManager
from flask.ext.sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) app = Flask(__name__)
app.config.from_object('config') app.config.from_object('config')
@ -11,4 +11,42 @@ login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
db = SQLAlchemy(app) db = SQLAlchemy(app)
def enable_github_oauth(GITHUB_ENABLE):
if not GITHUB_ENABLE:
return None, None
from flask_oauthlib.client import OAuth
oauth = OAuth(app)
github = oauth.remote_app(
'github',
consumer_key=app.config['GITHUB_OAUTH_KEY'],
consumer_secret=app.config['GITHUB_OAUTH_SECRET'],
request_token_params={'scope': app.config['GITHUB_OAUTH_SCOPE']},
base_url=app.config['GITHUB_OAUTH_URL'],
request_token_url=None,
access_token_method='POST',
access_token_url=app.config['GITHUB_OAUTH_TOKEN'],
authorize_url=app.config['GITHUB_OAUTH_AUTHORIZE']
)
@app.route('/user/authorized')
def authorized():
session['github_oauthredir'] = url_for('.authorized', _external=True)
resp = github.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
session['github_token'] = (resp['access_token'], '')
return redirect(url_for('.login'))
@github.tokengetter
def get_github_oauth_token():
return session.get('github_token')
return oauth, github
oauth, github = enable_github_oauth(app.config.get('GITHUB_OAUTH_ENABLE'))
from app import views, models from app import views, models

View file

@ -58,7 +58,6 @@
<div class="form-group"> <div class="form-group">
<input type="otptoken" class="form-control" placeholder="OTP Token" name="otptoken"> <input type="otptoken" class="form-control" placeholder="OTP Token" name="otptoken">
</div> </div>
{% if ldap_enabled and basic_enabled %} {% if ldap_enabled and basic_enabled %}
<div class="form-group"> <div class="form-group">
<select class="form-control" name="auth_method"> <select class="form-control" name="auth_method">
@ -69,21 +68,21 @@
<option value="LDAP">LDAP Authentication</option> <option value="LDAP">LDAP Authentication</option>
{% endif %} {% endif %}
</select> </select>
</div> </div>
{% elif ldap_enabled and not basic_enabled %} {% elif ldap_enabled and not basic_enabled %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="auth_method" value="LDAP"> <input type="hidden" name="auth_method" value="LDAP">
</div> </div>
{% elif basic_enabled and not ldap_enabled %} {% elif basic_enabled and not ldap_enabled %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="auth_method" value="LOCAL"> <input type="hidden" name="auth_method" value="LOCAL">
</div> </div>
{% else %} {% else %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="auth_method" value="LOCAL"> <input type="hidden" name="auth_method" value="LOCAL">
</div> </div>
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-xs-8"> <div class="col-xs-8">
<div class="checkbox icheck"> <div class="checkbox icheck">
@ -99,6 +98,10 @@
<!-- /.col --> <!-- /.col -->
</div> </div>
</form> </form>
{% if github_enabled %}
<a href="{{ url_for('github_login') }}">Github oauth login</a>
{% endif %}
<br>
{% if signup_enabled %} {% if signup_enabled %}
<a href="{{ url_for('register') }}" class="text-center">Create an account </a> <a href="{{ url_for('register') }}" class="text-center">Create an account </a>
{% endif %} {% endif %}

View file

@ -1,23 +1,23 @@
import os
import json
import jinja2
import traceback
import pyqrcode
import base64 import base64
import json
from functools import wraps import os
from flask_login import login_user, logout_user, current_user, login_required import traceback
from flask import Flask, g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory
from werkzeug import secure_filename
from lib import utils
from app import app, login_manager
from .models import User, Role, Domain, DomainUser, Record, Server, History, Anonymous, Setting, DomainSetting
from io import BytesIO
from distutils.util import strtobool from distutils.util import strtobool
from distutils.version import StrictVersion from distutils.version import StrictVersion
from optparse import Values from functools import wraps
from io import BytesIO
import jinja2
import pyqrcode
from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug import secure_filename
from werkzeug.security import gen_salt
from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting
from app import app, login_manager, github
from lib import utils
jinja2.filters.FILTERS['display_record_name'] = utils.display_record_name jinja2.filters.FILTERS['display_record_name'] = utils.display_record_name
jinja2.filters.FILTERS['display_master_name'] = utils.display_master_name jinja2.filters.FILTERS['display_master_name'] = utils.display_master_name
@ -153,6 +153,12 @@ def register():
else: else:
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
@app.route('/github/login')
def github_login():
if not app.config.get('GITHUB_OAUTH_ENABLE'):
return abort(400)
return github.authorize(callback=url_for('authorized', _external=True))
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
@login_manager.unauthorized_handler @login_manager.unauthorized_handler
def login(): def login():
@ -161,25 +167,44 @@ def login():
LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else '' LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else ''
BASIC_ENABLED = app.config['BASIC_ENABLED'] BASIC_ENABLED = app.config['BASIC_ENABLED']
SIGNUP_ENABLED = app.config['SIGNUP_ENABLED'] SIGNUP_ENABLED = app.config['SIGNUP_ENABLED']
GITHUB_ENABLE = app.config.get('GITHUB_OAUTH_ENABLE')
if g.user is not None and current_user.is_authenticated: if g.user is not None and current_user.is_authenticated:
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
if 'github_token' in session:
me = github.get('user')
user_info = me.data
user = User.query.filter_by(username=user_info['name']).first()
if not user:
# create user
user = User(username=user_info['name'],
plain_text_password=gen_salt(7),
email=user_info['email'])
user.create_local_user()
session['user_id'] = user.id
login_user(user, remember = False)
return redirect(url_for('index'))
if request.method == 'GET': if request.method == 'GET':
return render_template('login.html', ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) return render_template('login.html',
github_enabled=GITHUB_ENABLE,
ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
# process login # process login
username = request.form['username'] username = request.form['username']
password = request.form['password'] password = request.form['password']
otp_token = request.form['otptoken'] if 'otptoken' in request.form else None otp_token = request.form.get('otptoken')
auth_method = request.form['auth_method'] if 'auth_method' in request.form else 'LOCAL' auth_method = request.form.get('auth_method', 'LOCAL')
# addition fields for registration case # addition fields for registration case
firstname = request.form['firstname'] if 'firstname' in request.form else None firstname = request.form.get('firstname')
lastname = request.form['lastname'] if 'lastname' in request.form else None lastname = request.form.get('lastname')
email = request.form['email'] if 'email' in request.form else None email = request.form.get('email')
rpassword = request.form['rpassword'] if 'rpassword' in request.form else None rpassword = request.form.get('rpassword')
if None in [firstname, lastname, email]: if None in [firstname, lastname, email]:
#login case #login case
remember_me = False remember_me = False
@ -210,13 +235,13 @@ def login():
else: else:
# registration case # registration case
user = User(username=username, plain_text_password=password, firstname=firstname, lastname=lastname, email=email) user = User(username=username, plain_text_password=password, firstname=firstname, lastname=lastname, email=email)
# TODO: Move this into the JavaScript # TODO: Move this into the JavaScript
# validate password and password confirmation # validate password and password confirmation
if password != rpassword: if password != rpassword:
error = "Passsword and confirmation do not match" error = "Passsword and confirmation do not match"
return render_template('register.html', error=error) return render_template('register.html', error=error)
try: try:
result = user.create_local_user() result = user.create_local_user()
if result == True: if result == True:
@ -229,8 +254,10 @@ def login():
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
session.pop('user_id', None)
session.pop('github_token', None)
logout_user() logout_user()
return redirect(url_for('login')) return redirect(url_for('login'))
@app.route('/dashboard', methods=['GET', 'POST']) @app.route('/dashboard', methods=['GET', 'POST'])
@ -328,7 +355,7 @@ def domain_add():
def domain_delete(domain_name): def domain_delete(domain_name):
d = Domain() d = Domain()
result = d.delete(domain_name) result = d.delete(domain_name)
if result['status'] == 'error': if result['status'] == 'error':
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
@ -362,7 +389,7 @@ def domain_management(domain_name):
d = Domain(name=domain_name) d = Domain(name=domain_name)
domain_user_ids = d.get_user() domain_user_ids = d.get_user()
# grant/revoke user privielges # grant/revoke user privielges
d.grant_privielges(new_user_list) d.grant_privielges(new_user_list)
history = History(msg='Change domain %s access control' % domain_name, detail=str({'user_has_access': new_user_list}), created_by=current_user.username) history = History(msg='Change domain %s access control' % domain_name, detail=str({'user_has_access': new_user_list}), created_by=current_user.username)
@ -442,7 +469,7 @@ def record_delete(domain_name, record_name, record_type):
print result['msg'] print result['msg']
except: except:
print traceback.format_exc() print traceback.format_exc()
return redirect(url_for('error', code=500)), 500 return redirect(url_for('error', code=500)), 500
return redirect(url_for('domain', domain_name=domain_name)) return redirect(url_for('domain', domain_name=domain_name))
@ -471,7 +498,7 @@ def admin_setdomainsetting(domain_name):
new_value = str(data['value']) new_value = str(data['value'])
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
setting = DomainSetting.query.filter(DomainSetting.domain == domain).filter(DomainSetting.setting == new_setting).first() setting = DomainSetting.query.filter(DomainSetting.domain == domain).filter(DomainSetting.setting == new_setting).first()
if setting: if setting:
if setting.set(new_value): if setting.set(new_value):
history = History(msg='Setting %s changed value to %s for %s' % (new_setting, new_value, domain.name), created_by=current_user.username) history = History(msg='Setting %s changed value to %s for %s' % (new_setting, new_value, domain.name), created_by=current_user.username)
@ -485,7 +512,7 @@ def admin_setdomainsetting(domain_name):
history.add() history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'New setting created and updated.' } )) return make_response(jsonify( { 'status': 'ok', 'msg': 'New setting created and updated.' } ))
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to create new setting.' } )) return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to create new setting.' } ))
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except: except:
@ -499,12 +526,12 @@ def admin_setdomainsetting(domain_name):
def admin(): def admin():
domains = Domain.query.all() domains = Domain.query.all()
users = User.query.all() users = User.query.all()
server = Server(server_id='localhost') server = Server(server_id='localhost')
configs = server.get_config() configs = server.get_config()
statistics = server.get_statistic() statistics = server.get_statistic()
history_number = History.query.count() history_number = History.query.count()
if statistics: if statistics:
uptime = filter(lambda uptime: uptime['name'] == 'uptime', statistics)[0]['value'] uptime = filter(lambda uptime: uptime['name'] == 'uptime', statistics)[0]['value']
else: else:
@ -518,20 +545,20 @@ def admin():
def admin_createuser(): def admin_createuser():
if request.method == 'GET': if request.method == 'GET':
return render_template('admin_createuser.html') return render_template('admin_createuser.html')
if request.method == 'POST': if request.method == 'POST':
fdata = request.form fdata = request.form
user = User(username=fdata['username'], plain_text_password=fdata['password'], firstname=fdata['firstname'], lastname=fdata['lastname'], email=fdata['email']) user = User(username=fdata['username'], plain_text_password=fdata['password'], firstname=fdata['firstname'], lastname=fdata['lastname'], email=fdata['email'])
if fdata['password'] == "": if fdata['password'] == "":
return render_template('admin_createuser.html', user=user, blank_password=True) return render_template('admin_createuser.html', user=user, blank_password=True)
result = user.create_local_user(); result = user.create_local_user();
if result == 'Email already existed': if result == 'Email already existed':
return render_template('admin_createuser.html', user=user, duplicate_email=True) return render_template('admin_createuser.html', user=user, duplicate_email=True)
if result == 'Username already existed': if result == 'Username already existed':
return render_template('admin_createuser.html', user=user, duplicate_username=True) return render_template('admin_createuser.html', user=user, duplicate_username=True)
@ -564,7 +591,7 @@ def admin_manageuser():
return make_response(jsonify( { 'status': 'ok', 'msg': 'User has been removed.' } ), 200) return make_response(jsonify( { 'status': 'ok', 'msg': 'User has been removed.' } ), 200)
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot remove user.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot remove user.' } ), 500)
elif jdata['action'] == 'revoke_user_privielges': elif jdata['action'] == 'revoke_user_privielges':
user = User(username=data) user = User(username=data)
result = user.revoke_privilege() result = user.revoke_privilege()
@ -573,8 +600,8 @@ def admin_manageuser():
history.add() history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Revoked user privielges.' } ), 200) return make_response(jsonify( { 'status': 'ok', 'msg': 'Revoked user privielges.' } ), 200)
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500)
elif jdata['action'] == 'set_admin': elif jdata['action'] == 'set_admin':
username = data['username'] username = data['username']
is_admin = data['is_admin'] is_admin = data['is_admin']
@ -608,7 +635,7 @@ def admin_history():
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Can not remove histories.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Can not remove histories.' } ), 500)
if request.method == 'GET': if request.method == 'GET':
histories = History.query.all() histories = History.query.all()
return render_template('admin_history.html', histories=histories) return render_template('admin_history.html', histories=histories)
@ -616,10 +643,10 @@ def admin_history():
@login_required @login_required
@admin_role_required @admin_role_required
def admin_settings(): def admin_settings():
if request.method == 'GET': if request.method == 'GET':
settings = Setting.query.filter(Setting.name != 'maintenance') settings = Setting.query.filter(Setting.name != 'maintenance')
return render_template('admin_settings.html', settings=settings) return render_template('admin_settings.html', settings=settings)
@app.route('/admin/setting/<string:setting>/toggle', methods=['POST']) @app.route('/admin/setting/<string:setting>/toggle', methods=['POST'])
@login_required @login_required
@admin_role_required @admin_role_required
@ -673,8 +700,8 @@ def user_profile():
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
file_extension = filename.rsplit('.', 1)[1] file_extension = filename.rsplit('.', 1)[1]
if file_extension.lower() in ['jpg', 'jpeg', 'png']: if file_extension.lower() in ['jpg', 'jpeg', 'png']:
save_file_name = current_user.username + '.' + file_extension save_file_name = current_user.username + '.' + file_extension
file.save(os.path.join(app.config['UPLOAD_DIR'], 'avatar', save_file_name)) file.save(os.path.join(app.config['UPLOAD_DIR'], 'avatar', save_file_name))
@ -731,7 +758,7 @@ def dyndns_update():
domains = User(id=current_user.id).get_domain() domains = User(id=current_user.id).get_domain()
except: except:
return render_template('dyndns.html', response='911'), 200 return render_template('dyndns.html', response='911'), 200
domain = None domain = None
domain_segments = hostname.split('.') domain_segments = hostname.split('.')
for index in range(len(domain_segments)): for index in range(len(domain_segments)):
@ -741,12 +768,12 @@ def dyndns_update():
if potential_domain in domains: if potential_domain in domains:
domain = potential_domain domain = potential_domain
break break
if not domain: if not domain:
history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username)
history.add() history.add()
return render_template('dyndns.html', response='nohost'), 200 return render_template('dyndns.html', response='nohost'), 200
r = Record() r = Record()
r.name = hostname r.name = hostname
# check if the user requested record exists within this domain # check if the user requested record exists within this domain
@ -774,7 +801,7 @@ def dyndns_update():
history = History(msg='DynDNS update: created record %s in zone %s, it now represents %s' % (hostname,domain.name,myip), detail=str(result), created_by=current_user.username) history = History(msg='DynDNS update: created record %s in zone %s, it now represents %s' % (hostname,domain.name,myip), detail=str(result), created_by=current_user.username)
history.add() history.add()
return render_template('dyndns.html', response='good'), 200 return render_template('dyndns.html', response='good'), 200
history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username)
history.add() history.add()
return render_template('dyndns.html', response='nohost'), 200 return render_template('dyndns.html', response='nohost'), 200
@ -783,6 +810,6 @@ def dyndns_update():
@app.route('/', methods=['GET', 'POST']) @app.route('/', methods=['GET', 'POST'])
@login_required @login_required
def index(): def index():
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
# END VIEWS # END VIEWS

View file

@ -34,6 +34,15 @@ LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me'
LDAP_USERNAMEFIELD = 'uid' LDAP_USERNAMEFIELD = 'uid'
LDAP_FILTER = '(objectClass=inetorgperson)' LDAP_FILTER = '(objectClass=inetorgperson)'
# Github Oauth
GITHUB_OAUTH_ENABLE = False
GITHUB_OAUTH_KEY = 'G0j1Q15aRsn36B3aD6nwKLiYbeirrUPU8nDd1wOC'
GITHUB_OAUTH_SECRET = '0WYrKWePeBDkxlezzhFbDn1PBnCwEa0vCwVFvy6iLtgePlpT7WfUlAa9sZgm'
GITHUB_OAUTH_SCOPE = 'email'
GITHUB_OAUTH_URL = 'http://127.0.0.1:5000/api/v3/'
GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:5000/oauth/token'
GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize'
#Default Auth #Default Auth
BASIC_ENABLED = True BASIC_ENABLED = True
SIGNUP_ENABLED = True SIGNUP_ENABLED = True

View file

@ -10,3 +10,4 @@ SQLAlchemy==1.0.9
sqlalchemy-migrate==0.10.0 sqlalchemy-migrate==0.10.0
onetimepass==1.0.1 onetimepass==1.0.1
PyQRCode==1.2 PyQRCode==1.2
Flask-OAuthlib==0.9.3