first pass, non-working
This commit is contained in:
parent
459a9c2c59
commit
3c2f4a0445
|
@ -161,4 +161,4 @@ if ENV != "DEV":
|
|||
ASSETS_DEBUG = False
|
||||
SQLALCHEMY_ECHO = False
|
||||
|
||||
MODULES = ['wiki', 'auth', 'search']
|
||||
MODULES = ['wiki', 'search', 'auth', 'auth.local', 'auth.oauth']
|
||||
|
|
0
realms/modules/auth/ldap/__init__.py
Normal file
0
realms/modules/auth/ldap/__init__.py
Normal file
0
realms/modules/auth/local/__init__.py
Normal file
0
realms/modules/auth/local/__init__.py
Normal file
|
@ -1,9 +1,10 @@
|
|||
import click
|
||||
from realms.lib.util import random_string
|
||||
from realms.modules.auth.models import User
|
||||
from realms.modules.auth.local.models import User
|
||||
from realms.lib.util import green, red, yellow
|
||||
from realms import flask_cli
|
||||
|
||||
|
||||
@flask_cli.group(short_help="Auth Module")
|
||||
def cli():
|
||||
pass
|
107
realms/modules/auth/local/models.py
Normal file
107
realms/modules/auth/local/models.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from flask import current_app, render_template
|
||||
from flask.ext.login import logout_user, login_user
|
||||
from realms import login_manager, db
|
||||
from realms.lib.model import Model
|
||||
from ..models import BaseUser
|
||||
from .forms import LoginForm
|
||||
from itsdangerous import URLSafeSerializer, BadSignature
|
||||
from hashlib import sha256
|
||||
import bcrypt
|
||||
|
||||
|
||||
@login_manager.token_loader
|
||||
def load_token(token):
|
||||
# Load unsafe because payload is needed for sig
|
||||
sig_okay, payload = URLSafeSerializer(current_app.config['SECRET_KEY']).loads_unsafe(token)
|
||||
|
||||
if not payload:
|
||||
return None
|
||||
|
||||
# User key *could* be stored in payload to avoid user lookup in db
|
||||
user = User.get_by_id(payload.get('id'))
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
try:
|
||||
if BaseUser.signer(sha256(user.password).hexdigest()).loads(token):
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
except BadSignature:
|
||||
return None
|
||||
|
||||
|
||||
class User(Model, BaseUser):
|
||||
__tablename__ = 'users'
|
||||
type = 'local'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(128), unique=True)
|
||||
email = db.Column(db.String(128), unique=True)
|
||||
password = db.Column(db.String(60))
|
||||
admin = False
|
||||
|
||||
hidden_fields = ['password']
|
||||
readonly_fields = ['email', 'password']
|
||||
|
||||
@property
|
||||
def auth_token_id(self):
|
||||
return self.password
|
||||
|
||||
@staticmethod
|
||||
def load_user(*args, **kwargs):
|
||||
return User.get_by_id(args[0])
|
||||
|
||||
@staticmethod
|
||||
def create(username, email, password):
|
||||
u = User()
|
||||
u.username = username
|
||||
u.email = email
|
||||
u.password = User.hash_password(password)
|
||||
u.save()
|
||||
|
||||
@staticmethod
|
||||
def get_by_username(username):
|
||||
return User.query().filter_by(username=username).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_email(email):
|
||||
return User.query().filter_by(email=email).first()
|
||||
|
||||
@staticmethod
|
||||
def signer(salt):
|
||||
return URLSafeSerializer(current_app.config['SECRET_KEY'] + salt)
|
||||
|
||||
@staticmethod
|
||||
def auth(email, password):
|
||||
user = User.get_by_email(email)
|
||||
|
||||
if not user:
|
||||
# User doesn't exist
|
||||
return False
|
||||
|
||||
if User.check_password(password, user.password):
|
||||
# Password is good, log in user
|
||||
login_user(user, remember=True)
|
||||
return user
|
||||
else:
|
||||
# Password check failed
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def hash_password(password):
|
||||
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(12))
|
||||
|
||||
@staticmethod
|
||||
def check_password(password, hashed):
|
||||
return bcrypt.hashpw(password.encode('utf-8'), hashed.encode('utf-8')) == hashed
|
||||
|
||||
@classmethod
|
||||
def logout(cls):
|
||||
logout_user()
|
||||
|
||||
@staticmethod
|
||||
def login_form():
|
||||
form = LoginForm()
|
||||
return render_template('auth/local/login.html', form=form)
|
||||
|
51
realms/modules/auth/local/views.py
Normal file
51
realms/modules/auth/local/views.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for
|
||||
from realms.modules.auth.local.models import User
|
||||
from realms.modules.auth.local.forms import LoginForm, RegistrationForm
|
||||
|
||||
blueprint = Blueprint('auth.local', __name__)
|
||||
|
||||
|
||||
@blueprint.route("/login/local", methods=['POST'])
|
||||
def login():
|
||||
form = LoginForm()
|
||||
|
||||
if not form.validate():
|
||||
flash('Form invalid', 'warning')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
if User.auth(request.form['email'], request.form['password']):
|
||||
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
else:
|
||||
flash('Email or Password Incorrect', 'warning')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
|
||||
@blueprint.route("/register", methods=['GET', 'POST'])
|
||||
def register():
|
||||
|
||||
if not current_app.config['REGISTRATION_ENABLED']:
|
||||
flash("Registration is disabled")
|
||||
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
||||
form = RegistrationForm()
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
if not form.validate():
|
||||
flash('Form invalid', 'warning')
|
||||
return redirect(url_for('auth.local.register'))
|
||||
|
||||
if User.get_by_username(request.form['username']):
|
||||
flash('Username is taken', 'warning')
|
||||
return redirect(url_for('auth.local.register'))
|
||||
|
||||
if User.get_by_email(request.form['email']):
|
||||
flash('Email is taken', 'warning')
|
||||
return redirect(url_for('auth.local.register'))
|
||||
|
||||
User.create(request.form['username'], request.form['email'], request.form['password'])
|
||||
User.auth(request.form['email'], request.form['password'])
|
||||
|
||||
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
||||
return render_template("auth/register.html", form=form)
|
|
@ -1,39 +1,41 @@
|
|||
from flask import current_app
|
||||
from flask.ext.login import UserMixin, logout_user, login_user, AnonymousUserMixin
|
||||
from realms import login_manager, db
|
||||
from realms.lib.model import Model
|
||||
from realms import login_manager
|
||||
from realms.lib.util import gravatar_url
|
||||
from itsdangerous import URLSafeSerializer, BadSignature
|
||||
from hashlib import sha256
|
||||
import bcrypt
|
||||
import importlib
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.get_by_id(user_id)
|
||||
def load_user(auth_id):
|
||||
return Auth.load_user(auth_id)
|
||||
|
||||
auth_users = {}
|
||||
|
||||
|
||||
@login_manager.token_loader
|
||||
def load_token(token):
|
||||
# Load unsafe because payload is needed for sig
|
||||
sig_okay, payload = URLSafeSerializer(current_app.config['SECRET_KEY']).loads_unsafe(token)
|
||||
class Auth(object):
|
||||
|
||||
if not payload:
|
||||
return None
|
||||
@staticmethod
|
||||
def get_auth_user(auth_type):
|
||||
print auth_type
|
||||
mod = importlib.import_module('realms.modules.auth.%s.models' % auth_type)
|
||||
return mod.User
|
||||
|
||||
# User key *could* be stored in payload to avoid user lookup in db
|
||||
user = User.get_by_id(payload.get('id'))
|
||||
@staticmethod
|
||||
def load_user(auth_id):
|
||||
print auth_id
|
||||
auth_type, user_id = auth_id.split("/")
|
||||
return Auth.get_auth_user(auth_type).load_user(user_id)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
try:
|
||||
if User.signer(sha256(user.password).hexdigest()).loads(token):
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
except BadSignature:
|
||||
return None
|
||||
@staticmethod
|
||||
def login_forms():
|
||||
forms = []
|
||||
# TODO be dynamic
|
||||
for t in ['local']:
|
||||
forms.append(Auth.get_auth_user(t).login_form())
|
||||
return forms
|
||||
|
||||
|
||||
class AnonUser(AnonymousUserMixin):
|
||||
|
@ -42,40 +44,42 @@ class AnonUser(AnonymousUserMixin):
|
|||
admin = False
|
||||
|
||||
|
||||
class User(Model, UserMixin):
|
||||
__tablename__ = 'users'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(128), unique=True)
|
||||
email = db.Column(db.String(128), unique=True)
|
||||
password = db.Column(db.String(60))
|
||||
admin = False
|
||||
class BaseUser(UserMixin):
|
||||
id = None
|
||||
email = None
|
||||
username = None
|
||||
type = 'base'
|
||||
|
||||
hidden_fields = ['password']
|
||||
readonly_fields = ['email', 'password']
|
||||
def get_id(self):
|
||||
return unicode("%s/%s" % (self.type, self.id))
|
||||
|
||||
def get_auth_token(self):
|
||||
key = sha256(self.password).hexdigest()
|
||||
return User.signer(key).dumps(dict(id=self.id))
|
||||
key = sha256(self.auth_token_id).hexdigest()
|
||||
return BaseUser.signer(key).dumps(dict(id=self.id))
|
||||
|
||||
@property
|
||||
def auth_token_id(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def avatar(self):
|
||||
return gravatar_url(self.email)
|
||||
|
||||
@staticmethod
|
||||
def create(username, email, password):
|
||||
u = User()
|
||||
u.username = username
|
||||
u.email = email
|
||||
u.password = User.hash_password(password)
|
||||
u.save()
|
||||
def load_user(*args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def create(*args, **kwargs):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_by_username(username):
|
||||
return User.query().filter_by(username=username).first()
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_by_email(email):
|
||||
return User.query().filter_by(email=email).first()
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def signer(salt):
|
||||
|
@ -83,19 +87,7 @@ class User(Model, UserMixin):
|
|||
|
||||
@staticmethod
|
||||
def auth(email, password):
|
||||
user = User.get_by_email(email)
|
||||
|
||||
if not user:
|
||||
# User doesn't exist
|
||||
return False
|
||||
|
||||
if User.check_password(password, user.password):
|
||||
# Password is good, log in user
|
||||
login_user(user, remember=True)
|
||||
return user
|
||||
else:
|
||||
# Password check failed
|
||||
return False
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def hash_password(password):
|
||||
|
@ -109,4 +101,8 @@ class User(Model, UserMixin):
|
|||
def logout(cls):
|
||||
logout_user()
|
||||
|
||||
@staticmethod
|
||||
def login_form():
|
||||
pass
|
||||
|
||||
login_manager.anonymous_user = AnonUser
|
||||
|
|
0
realms/modules/auth/oauth/__init__.py
Normal file
0
realms/modules/auth/oauth/__init__.py
Normal file
36
realms/modules/auth/oauth/models.py
Normal file
36
realms/modules/auth/oauth/models.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from flask import render_template
|
||||
from flask_oauthlib.client import OAuth
|
||||
from realms import config
|
||||
from ..models import BaseUser
|
||||
|
||||
oauth = OAuth()
|
||||
|
||||
|
||||
class OAuthUser(BaseUser):
|
||||
# OAuth remote app
|
||||
app = None
|
||||
|
||||
|
||||
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):
|
||||
self.id = id_
|
||||
self.username = username
|
||||
self.email = email
|
||||
|
||||
@staticmethod
|
||||
def load_user(*args, **kwargs):
|
||||
return TwitterUser(args[0])
|
||||
|
||||
@staticmethod
|
||||
def login_form():
|
||||
return render_template('auth/oauth/twitter.html')
|
30
realms/modules/auth/oauth/views.py
Normal file
30
realms/modules/auth/oauth/views.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from flask import Blueprint, url_for, request, flash, redirect
|
||||
from .models import TwitterUser
|
||||
|
||||
blueprint = Blueprint('auth.oauth', __name__)
|
||||
|
||||
|
||||
def oauth_failed(next_url):
|
||||
flash(u'You denied the request to sign in.')
|
||||
return redirect(next_url)
|
||||
|
||||
@blueprint.route("/login/twitter")
|
||||
def login_twitter():
|
||||
return TwitterUser.app.authorize(callback=url_for('twitter_callback',
|
||||
next=request.args.get('next') or request.referrer or None))
|
||||
|
||||
@blueprint.route('/login/twitter/callback')
|
||||
def twitter_callback():
|
||||
next_url = request.args.get('next') or url_for('index')
|
||||
resp = TwitterUser.app.authorized_response()
|
||||
if resp is None:
|
||||
return oauth_failed(next_url)
|
||||
|
||||
session['twitter_token'] = (
|
||||
resp['oauth_token'],
|
||||
resp['oauth_token_secret']
|
||||
)
|
||||
session['twitter_user'] = resp['screen_name']
|
||||
|
||||
flash('You were signed in as %s' % resp['screen_name'])
|
||||
return redirect(next_url)
|
|
@ -1,72 +1,22 @@
|
|||
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for
|
||||
from realms.modules.auth.models import User
|
||||
from realms.modules.auth.forms import LoginForm, RegistrationForm
|
||||
from flask.ext.login import logout_user
|
||||
from realms.modules.auth.models import Auth
|
||||
|
||||
blueprint = Blueprint('auth', __name__)
|
||||
|
||||
|
||||
@blueprint.route("/logout")
|
||||
def logout_page():
|
||||
User.logout()
|
||||
flash("You are now logged out")
|
||||
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
||||
|
||||
@blueprint.route("/login", methods=['GET', 'POST'])
|
||||
def login():
|
||||
form = LoginForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if not form.validate():
|
||||
flash('Form invalid', 'warning')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
if User.auth(request.form['email'], request.form['password']):
|
||||
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
else:
|
||||
flash('Email or Password Incorrect', 'warning')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template("auth/login.html", form=form)
|
||||
return render_template("auth/login.html", forms=Auth.login_forms())
|
||||
|
||||
|
||||
@blueprint.route("/register", methods=['GET', 'POST'])
|
||||
def register():
|
||||
|
||||
if not current_app.config['REGISTRATION_ENABLED']:
|
||||
flash("Registration is disabled")
|
||||
@blueprint.route("/logout")
|
||||
def logout():
|
||||
logout_user()
|
||||
flash("You are now logged out")
|
||||
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
||||
form = RegistrationForm()
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
if not form.validate():
|
||||
flash('Form invalid', 'warning')
|
||||
return redirect(url_for('auth.register'))
|
||||
|
||||
if User.get_by_username(request.form['username']):
|
||||
flash('Username is taken', 'warning')
|
||||
return redirect(url_for('auth.register'))
|
||||
|
||||
if User.get_by_email(request.form['email']):
|
||||
flash('Email is taken', 'warning')
|
||||
return redirect(url_for('auth.register'))
|
||||
|
||||
User.create(request.form['username'], request.form['email'], request.form['password'])
|
||||
User.auth(request.form['email'], request.form['password'])
|
||||
|
||||
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
||||
return render_template("auth/register.html", form=form)
|
||||
|
||||
|
||||
@blueprint.route("/settings", methods=['GET', 'POST'])
|
||||
def settings():
|
||||
return render_template("auth/settings.html")
|
||||
|
||||
|
||||
@blueprint.route("/logout")
|
||||
def logout():
|
||||
User.logout()
|
||||
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||
|
|
5
realms/templates/auth/local/login.html
Normal file
5
realms/templates/auth/local/login.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% call render_form(form, action_url=url_for('auth.local.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.email, placeholder='Email', type='email', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||
{% endcall %}
|
|
@ -1,8 +1,6 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% block body %}
|
||||
{% call render_form(form, action_url=url_for('auth.login'), action_text='Login', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.email, placeholder='Email', type='email', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', required=1) }}
|
||||
{% endcall %}
|
||||
{% for form in forms %}
|
||||
{{ form|safe }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% from 'macros.html' import render_form, render_field %}
|
||||
{% block body %}
|
||||
{% call render_form(form, action_url=url_for('auth.register'), action_text='Register', btn_class='btn btn-primary') %}
|
||||
{% call render_form(form, action_url=url_for('auth.local.register'), action_text='Register', btn_class='btn btn-primary') %}
|
||||
{{ render_field(form.username, placeholder='Username', type='username', **{"required": 1, "data-parsley-type": "alphanum"}) }}
|
||||
{{ render_field(form.email, placeholder='Email', type='email', required=1) }}
|
||||
{{ render_field(form.password, placeholder='Password', type='password', **{"required": 1, "data-parsley-minlength": "6"}) }}
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
{% else %}
|
||||
<li><a href="{{ url_for('auth.login') }}"><i class="fa fa-user"></i> Login</a></li>
|
||||
{% if config.REGISTRATION_ENABLED %}
|
||||
<li><a href="{{ url_for('auth.register') }}"><i class="fa fa-users"></i> Register</a></li>
|
||||
<li><a href="{{ url_for('auth.local.register') }}"><i class="fa fa-users"></i> Register</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
|
Loading…
Reference in a new issue