import hashlib from flask import current_app, flash, g, redirect, render_template, \ request, session, url_for from sqlalchemy.sql.expression import bindparam, text from sqlalchemy.types import LargeBinary from skeleton import db from skeleton.lib import fixup_destination_url, local_request from .forms import LoginForm, ProfileForm, RegisterForm from . import fresh_login_required, gen_session_id, module from skeleton.models import Timezone from aaa.models.user import User from .user import get_user_id @module.route('/login', methods=('GET','POST')) def login(): form = LoginForm() # Generate a session ID for them if they don't have one if 'i' not in session: session['i'] = gen_session_id() fixup_destination_url('dsturl','post_login_url') if form.validate_on_submit(): remote_addr = request.environ['REMOTE_ADDR'] # Hash the password once here: h = hashlib.new('sha256') h.update(current_app.config['PASSWORD_HASH']) h.update(form.password.data) shapass = h.digest() # Change out the values of the session ttl idle = '1 second' if form.idle_ttl.data == 'tmp': idle = '20 minutes' elif form.idle_ttl.data == 'day': idle = '1 day' elif form.idle_ttl.data == 'week': idle = '1 week' else: flask.abort(500) # Generate a new session ID upon login. If someone steals my session # id, I want to explicitly prevent its use as a way of inject # unauthenticated session information in to an authenticated # session. In the future once pgmemcache has been hooked up to the # database, the old session id will be expired from memcache # automatically. new_sess_id = gen_session_id() ses = db.session result = ses.execute( text("SELECT ret, col, msg FROM aaa.login(:email, :pw, :ip, :sid, :idle, :secure) AS (ret BOOL, col TEXT, msg TEXT)", bindparams=[ bindparam('email', form.email.data), bindparam('pw', shapass, type_=LargeBinary), bindparam('ip', remote_addr), bindparam('sid', new_sess_id), bindparam('idle',idle), bindparam('secure', request.is_secure)])) # Explicitly commit regardless of the remaining logic. The database # did the right thing behind the closed doors of aaa.login() and we # need to make sure that the logging to shadow.aaa_login_attempts is # COMMIT'ed so that customer support can help the poor, frustrated # (stupid?) users. ses.commit() row = result.first() if row[0] == True: session['i'] = new_sess_id session['li'] = True flash('Successfully logged in as %s' % (form.email.data)) if 'post_login_url' in session: return redirect(session.pop('post_login_url')) else: return redirect(url_for('home.index')) else: session.pop('li', None) # Return a useful error message from the database try: # If the database says be vague, we'll be vague in our error # messages. When the database commands it we obey, got it? if row[1] == 'vague': # Set bogus data so that 'form.errors == True'. If brute # force weren't such an issue, we'd just append a field # error like below. If you want to get the specifics of # why the database rejected a user, temporarily change # the above 'vague' to something that the database # doesn't return, such as 'EDRAT' or something equally # POSIXly funny. form.errors['EPERM'] = 'There is no intro(2) error code for web errors' pass else: field = form.__getattribute__(row[1]) field.errors.append(row[2]) except AttributeError as e: pass return render_template('aaa/login.html', form=form) @module.route('/logout') def logout(): # Is there a destination post-logout? dsturl = None if request.referrer and local_request(request.referrer): dsturl = request.referrer else: dsturl = None # End the session in the database already_logged_out = False if 'li' in session: ses = db.session result = ses.execute( text("SELECT ret, col, msg FROM aaa.logout(:sid) AS (ret BOOL, col TEXT, msg TEXT)", bindparams=[bindparam('sid', session['i'])])) ses.commit() # For now, don't test the result of the logout call. Regardless of # whether or not a user provides us with a valid session ID from the # wrong IP address, terminate the session. Shoot first, ask questions # later (i.e. why was a BadUser in posession of GoodUser's session # ID?!) else: already_logged_out = True # Nuke every key in the session for k in session.keys(): session.pop(k) # Set a flash message after we nuke the keys in session if already_logged_out: flash('Session cleared for logged out user') else: flash('You were logged out') return render_template('aaa/logout.html', dsturl=dsturl) @module.route('/profile', methods=('GET','POST')) @fresh_login_required def profile(): user_id = get_user_id(session_id = session['i']) user = User.query.filter_by(user_id=user_id).first_or_404() form = ProfileForm(obj=user) form.timezone.query = Timezone.query.order_by(Timezone.name) if form.validate_on_submit(): shapass = None if form.password: # Hash the password once here: h = hashlib.new('sha256') h.update(current_app.config['PASSWORD_HASH']) h.update(form.password.data) shapass = h.digest() form.populate_obj(user) user.password = shapass db.session.add(user) db.session.commit() return render_template('aaa/profile.html', form=form) @module.route('/register', methods=('GET','POST')) def register(): form = RegisterForm() if 'i' not in session: session['i'] = gen_session_id() form.timezone.query = Timezone.query.order_by(Timezone.name) if form.validate_on_submit(): # Form validates, execute the registration pl function remote_addr = request.environ['REMOTE_ADDR'] # Hash the password once here: h = hashlib.new('sha256') h.update(current_app.config['PASSWORD_HASH']) h.update(form.password.data) shapass = h.digest() ses = db.session result = ses.execute( text("SELECT ret, col, msg FROM aaa.register(:email, :pw, :ip) AS (ret BOOL, col TEXT, msg TEXT)", bindparams=[ bindparam('email', form.email.data), bindparam('pw', shapass, type_=LargeBinary), bindparam('ip', remote_addr)])) row = result.first() if row[0] == True: # Update the user's timezone if they submitted a timezone if form.timezone.data: res = ses.execute( text("INSERT INTO aaa.user_info (user_id, timezone_id) VALUES (get_user_id_by_email(:email), :tz)", bindparams=[bindparam('email', form.email.data), bindparam('tz', form.timezone.data.id),])) ses.commit() flash('Thanks for registering! Please check your %s email account to confirm your email address.' % (form.email.data)) return redirect(url_for('aaa.login')) else: # Return a useful error message from the database try: field = form.__getattribute__(row[1]) field.errors.append(row[2]) except AttributeError as e: pass return render_template('aaa/register.html', form=form)