ldap first pass
This commit is contained in:
parent
3c2f4a0445
commit
2eaf09dc78
|
@ -17,15 +17,15 @@ if ! type "add-apt-repository" > /dev/null; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Elastic Search
|
# Elastic Search
|
||||||
wget -qO - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | sudo apt-key add -
|
# wget -qO - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | sudo apt-key add -
|
||||||
echo 'deb http://packages.elasticsearch.org/elasticsearch/1.4/debian stable main' | sudo tee /etc/apt/sources.list.d/elastic.list
|
# echo 'deb http://packages.elasticsearch.org/elasticsearch/1.4/debian stable main' | sudo tee /etc/apt/sources.list.d/elastic.list
|
||||||
|
|
||||||
sudo add-apt-repository -y ppa:chris-lea/node.js
|
sudo add-apt-repository -y ppa:chris-lea/node.js
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
|
||||||
sudo apt-get install -y python build-essential pkg-config git \
|
sudo apt-get install -y python build-essential pkg-config git \
|
||||||
python-pip python-virtualenv python-dev zlib1g-dev \
|
python-pip python-virtualenv python-dev zlib1g-dev libldap2-dev libsasl2-dev \
|
||||||
libffi-dev libyaml-dev libssl-dev nodejs openjdk-7-jre-headless elasticsearch
|
libffi-dev libyaml-dev libssl-dev nodejs
|
||||||
|
|
||||||
# Create swap file because ES eats up RAM and 14.04 doesn't have swap by default
|
# Create swap file because ES eats up RAM and 14.04 doesn't have swap by default
|
||||||
sudo fallocate -l 1G /swapfile
|
sudo fallocate -l 1G /swapfile
|
||||||
|
|
|
@ -15,6 +15,7 @@ from flask.ext.cache import Cache
|
||||||
from flask.ext.login import LoginManager, current_user
|
from flask.ext.login import LoginManager, current_user
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
from flask.ext.assets import Environment, Bundle
|
from flask.ext.assets import Environment, Bundle
|
||||||
|
from flask_ldap_login import LDAPLoginManager
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
@ -163,6 +164,7 @@ def create_app(config=None):
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
assets.init_app(app)
|
assets.init_app(app)
|
||||||
search.init_app(app)
|
search.init_app(app)
|
||||||
|
ldap.init_app(app)
|
||||||
|
|
||||||
db.Model = declarative_base(metaclass=HookModelMeta, cls=HookMixin)
|
db.Model = declarative_base(metaclass=HookModelMeta, cls=HookMixin)
|
||||||
|
|
||||||
|
@ -202,6 +204,7 @@ db = SQLAlchemy()
|
||||||
cache = Cache()
|
cache = Cache()
|
||||||
assets = Assets()
|
assets = Assets()
|
||||||
search = Search()
|
search = Search()
|
||||||
|
ldap = LDAPLoginManager()
|
||||||
|
|
||||||
assets.register('main.js',
|
assets.register('main.js',
|
||||||
'vendor/jquery/dist/jquery.js',
|
'vendor/jquery/dist/jquery.js',
|
||||||
|
|
|
@ -83,6 +83,38 @@ DB_URI = 'sqlite:////tmp/wiki.db'
|
||||||
# DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname'
|
# DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname'
|
||||||
# DB_URI = 'crate://'
|
# DB_URI = 'crate://'
|
||||||
|
|
||||||
|
LDAP = {
|
||||||
|
'URI': 'ldap://localhost:8389',
|
||||||
|
|
||||||
|
# This BIND_DN/BIND_PASSORD default to '', this is shown here for demonstrative purposes
|
||||||
|
# The values '' perform an anonymous bind so we may use search/bind method
|
||||||
|
'BIND_DN': '',
|
||||||
|
'BIND_AUTH': '',
|
||||||
|
|
||||||
|
# Adding the USER_SEARCH field tells the flask-ldap-login that we are using
|
||||||
|
# the search/bind method
|
||||||
|
'USER_SEARCH': {'base': 'dc=example,dc=com', 'filter': 'uid=%(username)s'},
|
||||||
|
|
||||||
|
# Map ldap keys into application specific keys
|
||||||
|
'KEY_MAP': {
|
||||||
|
'name': 'cn',
|
||||||
|
'company': 'o',
|
||||||
|
'location': 'l',
|
||||||
|
'email': 'mail',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OAUTH = {
|
||||||
|
'twitter': {
|
||||||
|
'key': '',
|
||||||
|
'secret': ''
|
||||||
|
},
|
||||||
|
'github': {
|
||||||
|
'key': '',
|
||||||
|
'secret': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CACHE_TYPE = 'simple'
|
CACHE_TYPE = 'simple'
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
|
@ -161,4 +193,4 @@ if ENV != "DEV":
|
||||||
ASSETS_DEBUG = False
|
ASSETS_DEBUG = False
|
||||||
SQLALCHEMY_ECHO = False
|
SQLALCHEMY_ECHO = False
|
||||||
|
|
||||||
MODULES = ['wiki', 'search', 'auth', 'auth.local', 'auth.oauth']
|
MODULES = ['wiki', 'search', 'auth', 'auth.local', 'auth.oauth', 'auth.ldap']
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
from flask_ldap_login import LDAPLoginManager
|
||||||
|
|
||||||
|
ldap_mgr = LDAPLoginManager()
|
||||||
|
|
7
realms/modules/auth/ldap/forms.py
Normal file
7
realms/modules/auth/ldap/forms.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from flask_wtf import Form
|
||||||
|
from wtforms import StringField, PasswordField, validators
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
email = StringField('Email', [validators.DataRequired()])
|
||||||
|
password = PasswordField('Password', [validators.DataRequired()])
|
31
realms/modules/auth/ldap/models.py
Normal file
31
realms/modules/auth/ldap/models.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from flask import current_app, render_template
|
||||||
|
from flask.ext.login import login_user
|
||||||
|
from realms import ldap
|
||||||
|
from flask_ldap_login import LDAPLoginForm
|
||||||
|
from ..models import BaseUser
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
|
users = {}
|
||||||
|
|
||||||
|
@ldap.save_user
|
||||||
|
def save_user(username, userdata):
|
||||||
|
users[username] = User(username, userdata)
|
||||||
|
return users[username]
|
||||||
|
|
||||||
|
class User(BaseUser):
|
||||||
|
type = 'ldap'
|
||||||
|
|
||||||
|
def __init__(self, username, data):
|
||||||
|
self.id = username
|
||||||
|
self.username = username
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def login_form():
|
||||||
|
form = LDAPLoginForm()
|
||||||
|
return render_template('auth/ldap/login.html', form=form)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def auth(*args):
|
||||||
|
login_user(args[0].user, remember=True)
|
||||||
|
return True
|
18
realms/modules/auth/ldap/views.py
Normal file
18
realms/modules/auth/ldap/views.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from flask import current_app, request, redirect, Blueprint, flash, url_for
|
||||||
|
from ..ldap.models import User
|
||||||
|
from flask_ldap_login import LDAPLoginForm
|
||||||
|
|
||||||
|
blueprint = Blueprint('auth.ldap', __name__)
|
||||||
|
|
||||||
|
@blueprint.route("/login/ldap", methods=['POST'])
|
||||||
|
def login():
|
||||||
|
form = LDAPLoginForm()
|
||||||
|
|
||||||
|
if not form.validate():
|
||||||
|
flash('Form invalid', 'warning')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
if User.auth(form.user):
|
||||||
|
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('auth.login'))
|
|
@ -19,13 +19,11 @@ class Auth(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_auth_user(auth_type):
|
def get_auth_user(auth_type):
|
||||||
print auth_type
|
|
||||||
mod = importlib.import_module('realms.modules.auth.%s.models' % auth_type)
|
mod = importlib.import_module('realms.modules.auth.%s.models' % auth_type)
|
||||||
return mod.User
|
return mod.User
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_user(auth_id):
|
def load_user(auth_id):
|
||||||
print auth_id
|
|
||||||
auth_type, user_id = auth_id.split("/")
|
auth_type, user_id = auth_id.split("/")
|
||||||
return Auth.get_auth_user(auth_type).load_user(user_id)
|
return Auth.get_auth_user(auth_type).load_user(user_id)
|
||||||
|
|
||||||
|
@ -33,7 +31,7 @@ class Auth(object):
|
||||||
def login_forms():
|
def login_forms():
|
||||||
forms = []
|
forms = []
|
||||||
# TODO be dynamic
|
# TODO be dynamic
|
||||||
for t in ['local']:
|
for t in ['local', 'ldap']:
|
||||||
forms.append(Auth.get_auth_user(t).login_form())
|
forms.append(Auth.get_auth_user(t).login_form())
|
||||||
return forms
|
return forms
|
||||||
|
|
||||||
|
@ -87,7 +85,7 @@ class BaseUser(UserMixin):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def auth(email, password):
|
def auth(email, password):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hash_password(password):
|
def hash_password(password):
|
||||||
|
|
|
@ -8,25 +8,31 @@ oauth = OAuth()
|
||||||
|
|
||||||
class OAuthUser(BaseUser):
|
class OAuthUser(BaseUser):
|
||||||
# OAuth remote app
|
# OAuth remote app
|
||||||
app = None
|
remote_app = None
|
||||||
|
|
||||||
|
|
||||||
class TwitterUser(OAuthUser):
|
class TwitterUser(OAuthUser):
|
||||||
|
|
||||||
app = oauth.remote_app(
|
|
||||||
'twitter',
|
|
||||||
base_url='https://api.twitter.com/1/',
|
|
||||||
request_token_url='https://api.twitter.com/oauth/request_token',
|
|
||||||
access_token_url='https://api.twitter.com/oauth/access_token',
|
|
||||||
authorize_url='https://api.twitter.com/oauth/authenticate',
|
|
||||||
consumer_key=config.TWITTER_KEY,
|
|
||||||
consumer_secret=config.TWITTER_SECRET)
|
|
||||||
|
|
||||||
def __init__(self, id_, username, email=None):
|
def __init__(self, id_, username, email=None):
|
||||||
self.id = id_
|
self.id = id_
|
||||||
self.username = username
|
self.username = username
|
||||||
self.email = email
|
self.email = email
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def app(cls):
|
||||||
|
if cls.remote_app:
|
||||||
|
return cls.remote_app
|
||||||
|
|
||||||
|
cls.remote_app = oauth.remote_app(
|
||||||
|
'twitter',
|
||||||
|
base_url='https://api.twitter.com/1/',
|
||||||
|
request_token_url='https://api.twitter.com/oauth/request_token',
|
||||||
|
access_token_url='https://api.twitter.com/oauth/access_token',
|
||||||
|
authorize_url='https://api.twitter.com/oauth/authenticate',
|
||||||
|
consumer_key=config.OAUTH['twitter']['key'],
|
||||||
|
consumer_secret=config.OAUTH['twitter']['secret'])
|
||||||
|
return cls.remote_app
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_user(*args, **kwargs):
|
def load_user(*args, **kwargs):
|
||||||
return TwitterUser(args[0])
|
return TwitterUser(args[0])
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
from flask import Blueprint, url_for, request, flash, redirect
|
from flask import Blueprint, url_for, request, flash, redirect, session
|
||||||
from .models import TwitterUser
|
from .models import TwitterUser
|
||||||
|
|
||||||
blueprint = Blueprint('auth.oauth', __name__)
|
blueprint = Blueprint('auth.oauth', __name__)
|
||||||
|
|
||||||
|
|
||||||
def oauth_failed(next_url):
|
def oauth_failed(next_url):
|
||||||
flash(u'You denied the request to sign in.')
|
flash('You denied the request to sign in.')
|
||||||
return redirect(next_url)
|
return redirect(next_url)
|
||||||
|
|
||||||
@blueprint.route("/login/twitter")
|
@blueprint.route("/login/twitter")
|
||||||
def login_twitter():
|
def login_twitter():
|
||||||
return TwitterUser.app.authorize(callback=url_for('twitter_callback',
|
return TwitterUser.app().authorize(callback=url_for('twitter_callback',
|
||||||
next=request.args.get('next') or request.referrer or None))
|
next=request.args.get('next') or request.referrer or None))
|
||||||
|
|
||||||
@blueprint.route('/login/twitter/callback')
|
@blueprint.route('/login/twitter/callback')
|
||||||
def twitter_callback():
|
def twitter_callback():
|
||||||
next_url = request.args.get('next') or url_for('index')
|
next_url = request.args.get('next') or url_for('index')
|
||||||
resp = TwitterUser.app.authorized_response()
|
resp = TwitterUser.app().authorized_response()
|
||||||
if resp is None:
|
if resp is None:
|
||||||
return oauth_failed(next_url)
|
return oauth_failed(next_url)
|
||||||
|
|
||||||
|
@ -27,4 +27,4 @@ def twitter_callback():
|
||||||
session['twitter_user'] = resp['screen_name']
|
session['twitter_user'] = resp['screen_name']
|
||||||
|
|
||||||
flash('You were signed in as %s' % resp['screen_name'])
|
flash('You were signed in as %s' % resp['screen_name'])
|
||||||
return redirect(next_url)
|
return redirect(next_url)
|
||||||
|
|
21
realms/templates/auth/ldap/login.html
Normal file
21
realms/templates/auth/ldap/login.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% from 'macros.html' import render_form, render_field %}
|
||||||
|
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#ldap-modal">
|
||||||
|
<i class="fa fa-folder-open-o"></i> Login with LDAP
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="modal fade" id="ldap-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">LDAP Login</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% call render_form(form, action_url=url_for('auth.ldap.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||||
|
{{ render_field(form.username, placeholder='Username', type='text', required=1) }}
|
||||||
|
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -2,5 +2,6 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% for form in forms %}
|
{% for form in forms %}
|
||||||
{{ form|safe }}
|
{{ form|safe }}
|
||||||
|
<hr />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -28,12 +28,14 @@ setup(name='realms-wiki',
|
||||||
'Flask-Cache==0.13.1',
|
'Flask-Cache==0.13.1',
|
||||||
'Flask-Elastic==0.2',
|
'Flask-Elastic==0.2',
|
||||||
'Flask-Login==0.2.11',
|
'Flask-Login==0.2.11',
|
||||||
|
'Flask-OAuthlib==0.9.1',
|
||||||
'Flask-SQLAlchemy==2.0',
|
'Flask-SQLAlchemy==2.0',
|
||||||
'Flask-WTF==0.10.2',
|
'Flask-WTF==0.10.2',
|
||||||
'PyYAML==3.11',
|
'PyYAML==3.11',
|
||||||
'bcrypt==1.0.2',
|
'bcrypt==1.0.2',
|
||||||
'beautifulsoup4==4.3.2',
|
'beautifulsoup4==4.3.2',
|
||||||
'click==3.3',
|
'click==3.3',
|
||||||
|
'flask-ldap-login==0.3.0',
|
||||||
'gevent==1.0.2',
|
'gevent==1.0.2',
|
||||||
'ghdiff==0.4',
|
'ghdiff==0.4',
|
||||||
'gittle==0.4.0',
|
'gittle==0.4.0',
|
||||||
|
|
Loading…
Reference in a new issue