def _send_token(req, info): """Send an email token if the password was forgotten. _send_token(req, info) Returns a status page. """ import logging import os import manage_kbasix import manage_users import smtplib import aux from email.mime.text import MIMEText logging.debug('Starting forgotten password procedure for "%s"' % \ info['login_name']) if not manage_users._info(info['login_name']) or \ manage_users._info(info['login_name'])['locked'] or \ info['login_name'] in info['banned_logins']: info['class'] = 'fail' info['main_header'] = aux._make_header(info) info['title'] = 'Password reset' info['details'] = 'Either that user name was not found, you are \ not allowed into the system, or the account has yet to be activated.' info['status_button_1'] = '' info['status_button_2'] = '' logging.debug('Token not sent to "%s" because "%s"' % \ (info['login_name'], info['details'])) return aux._fill_page(info['status_page_'], info) uid = manage_users._info(info['login_name'])['uid'] user_email = manage_kbasix._account_info(info['login_name'], \ 'profile')['user_email'] # The token will only allow access to the profile module (which allows # to change the password). It should work from any IP address. token = manage_kbasix._create_token(req, uid, ip_set=False, \ access='profile.py') www_path = os.path.dirname((req.subprocess_env['SCRIPT_NAME'])) info['profile_url'] = \ req.construct_url(www_path + \ '/profile.py/process?start&token=%s' % token) msg = MIMEText(aux._fill_str(info['reset_password_notice'], info)) msg['Subject'] = aux._fill_str(info['reset_password_subject'], info) msg['From'] = info['reset_password_from_email_'] msg['To'] = user_email s = smtplib.SMTP(info['smtp_host_']) s.sendmail(info['reset_password_from_email_'], [user_email], \ msg.as_string()) s.quit() info['class'] = 'success' info['main_header'] = aux._make_header(info) info['title'] = 'Password reset' info['details'] = aux._fill_str(info['reset_password_blurb'], info) info['status_button_1'] = '' info['status_button_2'] = '' logging.info('Token sent due to forgotten password by "%s" to "%s"' % \ (info['login_name'], user_email)) return aux._fill_page(info['status_page_'], info)
def process(req): from aux import _make_header, _fill_page from defs import kbasix, confirm info = {} info.update(kbasix) info.update(confirm) import logging logging.basicConfig(level = getattr(logging, info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for confirm.py') info['details'] = '[SYS] Invalid request [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot confirm over an insecure connection.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) info['token'] = '' info['main_header'] = _make_header(info) if 'start' in req.form: try: return _confirm(req, info) except Exception as reason: logging.critical(reason) info['details'] = 'Unable to process confirmation [%s].' % info[ 'error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected action trying to confirm registration.' logging.warn(info['details']) return _fill_page(info['error_page_'], info)
def process(req): """Process the welcome page. process(req) """ # We import functions "as needed" at this stage, making # sure they are private (underscore prefix). The exception # is "defs", which are just a bunch of defintions (although # any functions defined within it should be made private). from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, main # Here and in all other modules the module-specific definitions # take precedence over the 'kbasix' ones. info = {} info.update(kbasix) info.update(main) import logging logging.basicConfig(level=getattr(logging, \ info['log_level_'].upper()), \ filename=info['log_file_'], \ datefmt=info['log_dateformat_'], \ format=info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for main.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) try: session = _is_session(req, required=False) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: # We use 'warn' level because the '_is_session' redirect seems to # trigger this (if 'required=True') although it's not a critical # error. logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) try: return _initialize(info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to initialize the main \ page [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info)
def process(req): """Process the logout page. process(req) """ from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, logout info = {} info.update(kbasix) info.update(logout) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for logout.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) try: session = _is_session(req, required=False) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) if not info['token']: info['title'] = 'Logout' info['class'] = 'information' info['details'] = 'You are not logged in.' info['status_button_1'] = '' info['status_button_2'] = '' return _fill_page(info['status_page_'], info) else: try: return _logout(info['token'], info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to terminate session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info)
def _logout(token, info): """Logout a user. _logout(token, info) Returns the logout status page. """ import logging import manage_kbasix import aux uid = int(token.split('-')[0]) manage_kbasix._delete_token(uid, token='*') info['token'] = '' info['title'] = aux._fill_str(info['goodbye_title'], info) info['class'] = 'information' info['details'] = aux._fill_str(info['goodbye_blurb'], info) info['status_button_1'] = '' info['status_button_2'] = '' info['main_header'] = aux._make_header(info) logging.debug('Successful logout (%s)' % info['login_name']) return aux._fill_page(info['status_page_'], info)
def process(req): """Process the profile page. process(req) """ from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, profile info = {} info.update(kbasix) info.update(profile) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for profile.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot edit your profile over an insecure \ connection.' return _fill_page(info['error_page_'], info) try: # We need to holdover (which only takes when "per_request_token" is # True) because we want to keep the restricted token generated when # a password is forgotten. Note that this check should be added to # any module which needs to be restricted. if 'token' in req.form and '-all-tk' not in req.form['token']: session = _is_session(req, required=True, holdover=True) else: session = _is_session(req, required=True) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) if 'start' in req.form: try: return _initialize(info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to initialize profile editor \ [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info) elif 'action' not in req.form: info['details'] = 'No action specified trying to edit profile.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) elif req.form['action'] == 'update': try: return _update(req, info) except UpdateError as reason: logging.critical(reason) info['details'] = '[SYS] Unable to update the profile \ [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to update the profile [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected action trying to update the profile.' logging.error(info['details']) return _fill_page(info['error_page_'], info)
def process(req): """Process the registration page. process(req) """ from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, register info = {} info.update(kbasix) info.update(register) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for register.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot register over an insecure connection.' logging.info('Disallowed insecure access to register.py') return _fill_page(info['error_page_'], info) try: session = _is_session(req, required=False) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) if 'start' in req.form: try: return _initialize(info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to initialize registration \ [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info) elif 'action' not in req.form: info['details'] = 'No action specified trying to register.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) elif req.form['action'] == 'register': try: return _register(req, info) except RegisterError as reason: logging.critical(reason) info['details'] = '[SYS] Unable to register [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to register [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected action trying to register.' logging.error(info['details']) return _fill_page(info['error_page_'], info)
def process(req): """Process the file manager page. process(req) """ from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, file_manager info = {} info.update(kbasix) info.update(file_manager) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for file_manager.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot manage your file over an insecure \ connection.' logging.info('Disallowed insecure access to file_manager.py') return _fill_page(info['error_page_'], info) try: # The holdover is required if "per_request_token" is True, and # has no effect otherwise. This is needed due to the # client-generated download window (which has no concept of the # new request token). if 'action' in req.form and req.form['action'] == 'download': session = _is_session(req, required=True, holdover=True) else: session = _is_session(req, required=True) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) info['filter'] = False if 'start' in req.form: try: return _initialize(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to initialize the file manager \ [%s].' % info['error_blurb_'] return _fill_page(info['error_page_'], info) elif 'action' not in req.form: info['details'] = 'No action specified within the file manager.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) elif req.form['action'] == 'filter': try: info['filter'] = True return _initialize(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] File manager error [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif req.form['action'] == 'copy_file': try: return _copy_file(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] File manager error [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif req.form['action'] == 'confirm_delete': try: return _confirm_delete(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] File manager error [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif req.form['action'] == 'delete': try: return _delete_file(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Error deleting file [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif req.form['action'] == 'confirm_bulk_delete': try: return _confirm_bulk_delete(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Error deleting files [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif req.form['action'] == 'download': try: return _download_file(req, info) except Exception as reason: # Not critical... user-end problem maybe? logging.error(reason) info['details'] = '[SYS] Unable to download file [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected file manager action.' logging.error(info['details']) return _fill_page(info['error_page_'], info)
def process(req): """Process the upload page. process(req) """ # Note that upload sizes can be configured via "LimitRequestBody" # either in .htaccess or httpd.conf (the latter requires an httpd # restart). However, doing so results in mod_python spitting out a # nasty error message ("Request Entity Too Large") upon hitting said # limit. See: # # http://www.mail-archive.com/[email protected]/msg87503.html # # We can somewhat gracefully bypass this behaviour by adding the # following to .htaccess (using 10485760 as an example, and keeping in # mind that on a production server PythonDebug should be Off in # python.conf): # # PythonDebug Off # LimitRequestBody 10485760 # ErrorDocument 413 /path/upload.py/process?file_limit=10485760 # ErrorDocument 500 "An error was encountered" # # Note that the ErrorDocument message should be small (see below), and # that the paths are relative to DocumentRoot. # # Now, this approach has the following issue: the file is _not_ # uploaded to the server, but instead seems to be stored on the client # side and for some reason takes a long time to process there # (strangely so, as it's not being transferred). This method seems to # work well for medium-sized files above LimitRequestBody, but then for # huge files the client shows a connection reset error, explained here: # # http://stackoverflow.com/questions/4467443/limitrequestbody-doesnt-respond-with-413-for-large-file25mb # # In spite of the fact that the client feedback is slow (and that the # connection is reset on large files) it may be worthwhile to keep # since at least this does limit the resources used on the server, # including avoiding the "phantom file" space usage on /tmp (downloads # are stored in /tmp as a "phantom file", its usage can be obtained via # "df", freeing it requires an httpd restart if the upload happens to # die in one of the "file too large" manners [just closing the tab does # not cause this]). # # In short: # # 0 < size < LimitRequestBody: Upload success ful (OK) # LimitRequestBody < size < ~1GB : Upload limit message # (OK) # ~1GB < size < ~2GB : Connection reset (file # too large) # ~2GB < size : Browsers barf (file # too large) from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page, _fill_str, _go_back_button from defs import kbasix, upload info = {} info.update(kbasix) info.update(upload) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for upload.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) # The following 'file_limit' comes from the 413 error redirect in # .htaccess. We disallow sneakiness by making sure the only argument # is an integer. Furthermore we note that no session handling is done # by this point (so, although the error page has no tokens, the browser # "Back" button will take us to our upload page, regardless of whether # the tokens are per-request). Since it's a dead-end error page with # no user-data it shouldn't affect the fact it lives outside a session's # scope. # Although the uploads page is session-protected the upload-limit error # message has to be public as we can't get the token from # .htaccess. Furthermore, it seems this message has to be pretty small # (returning "status_page_" gives "Request Entity Too Large"). Note # that the user temporarily loses their token, but has no other option # other than to browse "Back", recovering their token (also important # because HTTPS isn't required either). if 'file_limit' in req.form: file_limit = req.form['file_limit'].value try: max_size = int(file_limit) except: info['details'] = 'Non-numerical upload size' logging.error(info['details'] + ': %s' % file_limit) return _fill_page(info['error_page_'], info) info['details'] = 'The file you are trying to upload is too \ large (max size = %s bytes).' % max_size logging.warn(info['details']) return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot upload over an insecure connection.' logging.info(info['details']) return _fill_page(info['error_page_'], info) try: session = _is_session(req, required=True) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) # User cannot upload if over-quota. try: (info['user_dir_size'], over_page) = \ _check_quota(req, 'upload', info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to determine quota [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) if info['user_dir_size'] < 0: return over_page if 'start' in req.form: try: return _initialize(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to initialize upload [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif 'action' not in req.form: info['details'] = 'No action specified trying to upload.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) elif req.form['action'] == 'upload_file': try: return _get_file(req, info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Error uploading file [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected action trying to upload.' logging.error(info['details']) return _fill_page(info['error_page_'], info)
def _login(req, info): """Try to login the user, or send an email token if the password is forgotten. _login(req, info) Returns a status page. """ if 'forgot_password' in req.form: try: return _send_token(req, info) except Exception as reason: raise SendTokenError(reason) import logging from mod_python import apache import manage_users import manage_kbasix import time import aux from mod_python import util logging.debug('Starting login process for "%s"' % info['login_name']) if '*' in info['allowed_internal_logins'] or \ info['login_name'] in info['allowed_internal_logins']: allow_internal = True else: allow_internal = False password = req.form['user_password'].value # Note that '_authenticate' only returns the uid if 'is_usr' is # True, otherwise it'll return a keyword specifying the authentication # failure point. try: (is_usr, status) = manage_users._authenticate(info['login_name'], \ password) logging.info('Authentication for "%s" was "%s" with \ status/uid: %s' % (info['login_name'], is_usr, status)) except Exception as reason: raise LoginError(reason) blocked = False # We don't just show the reasons for authentication failures, but # instead use codes defined in 'defs.py' (the log is explicit in this # respect). if is_usr: uid = status locked = manage_users._info(info['login_name'])['locked'] if locked: blocked = True msg = info['reason_not_active_'] else: blocked = True msg = info['reason_auth_fail_'] if not blocked: auth_method = manage_users._info(info['login_name'])['auth_method'] # An empty 'auth_method' means the auth is internal. if not auth_method and not allow_internal: blocked = True msg = info['reason_not_allowed_'] elif info['login_name'] in info['banned_logins']: blocked = True msg = info['reason_banned_'] if not blocked: try: info['token'] = manage_kbasix._create_token(req, uid) info['user_name'] = \ manage_kbasix._account_info(info['login_name'], \ 'profile')['user_name'] info['access'] = 'all' info['class'] = 'information' info['main_header'] = aux._make_header(info) info['title'] = aux._fill_str(info['welcome_title'], info) info['details'] = aux._fill_str(info['welcome_blurb'], info) info['status_button_1'] = """ <form action="../%(referrer)s/process?start" method="post"> <input type="hidden" name="token" value="%(token)s"> <input type="submit" value="Continue" /> </form> """ % info info['status_button_2'] = '' manage_kbasix._account_mod(info['login_name'], \ 'profile', {'last_login': \ time.time()}) logging.info('Successful login from %s (%s)' % \ (req.get_remote_host(apache.REMOTE_NOLOOKUP), \ info['login_name'])) return aux._fill_page(info['status_page_'], info) except Exception as reason: raise LoginError(reason) else: info['class'] = 'fail' info['title'] = 'Login' info['details'] = aux._fill_str(msg, info) info['status_button_1'] = aux._go_back_button(req, token = '') info['status_button_2'] = '' logging.info('Failed login for "%s" because "%s"' % \ (info['login_name'], info['details'])) return aux._fill_page(info['status_page_'], info)
def process(req): """Process the login page. process(req) """ from manage_kbasix import _is_session, _account_info from aux import _make_header, _fill_page from defs import kbasix, login info = {} info.update(kbasix) info.update(login) import logging logging.basicConfig(level = getattr(logging, \ info['log_level_'].upper()), \ filename = info['log_file_'], \ datefmt = info['log_dateformat_'], \ format = info['log_format_']) if repr(type(req)) != "<type 'mp_request'>": logging.critical('Invalid request for login.py') info['details'] = '[SYS] Invalid request [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) if not req.is_https(): info['details'] = 'You cannot login over an insecure connection.' logging.info('Disallowed insecure access to login.py') return _fill_page(info['error_page_'], info) try: session = _is_session(req, required=False) info.update(session) if session['token']: profile = _account_info(info['login_name'], 'profile') info.update(profile) except Exception as reason: logging.warn(reason) info['details'] = '[SYS] Unable to verify session [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) # This page should not appear to people who are already logged in. if info['token']: info['details'] = 'You are already logged in as "%s".' % \ info['user_name'] logging.debug(info['details']) return _fill_page(info['error_page_'], info) info['main_header'] = _make_header(info) # When a page requires a user to be logged-in it sends its name as # "referrer", which is obtained from "req.canonical_filename", and # spoofing it shouldn't gain anything other than attempting to access # a different URL (which can be done by editing the URL directly # anyways). if 'referrer' not in req.form: info['referrer'] = 'main.py' else: info['referrer'] = req.form['referrer'].value if 'start' in req.form: try: return _initialize(info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to open login page [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) elif 'action' not in req.form: info['details'] = 'No action specified trying to login.' logging.warn(info['details']) return _fill_page(info['error_page_'], info) elif req.form['action'] == 'login': try: if not req.form['user_name']: info['details'] = 'You must input your user name.' return _fill_page(info['error_page_'], info) else: info['user_name'] = req.form['user_name'].value info['login_name'] = info['user_name'].lower() return _login(req, info) except SendTokenError as reason: logging.critical(reason) info['details'] = '[SYS] Unable to email new token [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) except LoginError as reason: logging.critical(reason) info['details'] = '[SYS] Unable to login [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) except Exception as reason: logging.critical(reason) info['details'] = '[SYS] Unable to login [%s].' % \ info['error_blurb_'] return _fill_page(info['error_page_'], info) else: info['details'] = 'Unexpected action trying to login.' logging.error(info['details']) return _fill_page(info['error_page_'], info)