|
Pierre-Yves Chibon |
33b534 |
# -*- coding: utf-8 -*-
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
(c) 2014 - Copyright Red Hat Inc
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
Authors:
|
|
Pierre-Yves Chibon |
e5fe0e |
Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
import hashlib
|
|
Pierre-Yves Chibon |
e5fe0e |
import datetime
|
|
Pierre-Yves Chibon |
e5fe0e |
import urlparse
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
import flask
|
|
Pierre-Yves Chibon |
e5fe0e |
from sqlalchemy.exc import SQLAlchemyError
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
import progit.login_forms as forms
|
|
Pierre-Yves Chibon |
e5fe0e |
import progit.lib
|
|
Pierre-Yves Chibon |
2c97c2 |
import progit.lib.login
|
|
Pierre-Yves Chibon |
f2c72b |
import progit.lib.model as model
|
|
Pierre-Yves Chibon |
f2c72b |
import progit.lib.notify
|
|
Pierre-Yves Chibon |
e5fe0e |
from progit import APP, SESSION, is_admin
|
|
Pierre-Yves Chibon |
9647e1 |
from progit.ui.admin import admin_required
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
@APP.route('/user/new', methods=['GET', 'POST'])
|
|
Pierre-Yves Chibon |
e5fe0e |
def new_user():
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Create a new user.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
form = forms.NewUserForm()
|
|
Pierre-Yves Chibon |
e5fe0e |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
username = form.user.data
|
|
Pierre-Yves Chibon |
f05535 |
if progit.lib.search_user(SESSION, username=username):
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Username already taken.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.request.url)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
email = form.email_address.data
|
|
Pierre-Yves Chibon |
f05535 |
if progit.lib.search_user(SESSION, email=email):
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Email address already taken.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.request.url)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
password = '%s%s' % (
|
|
Pierre-Yves Chibon |
e5fe0e |
form.password.data, APP.config.get('PASSWORD_SEED', None))
|
|
Pierre-Yves Chibon |
e5fe0e |
form.password.data = hashlib.sha512(password).hexdigest()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
ebcd57 |
token = progit.lib.login.id_generator(40)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
user = model.User()
|
|
Pierre-Yves Chibon |
e5fe0e |
user.token = token
|
|
Pierre-Yves Chibon |
e5fe0e |
form.populate_obj(obj=user)
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(user)
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.flush()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
emails = [email.email for email in user.emails]
|
|
Pierre-Yves Chibon |
e5fe0e |
if form.email_address.data not in emails:
|
|
Pierre-Yves Chibon |
e5fe0e |
useremail = model.UserEmail(
|
|
Pierre-Yves Chibon |
e5fe0e |
user_id=user.id,
|
|
Pierre-Yves Chibon |
e5fe0e |
email=form.email_address.data)
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(useremail)
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.flush()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.flush()
|
|
Pierre-Yves Chibon |
e5fe0e |
send_confirmation_email(user)
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'User created, please check your email to activate the '
|
|
Pierre-Yves Chibon |
e5fe0e |
'account')
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError as err:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Could not create user.')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.debug('Could not create user.')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.render_template(
|
|
Pierre-Yves Chibon |
e5fe0e |
'login/user_new.html',
|
|
Pierre-Yves Chibon |
e5fe0e |
form=form,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
@APP.route('/dologin', methods=['POST'])
|
|
Pierre-Yves Chibon |
e5fe0e |
def do_login():
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Lo the user in user.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
form = forms.LoginForm()
|
|
Pierre-Yves Chibon |
e5fe0e |
next_url = flask.request.args.get('next_url')
|
|
Pierre-Yves Chibon |
e5fe0e |
if not next_url or next_url == 'None':
|
|
Pierre-Yves Chibon |
e5fe0e |
next_url = flask.url_for('index')
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
e5fe0e |
username = form.username.data
|
|
Pierre-Yves Chibon |
e5fe0e |
password = '%s%s' % (
|
|
Pierre-Yves Chibon |
e5fe0e |
form.password.data, APP.config.get('PASSWORD_SEED', None))
|
|
Pierre-Yves Chibon |
e5fe0e |
password = hashlib.sha512(password).hexdigest()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
f05535 |
user_obj = progit.lib.search_user(SESSION, username=username)
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user_obj or user_obj.password != password:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Username or password invalid.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
elif user_obj.token:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Invalid user, did you confirm the creation with the url '
|
|
Pierre-Yves Chibon |
e5fe0e |
'provided by email?', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
else:
|
|
Pierre-Yves Chibon |
ebcd57 |
visit_key = progit.lib.login.id_generator(40)
|
|
Pierre-Yves Chibon |
e5fe0e |
expiry = datetime.datetime.now() + APP.config.get(
|
|
Pierre-Yves Chibon |
e5fe0e |
'PERMANENT_SESSION_LIFETIME')
|
|
Pierre-Yves Chibon |
e5fe0e |
session = model.ProgitUserVisit(
|
|
Pierre-Yves Chibon |
e5fe0e |
user_id=user_obj.id,
|
|
Pierre-Yves Chibon |
e5fe0e |
user_ip=flask.request.remote_addr,
|
|
Pierre-Yves Chibon |
e5fe0e |
visit_key=visit_key,
|
|
Pierre-Yves Chibon |
e5fe0e |
expiry=expiry,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(session)
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_user = user_obj
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_session_id = visit_key
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Welcome %s' % user_obj.username)
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError, err: # pragma: no cover
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Could not set the session in the db, '
|
|
Pierre-Yves Chibon |
e5fe0e |
'please report this error to an admin', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(next_url)
|
|
Pierre-Yves Chibon |
e5fe0e |
else:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Insufficient information provided', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
@APP.route('/confirm/<token>')</token>
|
|
Pierre-Yves Chibon |
e5fe0e |
def confirm_user(token):
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Confirm a user account.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
f05535 |
user_obj = progit.lib.search_user(SESSION, token=token)
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user_obj:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('No user associated with this token.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
else:
|
|
Pierre-Yves Chibon |
e5fe0e |
user_obj.token = None
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(user_obj)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Email confirmed, account activated')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError, err: # pragma: no cover
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Could not set the account as active in the db, '
|
|
Pierre-Yves Chibon |
e5fe0e |
'please report this error to an admin', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('index'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
@APP.route('/password/lost', methods=['GET', 'POST'])
|
|
Pierre-Yves Chibon |
e5fe0e |
def lost_password():
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Method to allow a user to change his/her password assuming the email
|
|
Pierre-Yves Chibon |
e5fe0e |
is not compromised.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
form = forms.LostPasswordForm()
|
|
Pierre-Yves Chibon |
e5fe0e |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
username = form.username.data
|
|
Pierre-Yves Chibon |
f05535 |
user_obj = progit.lib.search_user(SESSION, username=username)
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user_obj:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Username invalid.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
elif user_obj.token:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Invalid user, did you confirm the creation with the url '
|
|
Pierre-Yves Chibon |
e5fe0e |
'provided by email? Or did you already ask for a password '
|
|
Pierre-Yves Chibon |
e5fe0e |
'change?', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
ebcd57 |
token = progit.lib.login.id_generator(40)
|
|
Pierre-Yves Chibon |
e5fe0e |
user_obj.token = token
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(user_obj)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
send_lostpassword_email(user_obj)
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Check your email to finish changing your password')
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError as err:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Could not set the token allowing changing a password.',
|
|
Pierre-Yves Chibon |
e5fe0e |
'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.debug('Password lost change - Error setting token.')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.render_template(
|
|
Pierre-Yves Chibon |
e5fe0e |
'login/password_change.html',
|
|
Pierre-Yves Chibon |
e5fe0e |
form=form,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
@APP.route('/password/reset/<token>', methods=['GET', 'POST'])</token>
|
|
Pierre-Yves Chibon |
e5fe0e |
def reset_password(token):
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Method to allow a user to reset his/her password.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
form = forms.ResetPasswordForm()
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
f05535 |
user_obj = progit.lib.search_user(SESSION, token=token)
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user_obj:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('No user associated with this token.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
elif not user_obj.token:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Invalid user, this user never asked for a password change',
|
|
Pierre-Yves Chibon |
e5fe0e |
'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
password = '%s%s' % (
|
|
Pierre-Yves Chibon |
e5fe0e |
form.password.data, APP.config.get('PASSWORD_SEED', None))
|
|
Pierre-Yves Chibon |
e5fe0e |
user_obj.password = hashlib.sha512(password).hexdigest()
|
|
Pierre-Yves Chibon |
e5fe0e |
user_obj.token = None
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(user_obj)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Password changed')
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError as err:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Could not set the new password.', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.debug(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Password lost change - Error setting password.')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.redirect(flask.url_for('auth_login'))
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
return flask.render_template(
|
|
Pierre-Yves Chibon |
e5fe0e |
'login/password_reset.html',
|
|
Pierre-Yves Chibon |
e5fe0e |
form=form,
|
|
Pierre-Yves Chibon |
e5fe0e |
token=token,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
#
|
|
Pierre-Yves Chibon |
9647e1 |
# Admin endpoint specific to local login
|
|
Pierre-Yves Chibon |
9647e1 |
#
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
9647e1 |
@APP.route('/admin/groups', methods=['GET', 'POST'])
|
|
Pierre-Yves Chibon |
9647e1 |
@admin_required
|
|
Pierre-Yves Chibon |
9647e1 |
def admin_groups():
|
|
Pierre-Yves Chibon |
9647e1 |
""" List of the groups present in the system
|
|
Pierre-Yves Chibon |
9647e1 |
"""
|
|
Pierre-Yves Chibon |
9647e1 |
# Add new group if asked
|
|
Pierre-Yves Chibon |
9647e1 |
form = forms.NewGroupForm()
|
|
Pierre-Yves Chibon |
9647e1 |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
9647e1 |
grp = model.ProgitGroup()
|
|
Pierre-Yves Chibon |
9647e1 |
form.populate_obj(obj=grp)
|
|
Pierre-Yves Chibon |
9647e1 |
SESSION.add(grp)
|
|
Pierre-Yves Chibon |
9647e1 |
try:
|
|
Pierre-Yves Chibon |
9647e1 |
SESSION.flush()
|
|
Pierre-Yves Chibon |
9647e1 |
except SQLAlchemyError as err:
|
|
Pierre-Yves Chibon |
9647e1 |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
9647e1 |
flask.flash('Could not create group.')
|
|
Pierre-Yves Chibon |
9647e1 |
APP.logger.debug('Could not create group.')
|
|
Pierre-Yves Chibon |
9647e1 |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
9647e1 |
flask.flash('Group `%s` created.' % grp.group_name)
|
|
Pierre-Yves Chibon |
9647e1 |
SESSION.commit()
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
2c97c2 |
groups = progit.lib.login.get_groups(SESSION)
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
9647e1 |
return flask.render_template(
|
|
Pierre-Yves Chibon |
9647e1 |
'login/admin_groups.html',
|
|
Pierre-Yves Chibon |
9647e1 |
groups=groups,
|
|
Pierre-Yves Chibon |
9647e1 |
form=form,
|
|
Pierre-Yves Chibon |
40de1d |
conf_form=forms.ConfirmationForm(),
|
|
Pierre-Yves Chibon |
9647e1 |
)
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
9647e1 |
|
|
Pierre-Yves Chibon |
1c93fc |
@APP.route('/admin/group/<group>', methods=['GET', 'POST'])</group>
|
|
Pierre-Yves Chibon |
1c93fc |
@admin_required
|
|
Pierre-Yves Chibon |
1c93fc |
def admin_group(group):
|
|
Pierre-Yves Chibon |
1c93fc |
""" List of the users in a certain group
|
|
Pierre-Yves Chibon |
1c93fc |
"""
|
|
Pierre-Yves Chibon |
2c97c2 |
group_obj = progit.lib.login.get_group(SESSION, group)
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
if not group_obj:
|
|
Pierre-Yves Chibon |
1c93fc |
flask.flash('No group `%s` found' % groupname, 'error')
|
|
Pierre-Yves Chibon |
1c93fc |
return flask.redirect(flask.url_for('.admin_groups'))
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
# Add new user to the group if asked
|
|
Pierre-Yves Chibon |
1c93fc |
form = forms.LostPasswordForm()
|
|
Pierre-Yves Chibon |
1c93fc |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
f05535 |
user = progit.lib.search_user(SESSION, username=form.username.data)
|
|
Pierre-Yves Chibon |
1c93fc |
if not user:
|
|
Pierre-Yves Chibon |
1c93fc |
flask.flash('No user `%s` found' % form.username.data, 'error')
|
|
Pierre-Yves Chibon |
1c93fc |
return flask.redirect(flask.url_for('.admin_group', group=group))
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
grp = model.ProgitUserGroup(
|
|
Pierre-Yves Chibon |
e04c77 |
group_id=group_obj.id,
|
|
Pierre-Yves Chibon |
e04c77 |
user_id=user.id
|
|
Pierre-Yves Chibon |
1c93fc |
)
|
|
Pierre-Yves Chibon |
1c93fc |
SESSION.add(grp)
|
|
Pierre-Yves Chibon |
1c93fc |
try:
|
|
Pierre-Yves Chibon |
1c93fc |
SESSION.flush()
|
|
Pierre-Yves Chibon |
1c93fc |
except SQLAlchemyError as err:
|
|
Pierre-Yves Chibon |
1c93fc |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
1c93fc |
flask.flash(
|
|
Pierre-Yves Chibon |
1c93fc |
'Could not add user `%s` to group `%s`.' % (
|
|
Pierre-Yves Chibon |
1c93fc |
user.user, group_obj.group),
|
|
Pierre-Yves Chibon |
1c93fc |
'error')
|
|
Pierre-Yves Chibon |
1c93fc |
APP.logger.debug(
|
|
Pierre-Yves Chibon |
1c93fc |
'Could not add user `%s` to group `%s`.' % (
|
|
Pierre-Yves Chibon |
1c93fc |
user.user, group_obj.group))
|
|
Pierre-Yves Chibon |
1c93fc |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
flask.flash('User `%s` added.' % user.user)
|
|
Pierre-Yves Chibon |
1c93fc |
SESSION.commit()
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
2c97c2 |
users = progit.lib.login.get_users_by_group(SESSION, group)
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
return flask.render_template(
|
|
Pierre-Yves Chibon |
1c93fc |
'login/admin_users.html',
|
|
Pierre-Yves Chibon |
1c93fc |
form=form,
|
|
Pierre-Yves Chibon |
1eab4f |
conf_form=forms.ConfirmationForm(),
|
|
Pierre-Yves Chibon |
1c93fc |
group=group_obj,
|
|
Pierre-Yves Chibon |
1c93fc |
users=users,
|
|
Pierre-Yves Chibon |
1c93fc |
)
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
1c93fc |
|
|
Pierre-Yves Chibon |
4e4b82 |
@APP.route('/admin/group/<group>/<user>/delete', methods=['POST'])</user></group>
|
|
Pierre-Yves Chibon |
4e4b82 |
@admin_required
|
|
Pierre-Yves Chibon |
4e4b82 |
def admin_group_user_delete(user, group):
|
|
Pierre-Yves Chibon |
4e4b82 |
""" Delete an user from a certain group
|
|
Pierre-Yves Chibon |
4e4b82 |
"""
|
|
Pierre-Yves Chibon |
4e4b82 |
# Add new user to the group if asked
|
|
Pierre-Yves Chibon |
4e4b82 |
form = forms.ConfirmationForm()
|
|
Pierre-Yves Chibon |
4e4b82 |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
2c97c2 |
group_obj = progit.lib.login.get_group(SESSION, group)
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
4e4b82 |
if not group_obj:
|
|
Pierre-Yves Chibon |
4e4b82 |
flask.flash('No group `%s` found' % groupname, 'error')
|
|
Pierre-Yves Chibon |
4e4b82 |
return flask.redirect(flask.url_for('.admin_groups'))
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
f05535 |
user = progit.lib.search_user(SESSION, username=user)
|
|
Pierre-Yves Chibon |
4e4b82 |
if not user:
|
|
Pierre-Yves Chibon |
4e4b82 |
flask.flash('No user `%s` found' % user, 'error')
|
|
Pierre-Yves Chibon |
4e4b82 |
return flask.redirect(flask.url_for('.admin_groups'))
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
2c97c2 |
user_grp = progit.lib.login.get_user_group(
|
|
Pierre-Yves Chibon |
2c97c2 |
SESSION, user.id, group_obj.id)
|
|
Pierre-Yves Chibon |
4e4b82 |
SESSION.delete(user_grp)
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
4e4b82 |
SESSION.commit()
|
|
Pierre-Yves Chibon |
4e4b82 |
flask.flash(
|
|
Pierre-Yves Chibon |
4e4b82 |
'User `%s` removed from the group `%s`' % (user.user, group))
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
4e4b82 |
return flask.redirect(flask.url_for('.admin_group', group=group))
|
|
Pierre-Yves Chibon |
4e4b82 |
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
@APP.route('/admin/group/<group>/delete', methods=['POST'])</group>
|
|
Pierre-Yves Chibon |
40de1d |
@admin_required
|
|
Pierre-Yves Chibon |
40de1d |
def admin_group_delete(group):
|
|
Pierre-Yves Chibon |
40de1d |
""" Delete a certain group
|
|
Pierre-Yves Chibon |
40de1d |
"""
|
|
Pierre-Yves Chibon |
40de1d |
# Add new user to the group if asked
|
|
Pierre-Yves Chibon |
40de1d |
form = forms.ConfirmationForm()
|
|
Pierre-Yves Chibon |
40de1d |
if form.validate_on_submit():
|
|
Pierre-Yves Chibon |
2c97c2 |
group_obj = progit.lib.login.get_group(SESSION, group)
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
if not group_obj:
|
|
Pierre-Yves Chibon |
40de1d |
flask.flash('No group `%s` found' % groupname, 'error')
|
|
Pierre-Yves Chibon |
40de1d |
return flask.redirect(flask.url_for('.admin_groups'))
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
SESSION.delete(group_obj)
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
SESSION.commit()
|
|
Pierre-Yves Chibon |
40de1d |
flask.flash(
|
|
Pierre-Yves Chibon |
40de1d |
'Group `%s` has been deleted' % (group))
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
return flask.redirect(flask.url_for('.admin_groups'))
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
40de1d |
|
|
Pierre-Yves Chibon |
9647e1 |
#
|
|
Pierre-Yves Chibon |
e5fe0e |
# Methods specific to local login.
|
|
Pierre-Yves Chibon |
e5fe0e |
#
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
def send_confirmation_email(user):
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Sends the confirmation email asking the user to confirm its email
|
|
Pierre-Yves Chibon |
e5fe0e |
address.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user.emails:
|
|
Pierre-Yves Chibon |
e5fe0e |
return
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
url = APP.config.get('APPLICATION_URL', flask.request.url_root)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
url = urlparse.urljoin(
|
|
Pierre-Yves Chibon |
e5fe0e |
url or flask.request.url_root,
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.url_for('confirm_user', token=user.token),
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
message = """ Dear %(username)s,
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
Thank you for registering on progit at %(url)s.
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
To finish your registration, please click on the following link or copy/paste
|
|
Pierre-Yves Chibon |
e5fe0e |
it in your browser:
|
|
Pierre-Yves Chibon |
e5fe0e |
%(url)s
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
You account will not be activated until you finish this step.
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
Sincerely,
|
|
Pierre-Yves Chibon |
e5fe0e |
Your progit admin.
|
|
Pierre-Yves Chibon |
e5fe0e |
""" % (
|
|
Pierre-Yves Chibon |
e5fe0e |
{
|
|
Pierre-Yves Chibon |
e5fe0e |
'username': user.username,
|
|
Pierre-Yves Chibon |
e5fe0e |
'url': url,
|
|
Pierre-Yves Chibon |
e5fe0e |
})
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
progit.notify.send_email(
|
|
Pierre-Yves Chibon |
e5fe0e |
text=message,
|
|
Pierre-Yves Chibon |
e5fe0e |
subject='[Progit] Confirm your user account',
|
|
Pierre-Yves Chibon |
e5fe0e |
to_mail=user.emails[0].email,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
def send_lostpassword_email(user):
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Sends the email with the information on how to reset his/her password
|
|
Pierre-Yves Chibon |
e5fe0e |
to the user.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
if not user.emails:
|
|
Pierre-Yves Chibon |
e5fe0e |
return
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
url = APP.config.get('APPLICATION_URL', flask.request.url_root)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
url = urlparse.urljoin(
|
|
Pierre-Yves Chibon |
e5fe0e |
url or flask.request.url_root,
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.url_for('reset_password', token=user.token),
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
message = """ Dear %(username)s,
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
The IP address %(ip)s has requested a password change for this account.
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
If you wish to change your password, please click on the following link or
|
|
Pierre-Yves Chibon |
e5fe0e |
copy/paste it in your browser:
|
|
Pierre-Yves Chibon |
e5fe0e |
%(url)s
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
If you did not request this change, please inform an admin immediately!
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
Sincerely,
|
|
Pierre-Yves Chibon |
e5fe0e |
Your progit admin.
|
|
Pierre-Yves Chibon |
e5fe0e |
""" % (
|
|
Pierre-Yves Chibon |
e5fe0e |
{
|
|
Pierre-Yves Chibon |
e5fe0e |
'username': user.username,
|
|
Pierre-Yves Chibon |
e5fe0e |
'url': url,
|
|
Pierre-Yves Chibon |
e5fe0e |
'ip': flask.request.remote_addr,
|
|
Pierre-Yves Chibon |
e5fe0e |
})
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
progit.notify.send_email(
|
|
Pierre-Yves Chibon |
e5fe0e |
text=message,
|
|
Pierre-Yves Chibon |
e5fe0e |
subject='[Progit] Confirm your password change',
|
|
Pierre-Yves Chibon |
e5fe0e |
to_mail=user.emails[0].email,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
def logout():
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Log the user out by expiring the user's session.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_session_id = None
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_user = None
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('You have been logged out')
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
def _check_session_cookie():
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Set the user into flask.g if the user is logged in.
|
|
Pierre-Yves Chibon |
e5fe0e |
"""
|
|
Pierre-Yves Chibon |
e5fe0e |
cookie_name = APP.config.get('PROGIT_COOKIE_NAME', 'progit')
|
|
Pierre-Yves Chibon |
e5fe0e |
session_id = None
|
|
Pierre-Yves Chibon |
e5fe0e |
user = None
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
if cookie_name and cookie_name in flask.request.cookies:
|
|
Pierre-Yves Chibon |
e5fe0e |
sessionid = flask.request.cookies[cookie_name]
|
|
Pierre-Yves Chibon |
2c97c2 |
session = progit.lib.login.get_session_by_visitkey(SESSION, sessionid)
|
|
Pierre-Yves Chibon |
e5fe0e |
if session and session.user:
|
|
Pierre-Yves Chibon |
e5fe0e |
now = datetime.datetime.now()
|
|
Pierre-Yves Chibon |
e5fe0e |
new_expiry = now + APP.config.get('PERMANENT_SESSION_LIFETIME')
|
|
Pierre-Yves Chibon |
e5fe0e |
if now > session.expiry:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Session timed-out', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
elif APP.config.get('CHECK_SESSION_IP', True) \
|
|
Pierre-Yves Chibon |
e5fe0e |
and session.user_ip != flask.request.remote_addr:
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash('Session expired', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
else:
|
|
Pierre-Yves Chibon |
e5fe0e |
session_id = session.visit_key
|
|
Pierre-Yves Chibon |
e5fe0e |
user = session.user
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
session.expiry = new_expiry
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.add(session)
|
|
Pierre-Yves Chibon |
e5fe0e |
try:
|
|
Pierre-Yves Chibon |
e5fe0e |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e5fe0e |
except SQLAlchemyError, err: # pragma: no cover
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.flash(
|
|
Pierre-Yves Chibon |
e5fe0e |
'Could not prolong the session in the db, '
|
|
Pierre-Yves Chibon |
e5fe0e |
'please report this error to an admin', 'error')
|
|
Pierre-Yves Chibon |
e5fe0e |
APP.logger.exception(err)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_session_id = session_id
|
|
Pierre-Yves Chibon |
e5fe0e |
flask.g.fas_user = user
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
def _send_session_cookie(response):
|
|
Pierre-Yves Chibon |
e5fe0e |
""" Set the session cookie if the user is authenticated. """
|
|
Pierre-Yves Chibon |
e5fe0e |
cookie_name = APP.config.get('PROGIT_COOKIE_NAME', 'progit')
|
|
Pierre-Yves Chibon |
e5fe0e |
secure = APP.config.get('PROGIT_COOKIE_REQUIRES_HTTPS', True)
|
|
Pierre-Yves Chibon |
e5fe0e |
|
|
Pierre-Yves Chibon |
e5fe0e |
response.set_cookie(
|
|
Pierre-Yves Chibon |
e5fe0e |
key=cookie_name,
|
|
Pierre-Yves Chibon |
e5fe0e |
value=flask.g.fas_session_id or '',
|
|
Pierre-Yves Chibon |
e5fe0e |
secure=secure,
|
|
Pierre-Yves Chibon |
e5fe0e |
httponly=True,
|
|
Pierre-Yves Chibon |
e5fe0e |
)
|
|
Pierre-Yves Chibon |
e5fe0e |
return response
|