diff options
-rw-r--r-- | accounts/__init__.py | 29 | ||||
-rw-r--r-- | accounts/forms.py | 5 | ||||
-rw-r--r-- | accounts/models.py | 14 | ||||
-rw-r--r-- | accounts/templates/base.html | 18 | ||||
-rw-r--r-- | accounts/utils/__init__.py | 49 | ||||
-rw-r--r-- | accounts/utils/login.py | 24 | ||||
-rw-r--r-- | accounts/views/admin/__init__.py | 5 | ||||
-rw-r--r-- | accounts/views/default/__init__.py | 61 | ||||
-rw-r--r-- | requirements.txt | 1 |
9 files changed, 102 insertions, 104 deletions
diff --git a/accounts/__init__.py b/accounts/__init__.py index b11a143..ba5a670 100644 --- a/accounts/__init__.py +++ b/accounts/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +from flask.ext.login import LoginManager import account from flask import Flask, g, session from utils import * - from utils.sessions import EncryptedSessionInterface +from utils.login import parse_userid from views import default, admin @@ -24,23 +25,21 @@ if app.config.get('USERNAME_BLACKLIST_FILE'): with open(app.config['USERNAME_BLACKLIST_FILE']) as f: app.username_blacklist = f.read().split('\n') +login_manager = LoginManager() +login_manager.init_app(app) + +@login_manager.user_loader +def load_user(user_id): + try: + username, password = parse_userid(user_id) + return current_app.user_backend.auth(username, password) + except (current_app.user_backend.NoSuchUserError, + current_app.user_backend.InvalidPasswordError): + return None + @app.before_request def session_permanent(): if app.config.get('PERMANENT_SESSION_LIFETIME'): session.permanent = True else: session.permanent = False - -@app.before_request -def initialize_user(): - g.user = None - - if 'username' in session and 'password' in session: - username = ensure_utf8(session['username']) - password = ensure_utf8(session['password']) - try: - g.user = current_app.user_backend.auth(username, password) - except (current_app.user_backend.NoSuchUserError, - current_app.user_backend.InvalidPasswordError): - # we had crap in the session, delete it - logout_user() diff --git a/accounts/forms.py b/accounts/forms.py index 9e2e8f3..bcbe747 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -2,6 +2,7 @@ from account import SERVICES from flask import g, current_app, session, Markup from flask.ext.wtf import Form +from flask.ext.login import current_user from wtforms import TextField, PasswordField, ValidationError, BooleanField,\ validators from functools import partial @@ -98,13 +99,13 @@ class SettingsForm(Form): if form.password.data: if not field.data: raise ValidationError(u'Gib bitte dein altes Passwort ein, um ein neues zu setzen.') - if field.data != decrypt_password(session['password']): + if field.data != current_user.password: raise ValidationError(u'Altes Passwort ist falsch.') def validate_mail(form, field): results = current_app.user_backend.find_by_mail(field.data) for user in results: - if user.uid != g.user.uid: + if user.uid != current_user.uid: raise ValidationError(u'Diese E-Mail-Adresse wird schon von einem anderen Account benutzt!') def get_servicepassword(self, service_id): diff --git a/accounts/models.py b/accounts/models.py index 9e0c45e..f967180 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import +from flask.ext.login import UserMixin -class Account(object): +from accounts.utils.login import create_userid + + +class Account(UserMixin): """ An Account represents a complex ldap tree entry for spline users. For each service a spline user can have a different password. @@ -62,6 +67,13 @@ class Account(object): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) + def get_id(self): + """ + This is for flask-login. The returned string is saved inside + the cookie and used to identify the user. + """ + return create_userid(self.uid, self.password) + class Service(object): def __init__(self, id, name, url): diff --git a/accounts/templates/base.html b/accounts/templates/base.html index 1eeda55..f59408d 100644 --- a/accounts/templates/base.html +++ b/accounts/templates/base.html @@ -15,9 +15,15 @@ <div id="header-background"> </div> <header> - <h1><a href="{{ url_for('default.settings') if g.user else url_for('default.index') }}"> - <img src="{{ url_for('static', filename='logo.png') }}" alt="spline accounts" /> - </a></h1> + <h1> + {%- if current_user.is_authenticated -%} + <a href="{{ url_for('default.settings') }}"> + {%- else -%} + <a href="{{ url_for('default.index') }}"> + {%- endif -%} + <img src="{{ url_for('static', filename='logo.png') }}" alt="spline accounts" /> + </a> + </h1> <span id="roundcornerb"> </span> <span id="roundcornerw"> </span> @@ -31,9 +37,9 @@ {%- if not no_login_message %} <nav id="usermenu"> <ul> - {%- if g.user %} - <li>Angemeldet als <strong>{{ g.user.uid }}</strong></li> - {%- if g.user.uid in config.get('ADMIN_USERS', []) %} + {%- if current_user.is_authenticated %} + <li>Angemeldet als <strong>{{ current_user.uid }}</strong></li> + {%- if current_user.uid in config.get('ADMIN_USERS', []) %} <li><a href="{{ url_for('admin.index') }}">Admin</a></li> {%- endif %} <li><a href="{{ url_for('default.logout') }}">Abmelden</a></li> diff --git a/accounts/utils/__init__.py b/accounts/utils/__init__.py index 4529796..06cf969 100644 --- a/accounts/utils/__init__.py +++ b/accounts/utils/__init__.py @@ -4,6 +4,7 @@ import re from functools import wraps from flask import current_app, flash, g, redirect, render_template, request, session from flask import url_for as flask_url_for +from flask.ext.login import current_user from werkzeug.exceptions import Forbidden from wtforms.validators import Regexp, ValidationError @@ -31,54 +32,6 @@ def templated(template=None): return templated__ return templated_ -def login_required(f): - @wraps(f) - def login_required_(*args, **kwargs): - if not g.user: - raise Forbidden(u'Bitte einloggen!') - return f(*args, **kwargs) - return login_required_ - -def admin_required(f): - @wraps(f) - def admin_required_(*args, **kwargs): - if not g.user: - raise Forbidden(u'Bitte einloggen!') - if g.user.uid not in current_app.config.get('ADMIN_USERS', []): - raise Forbidden(u'Du bist kein Admin.') - return f(*args, **kwargs) - return admin_required_ - -def logout_required(f): - @wraps(f) - def logout_required_(*args, **kwargs): - if g.user: - raise Forbidden(u'Diese Seite ist nur für nicht eingeloggte Benutzer gedacht!') - return f(*args, **kwargs) - return logout_required_ - - -def login_user(username, password): - username = ensure_utf8(username) - password = ensure_utf8(password) - - try: - g.user = current_app.user_backend.auth(username, password) - except (current_app.user_backend.NoSuchUserError, - current_app.user_backend.InvalidPasswordError): - return False - - session['username'] = username - session['password'] = password - - return True - - -def logout_user(): - session.pop('username', None) - session.pop('password', None) - g.user = None - def ensure_utf8(s): if isinstance(s, unicode): diff --git a/accounts/utils/login.py b/accounts/utils/login.py new file mode 100644 index 0000000..9888e89 --- /dev/null +++ b/accounts/utils/login.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from flask.ext.login import current_user +from functools import wraps +from werkzeug.exceptions import Forbidden +from itsdangerous import base64_decode, base64_encode, compact_json + + +def create_userid(username, password): + userid = (username, password) + return base64_encode(compact_json.dumps(userid)) + + +def parse_userid(value): + return compact_json.loads(base64_decode(value)) + + +def logout_required(f): + @wraps(f) + def logout_required_(*args, **kwargs): + if current_user.is_authenticated: + raise Forbidden(u'Diese Seite ist nur für nicht eingeloggte Benutzer gedacht!') + return f(*args, **kwargs) + return logout_required_ + diff --git a/accounts/views/admin/__init__.py b/accounts/views/admin/__init__.py index 5564f93..be7f3d7 100644 --- a/accounts/views/admin/__init__.py +++ b/accounts/views/admin/__init__.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from flask import Blueprint from flask import current_app, redirect, request, g, flash, url_for +from flask.ext.login import current_user from uuid import uuid4 from werkzeug.exceptions import Forbidden @@ -15,9 +16,9 @@ bp = Blueprint('admin', __name__) @bp.before_request def restrict_bp_to_admins(): - if not g.user: + if not current_user.is_authenticated: raise Forbidden(u'Bitte einloggen!') - if g.user.uid not in current_app.config.get('ADMIN_USERS', []): + if current_user.uid not in current_app.config.get('ADMIN_USERS', []): raise Forbidden(u'Du bist kein Admin.') diff --git a/accounts/views/default/__init__.py b/accounts/views/default/__init__.py index 64c855f..37f71f6 100644 --- a/accounts/views/default/__init__.py +++ b/accounts/views/default/__init__.py @@ -4,11 +4,13 @@ from __future__ import absolute_import from copy import deepcopy from flask import Blueprint from flask import current_app, redirect, request, g, flash, url_for +from flask.ext.login import login_required, login_user, logout_user, current_user from accounts.forms import LoginForm, RegisterForm, RegisterCompleteForm, \ LostPasswordForm, SettingsForm from accounts.utils import * from accounts.utils.confirmation import Confirmation +from accounts.utils.login import logout_required from accounts.models import Account @@ -18,17 +20,21 @@ bp = Blueprint('default', __name__) @bp.route('/', methods=['GET', 'POST']) @templated('index.html') def index(): - if not g.user: - form = LoginForm(request.form) - if form.validate_on_submit(): - if login_user(form.username.data, form.password.data): - flash(u'Erfolgreich eingeloggt', 'success') - return redirect(url_for('.settings')) - else: - flash(u'Ungültiger Benutzername und/oder Passwort', 'error') - else: + if current_user.is_authenticated: return redirect(url_for('.settings')) + form = LoginForm(request.form) + if form.validate_on_submit(): + try: + user = current_app.user_backend.auth(form.username.data, + form.password.data) + login_user(user) + flash(u'Erfolgreich eingeloggt', 'success') + return redirect(url_for('.settings')) + except (current_app.user_backend.NoSuchUserError, + current_app.user_backend.InvalidPasswordError): + flash(u'Ungültiger Benutzername und/oder Passwort', 'error') + return {'form': form} @@ -71,9 +77,7 @@ def register_complete(token): user = Account(username, mail, password=form.password.data) current_app.user_backend.register(user) - - # populate request context and session - assert login_user(user.uid, user.password) + login_user(user) if current_app.config.get('MAIL_REGISTER_NOTIFY'): current_app.mail_backend.send( @@ -132,11 +136,9 @@ def lost_password_complete(token): user = current_app.user_backend.get_by_uid(username) user.change_password(form.password.data) current_app.user_backend.update(user, as_admin=True) + login_user(user) - session['username'] = username - session['password'] = form.password.data flash(u'Passwort geändert.', 'success') - return redirect(url_for('.settings')) return { @@ -150,7 +152,7 @@ def lost_password_complete(token): @templated('settings.html') @login_required def settings(): - form = SettingsForm(request.form, mail=g.user.attributes['mail']) + form = SettingsForm(request.form, mail=current_user.attributes['mail']) if form.validate_on_submit(): changed = False @@ -158,15 +160,15 @@ def settings(): for service in current_app.all_services: field = form.get_servicedelete(service.id) if(field.data): - g.user.reset_password(service.id) + current_user.reset_password(service.id) changed = True elif request.form.get('submit_main'): - if form.mail.data and form.mail.data != g.user.attributes['mail']: - confirm_token = Confirmation('change_mail').dumps((g.user.uid, form.mail.data)) + if form.mail.data and form.mail.data != current_user.attributes['mail']: + confirm_token = Confirmation('change_mail').dumps((current_user.uid, form.mail.data)) confirm_link = url_for('.change_mail', token=confirm_token, _external=True) - body = render_template('mail/change_mail.txt', username=g.user.uid, + body = render_template('mail/change_mail.txt', username=current_user.uid, mail=form.mail.data, link=confirm_link) current_app.mail_backend.send( @@ -179,9 +181,7 @@ def settings(): changed = True if form.password.data: - g.user.change_password(form.password.data, form.old_password.data) - session['password'] = form.password.data - + current_user.change_password(form.password.data, form.old_password.data) flash(u'Passwort geändert', 'success') changed = True @@ -189,10 +189,11 @@ def settings(): field = form.get_servicepassword(service.id) if field.data: changed = True - g.user.change_password(field.data, None, service.id) + current_user.change_password(field.data, None, service.id) if changed: - current_app.user_backend.update(g.user, as_admin=True) #XXX: as_admin wieder wegmachen sobald ACLs richtig gesetzt sind + current_app.user_backend.update(current_user, as_admin=True) #XXX: as_admin wieder wegmachen sobald ACLs richtig gesetzt sind + login_user(current_user) return redirect(url_for('.settings')) else: flash(u'Nichts geändert.') @@ -200,7 +201,7 @@ def settings(): services = deepcopy(current_app.all_services) for s in services: - s.changed = s.id in g.user.services + s.changed = s.id in current_user.services return { 'form': form, @@ -213,16 +214,16 @@ def settings(): def change_mail(token): username, mail = Confirmation('change_mail').loads_http(token, max_age=3*24*60*60) - if g.user.uid != username: + if current_user.uid != username: raise Forbidden(u'Bitte logge dich als der Benutzer ein, dessen E-Mail-Adresse du ändern willst.') results = current_app.user_backend.find_by_mail(mail) for user in results: - if user.uid != g.user.uid: + if user.uid != current_user.uid: raise Forbidden(u'Diese E-Mail-Adresse wird schon von einem anderen account benutzt!') - g.user.change_email(mail) - current_app.user_backend.update(g.user) + current_user.change_email(mail) + current_app.user_backend.update(current_user) flash(u'E-Mail-Adresse geändert.', 'success') return redirect(url_for('.settings')) diff --git a/requirements.txt b/requirements.txt index ea8476c..0d0a1f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Flask>=0.10 Flask-WTF Flask-Script +Flask-Login Werkzeug>=0.6 Jinja2>=2.4 WTForms>=1.0 |