def _check_permissions(file_info, info): """Check access permission on a file. _check_permissions(file_info, info) Returns a string: empty if access granted or a "denied access" page otherwise. """ import aux import manage_users gids = manage_users._info(info['login_name'])['gids'] denied_page = '' info['title'] = 'File unavailable' info['details'] = 'You no longer have access to this file.' info['status_button_1'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%s" /> <input type="submit" value="Files" /> </form> """ % info['token'] info['status_button_2'] = '' info['class'] = 'warning' if not file_info: denied_page = aux._fill_page(info['status_page_'], info) elif not info['uid'] == file_info['owner_uid'] and \ not file_info['local_share'] and \ not info['uid'] in file_info['uid_shares'] and \ not set(gids).intersection(set(file_info['gid_shares'])): denied_page = aux._fill_page(info['status_page_'], info) return denied_page
def _confirm(req, info): import logging import manage_kbasix import manage_users import aux logging.debug('Starting confirmation') info['title'] = aux._fill_str(info['confirmation_title'], info) info['status_button_1'] = '' info['status_button_2'] = '' if 'token' in req.form: token = req.form['token'] else: token = '' session = manage_kbasix._check_token(token, first_time=True) if not session: info['class'] = 'fail' info[ 'details'] = 'Failed to confirm account due to an invalid or stale token (probably the account confirmation deadline has passed)' logging.warn(info['details'] + ' [%s]' % token) return aux._fill_page(info['status_page_'], info) else: session['login_name'] = manage_users._info( session['uid'])['login_name'] info.update(session) manage_users._mod(info['login_name'], {'locked': False}) manage_kbasix._delete_token(info['uid']) profile = manage_kbasix._account_info(info['login_name'], 'profile') info.update(profile) info['class'] = 'success' info['details'] = aux._fill_str(info['successful_confirmation_blurb'], info) logging.info('Successfully confirmed account "%s"' % info['login_name']) return aux._fill_page(info['status_page_'], info)
def _check_permissions(file_info, info): """Check access permission on a file. _check_permissions(file_info, info) Returns a string: empty if access granted or a "denied access" page otherwise. """ import aux import manage_users gids = manage_users._info(info['login_name'])['gids'] denied_page = '' info['title'] = 'File unavailable' info['details'] = 'You no longer have access to this file.' info['status_button_1'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%s" /> <input type="submit" value="Files" /> </form> """ % info['token'] info['status_button_2'] = '' info['class'] = 'warning' if not file_info: denied_page = aux._fill_page(info['status_page_'], info) elif not info['uid'] == file_info['owner_uid'] and \ not file_info['local_share'] and \ not info['uid'] in file_info['uid_shares'] and \ not set(gids).intersection(set(file_info['gid_shares'])): denied_page = aux._fill_page(info['status_page_'], info) return denied_page
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 _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): """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 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 _check_quota(req, up_type, info): """Check the user's quota. 'up_type' is a string such as 'upload' or 'copy', depending on how the file is being added. Only '-file' files are counted towards quota. (user_dir_size, status) = _check_quota(req, up_type, info) Returns an (int, str) tuple with the user's space usage and an empty string if underquota, the number -1 and a "you are over quota" page (as a string) if overquota. Note that the quota doesn't impede a user from going over it, it just disallows adding files after it has been exceeded. Worst case scenario uses up approximately 'quota + max upload size'. """ import aux import logging user_dir_size = aux._get_dir_size(info) logging.debug('Space usage: %s/%s (%s)' % \ (user_dir_size, info['quota'], info['login_name'])) if user_dir_size >= info['quota']: info['user_dir_size'] = user_dir_size info['details'] = aux._fill_str(info['quota_limit_blurb'], info) info['class'] = 'fail' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' logging.info('File %s forbidden due to quota limits (%s)' % \ (up_type, info['login_name'])) info['title'] = up_type.capitalize() return (-1, aux._fill_page(info['status_page_'], info)) else: return (user_dir_size, '')
def _initialize(info): import logging import manage_kbasix import manage_users import aux profile = manage_kbasix._account_info(info['login_name'], 'profile') if '*' in info['allowed_internal_logins'] or \ info['login_name'] in info['allowed_internal_logins']: info['internal_disabled'] = '' else: info['internal_disabled'] = 'disabled' user = manage_users._info(info['login_name']) info['first_name'] = user['first_name'] info['last_name'] = user['last_name'] if user['auth_method'] == 'ldap': info['use_ldap'] = 'checked' else: info['use_ldap'] = '' info['ldap_options'] = '' for key in info['ldap_servers']: if user['auth_server'] == info['ldap_servers'][key]['server_']: info['ldap_servers'][key]['selected_'] = 'selected' info['ldap_options'] += info['ldap_servers'][key]['option_'] % info['ldap_servers'][key] info['user_ldap_name'] = user['user_auth_name'] info.update(profile) logging.debug('Starting profile page (%s)' % info['login_name']) return aux._fill_page(info['profile_page_'], info)
def _initialize(info): import logging import manage_kbasix import manage_users import aux profile = manage_kbasix._account_info(info['login_name'], 'profile') if '*' in info['allowed_internal_logins'] or \ info['login_name'] in info['allowed_internal_logins']: info['internal_disabled'] = '' else: info['internal_disabled'] = 'disabled' user = manage_users._info(info['login_name']) info['first_name'] = user['first_name'] info['last_name'] = user['last_name'] if user['auth_method'] == 'ldap': info['use_ldap'] = 'checked' else: info['use_ldap'] = '' info['ldap_options'] = '' for key in info['ldap_servers']: if user['auth_server'] == info['ldap_servers'][key]['server_']: info['ldap_servers'][key]['selected_'] = 'selected' info['ldap_options'] += info['ldap_servers'][key]['option_'] % info[ 'ldap_servers'][key] info['user_ldap_name'] = user['user_auth_name'] info.update(profile) logging.debug('Starting profile page (%s)' % info['login_name']) return aux._fill_page(info['profile_page_'], info)
def _initialize(info): """Initialize the welcome page. _initialize(info) Returns the KBasix welcome page. """ import aux import logging logging.debug('Starting main page (%s)' % info['login_name']) return aux._fill_page(info['main_page_'], info)
def _initialize(info): """Initialize the registration page. _initialize(info) Returns the KBasix registration page. """ import logging import aux # Only internal and LDAP authentication are currently supported. # Other mechanisms may be added by simply imitating how LDAP is done. info['ldap_options'] = '' for key in info['ldap_servers']: info['ldap_options'] += info['ldap_servers'][key]['option_'] % \ info['ldap_servers'][key] logging.debug('Starting registration page (%s)' % info['login_name']) return aux._fill_page(info['register_page_'], info)
def _initialize(info): """Initialize the registration page. _initialize(info) Returns the KBasix registration page. """ import logging import aux # Only internal and LDAP authentication are currently supported. # Other mechanisms may be added by simply imitating how LDAP is done. info['ldap_options'] = '' for key in info['ldap_servers']: info['ldap_options'] += info['ldap_servers'][key]['option_'] % \ info['ldap_servers'][key] logging.debug('Starting registration page (%s)' % info['login_name']) return aux._fill_page(info['register_page_'], info)
def _confirm_delete(req, info): """Query for a file deletion confirmation. _confirm_delete(req, info) Returns a status page. """ import logging import aux logging.debug('File deletion requested (%s)' % info['login_name']) info['file_tag'] = req.form['file_tag'].value _check_file_tag(info['file_tag'], info['login_name']) file_info = _get_file_info(info['file_tag'], info) # There is a check to see whether the file has the # appropriate access permissions, but it seems redundant # (the file is either deleted now or when the user refreshes # the file list). It doesn't hurt to keep, but this check # won't be done on bulk deletions, so deleting a single # file explicitly or by means of a "single-file-bulk-selection" # is slightly different. There's also a slight performance # penalty, so the future of the following three lines is not # guaranteed. denied_page = _check_permissions(file_info, info) if denied_page: return denied_page info['title'] = 'Confirm file deletion' info['class'] = 'information' info['details'] = 'Really delete file "%s"?' % file_info['file_name'] info['status_button_1'] = """ <form action="../file_manager.py/process?action=delete" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="hidden" name="file_tag" value="%(file_tag)s" /> <input type="submit" value="Delete" /> </form><br> """ % info info['status_button_2'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Cancel" /> </form> """ % info return aux._fill_page(info['status_page_'], info)
def _confirm_delete(req, info): """Query for a file deletion confirmation. _confirm_delete(req, info) Returns a status page. """ import logging import aux logging.debug('File deletion requested (%s)' % info['login_name']) info['file_tag'] = req.form['file_tag'].value _check_file_tag(info['file_tag'], info['login_name']) file_info = _get_file_info(info['file_tag'], info) # There is a check to see whether the file has the # appropriate access permissions, but it seems redundant # (the file is either deleted now or when the user refreshes # the file list). It doesn't hurt to keep, but this check # won't be done on bulk deletions, so deleting a single # file explicitly or by means of a "single-file-bulk-selection" # is slightly different. There's also a slight performance # penalty, so the future of the following three lines is not # guaranteed. denied_page = _check_permissions(file_info, info) if denied_page: return denied_page info['title'] = 'Confirm file deletion' info['class'] = 'information' info['details'] = 'Really delete file "%s"?' % file_info['file_name'] info['status_button_1'] = """ <form action="../file_manager.py/process?action=delete" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="hidden" name="file_tag" value="%(file_tag)s" /> <input type="submit" value="Delete" /> </form><br> """ % info info['status_button_2'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Cancel" /> </form> """ % info return aux._fill_page(info['status_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 _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 _initialize(req, info): """Initialize the upload page. _initialize(req, info) Returns the KBasix upload page. """ import logging import aux info['user_dir_size'] = aux._bytes_string(info['user_dir_size']) info['quota'] = aux._bytes_string(info['quota']) info['file_types_list'] = '\n' # This list of file types is arbitrary, and soley for the benefit # of the uploader (to aid the user in file organization). KBasix # does support some manner of magic type determination, but the # one provided by the user is not used at all for internal purposes. for i in info['file_types']: info['file_types_list'] += \ '<option value="%s">%s</option>\n' % (i, i.capitalize()) logging.debug('Starting upload page (%s)' % info['login_name']) return aux._fill_page(info['upload_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 _initialize(req, info): """Initialize the file manager page. _initialize(req, info) Returns the KBasix file manager page. """ import logging import aux import cgi import manage_kbasix logging.debug('Starting the file manager (%s)' % info['login_name']) info['user_dir_size'] = aux._bytes_string(aux._get_dir_size(info)) info['quota'] = aux._bytes_string(info['quota']) # Check to see if the files have been bulk-selected. if 'toggle_select' in req.form and \ req.form['toggle_select'].value == 'checked': info['toggle_select'] = 'checked' else: info['toggle_select'] = '' # The file manager settings are stored in the user's preferences file. prefs = manage_kbasix._account_info(info['login_name'], 'prefs') # Default file manager settings. if 'file_manager' not in prefs: logging.debug('Setting first-time preferences (%s)' % \ info['login_name']) prefs['file_manager'] = {} prefs['file_manager']['hidden_gidloc_shared_files'] = [] prefs['file_manager']['sort_criteria'] = \ info['default_sort_criteria_'] prefs['file_manager']['reverse'] = '' prefs['file_manager']['hide_shared'] = '' prefs['file_manager']['condensed_view'] = '' prefs['file_manager']['keywords'] = '' # Update the settings if filtering has been requested. for key in prefs['file_manager']: if not info['filter']: continue # This key is not directly changed by editing form values, but by # deleting shared entries. if key == 'hidden_gidloc_shared_files': continue if key in req.form: # We should use html.escape when migrating to python3 val = cgi.escape(req.form[key].value, True) if key == 'keywords': # If the keywords are not decoded here there are problems # substituting a mix of unicode and str e.g.: # # return '<html><body><p>%s</p><p>%s</p></body></html>' % \ # (info['main_header'], prefs['file_manager']['keywords']) # # fails. val = val.decode('utf-8') elif val and val \ not in info['allowed_sort_criteria'] + ['checked']: val = '' prefs['file_manager'][key] = val # This enables toggling. elif key not in req.form and prefs['file_manager'][key]: prefs['file_manager'][key] = '' # If someone tried to inject an invalid "sort_criteria" it'd be blanked # out above, and we fall back on "default_sort_criteria_" (invalid # radio buttons are already blanked out). if not prefs['file_manager']['sort_criteria']: prefs['file_manager']['sort_criteria'] = \ info['default_sort_criteria_'] info['sort_criteria_list'] = '\n' # Create the sort criteria drop. for i in info['allowed_sort_criteria']: s = '' if i == prefs['file_manager']['sort_criteria']: s = 'selected' info['sort_criteria_list'] += \ '<option %s value="%s">%s</option>\n' % \ (s, i, i.split('_')[-1].capitalize()) manage_kbasix._account_mod(info['login_name'], 'prefs', prefs) info['file_list'] = _get_file_list(info) info.update(prefs['file_manager']) return aux._fill_page(info['file_manager_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 _register(req, info): """Perform the registration process. _register(req, info) Returns the registration status page. """ import logging import re import time import manage_users import manage_kbasix import aux # User names will be unique, case-insensitive and case-aware. info['user_name'] = req.form['user_name'].value info['login_name'] = info['user_name'].lower() logging.info('Registering new account 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 info['user_email'] = req.form['user_email'].value err = 0 info['details'] = '' # KBasix is not yet tested on different locales, so we enforce # ASCII here. try: info['login_name'].encode('ascii') info['user_email'].encode('ascii') except: err += 1 info['details'] += 'Your user name and email address must be \ ASCII.<br>' if info['login_name'] in info['reserved_login_names']: err += 1 # This might be a lie, but the user need not know that. info['details'] += 'That user name is already taken.<br>' if info['login_name'] in info['blacklisted_login_names']: err += 1 info['details'] += 'That user name is not permitted.<br>' # Keep in mind that, regardless of %(login_name_max_length)s, problems # may arise if the file paths are too long (this is an issue with # with the fact that the user name is present as part of the "world" # URN). Note that there is a 100 character hard limit, regardless # of 'login_name_max_length'. if len(info['login_name']) > info['login_name_max_length']: err += 1 info['details'] += 'That user name is too long.<br>' if not info['login_name'].isalnum(): err += 1 info['details'] += 'Your user name can only contain letters and/or \ numbers.<br>' # To avoid UID look-alike names we can set 'alpha_start_login_name' if info['alpha_start_login_name'] and len(info['login_name']) > 0 and \ not info['login_name'][0].isalpha(): err += 1 info['details'] += 'The user name must begin with a letter.<br>' if len(info['user_email']) > info['email_max_length']: err += 1 info['details'] += 'That email address is too long.<br>' if not re.match(info['valid_email_'], info['user_email'], re.I): err += 1 info['details'] += 'Please input a valid email address.<br>' if 'use_ldap' in req.form: info['user_password'] = '******' info['auth_method'] = 'ldap' info['ldap_server'] = req.form['ldap_server'].value if info['ldap_server']: info['user_ldap_name'] = req.form['user_ldap_name'].value info['user_ldap_password'] = \ req.form['user_ldap_password'].value (err, info['details']) = _check_ldap(info, err) else: err += 1 info['details'] += 'Please specify the LDAP server.<br>' elif allow_internal: info['user_password'] = req.form['user_password'].value info['user_password_check'] = req.form['user_password_check'].value info['auth_method'] = '' info['ldap_server'] = info['user_ldap_name'] = '' (err, info['details']) = _check_password(info, err) else: err += 1 info['details'] += 'Unavailable authentication scheme.<br>' if not err: deadline = time.time() + info['account_confirmation_timeout'] try: (OK, dt) = manage_users._user_add(login_name = \ info['login_name'], \ password = \ info['user_password'], \ auth_method = \ info['auth_method'], \ user_auth_name = \ info['user_ldap_name'], \ auth_server = \ info['ldap_server'], \ expires = deadline) if OK: now = time.time() profile = {'registered': now, \ 'login_name': info['login_name'], \ 'user_name': info['user_name'], \ 'user_email': info['user_email'], \ 'quota': info['default_quota'], \ 'last_login': now} manage_kbasix._account_add(info['login_name'], profile) except Exception as reason: raise RegisterError(reason) if not OK: err += 1 info['details'] += dt else: try: _confirmation_email(req, info) except: err += 1 info['details'] += 'Unable to send confirmation email.<br>' if err: info['class'] = 'fail' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' logging.info('Failed account registration for "%s" because "%s"' % \ (info['login_name'], \ info['details'].replace('<br>',' '))) else: info['class'] = 'success' info['details'] += \ aux._fill_str(info['successful_registration_blurb'], info) info['status_button_1'] = '' info['status_button_2'] = '' logging.info('Successful account registration for "%s"' % \ info['login_name']) info['title'] = 'Registration' return aux._fill_page(info['status_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 _initialize(req, info): """Initialize the metadata editor page. _initialize(req, info) Returns the KBasix metadata editor page. """ import logging import aux import file_manager file_info = file_manager._get_file_info(info['file_tag'], info) # If 'file_info' is empty then that file has been unshared from # underneath the user, i.e. they couldn't have edited either way. if not file_info or file_info['owner_uid'] != info['uid']: info['title'] = 'Metadata unavailable' info['details'] = \ 'You cannot edit the metadata of a file you do not own.' info['status_button_1'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%s" /> <input type="submit" value="Back" /> </form> """ % info['token'] info['status_button_2'] = '' info['class'] = 'warning' return aux._fill_page(info['status_page_'], info) else: logging.debug('Starting the metadata editor (%s)' % \ info['login_name']) info['file_manager_button'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%s" /> <input type="submit" value="Files" /> </form> """ % info['token'] entry = {} # We need to translate the numeric UIDs and GIDs in actual # names. Also, the local and world booleans are converted # into their HTML equivalents. (entry['uid_shares'], entry['gid_shares'], entry['local_share'], \ entry['world_share']) = _get_shares(file_info, info) # If a file has been shared with the world, i.e. if it's directly # available via http the URL is set here. if entry['world_share']: import manage_kbasix import os user_name = manage_kbasix._account_info(info['login_name'], \ 'profile')['user_name'] entry['world_url'] = \ os.path.join(info['www_url_'], user_name, info['file_tag']) else: entry['world_url'] = '' info['boolean_data'] = aux._fill_str(info['boolean_meta_'], entry) # There are four types of metadata: # core metadata: set upon creation e.g. MD5SUM. # basic metadata: non-boolean, user-editable subset of the core # metadata e.g. UID shares. # boolean metadata: two booleans which toggles between sharing # with registered users/world. # custom metadata: metadata created by the user. # Note that basic is just an arbitrary distinction, whilst custom is # stored under a different key (file_info['custom']). Basic metadata # is defined by metaeditor['basic_meta'] in defs.py. # # The 'meta_template_' is the template defined in defs.py which # controls the information and layout of the entry. info['basic_data'] = '' for key, value in file_info.items(): if key not in info['basic_meta']: continue data = {} data['key'] = key # The shares are replaced by the names, not the numeric values. if key in ['uid_shares', 'gid_shares']: data['value'] = entry[key] else: data['value'] = value data.update(info['basic_meta'][key]) info['basic_data'] += aux._fill_str(info['meta_template_'], data) info['custom_data'] = '' for key, value in file_info['custom'].items(): data = {} data['key'] = key data['name'] = key data['value'] = value data['help'] = '' info['custom_data'] += aux._fill_str(info['meta_template_'], data) info['file_name'] = file_info['file_name'] return aux._fill_page(info['metaeditor_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 _confirm_bulk_delete(req, info): """Query for a multiple file deletion confirmation. _confirm_bulk_delete(req, info) Returns a status page. """ import logging import aux logging.debug('Bulk file deletion requested (%s)' % info['login_name']) info['title'] = 'Confirm the deletion of selected files' files = info['file_tags'] = '' n = 0 if 'bulk' not in req.form or not req.form['bulk']: info['details'] = 'No files selected' info['class'] = 'warning' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' return aux._fill_page(info['status_page_'], info) for file_tag in req.form['bulk']: # When only one file is selected. if not isinstance(req.form['bulk'], list): file_tag = req.form['bulk'].value _check_file_tag(file_tag, info['login_name']) file_info = _get_file_info(file_tag, info) # We're not checking permissions on each file (see the # comment in '_confirm_delete'). It'd be a hassle to # mess up a carefully selected list of files to announce # some of them cannot be deleted, even if the final outcome is # the same i.e. getting rid of the file(s). In the end the file(s) # will not show up once the file managers refreshes, either # because they have been deleted or access to them denied. if file_info: info['file_tags'] += ' ' + file_tag files += file_info['file_name'] + '<br>' n += 1 if not isinstance(req.form['bulk'], list): break if n == 0: # This could happen is one file is selected via checkbox having # been unshared from underneath the user. logging.warn('No deletable files found (%s)' % info['login_name']) info['class'] = 'warning' info['details'] = 'No files which you can delete were found.' info['status_button_1'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Back" /> </form> """ % info info['status_button_2'] = '' return aux._fill_page(info['status_page_'], info) elif n == 1: q = 'file' else: q = '%s files' % n info['details'] = 'Really delete the following %s?<br>%s' % (q, files) info['status_button_1'] = """ <form action="../file_manager.py/process?action=delete" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="hidden" name="file_tags" value="%(file_tags)s" /> <input type="submit" value="Delete" /> </form><br> """ % info info['status_button_2'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Cancel" /> </form> """ % info info['class'] = 'information' return aux._fill_page(info['status_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 _get_file(req, info): """Upload a file (-file) and create its associate metadata (-file-id) file. _get_file(req, info) Returns the KBasix upload status page. """ import os import cgi import time import hashlib import json import aux import logging from mod_python import apache # Definitions. # A time/hash pair is something like this: # # time 1345131658.602529 # hash 60f6bee3404d57f04bb47d9369da5b20e4e510eb0ced4011f3de065590407399 # # id and hash are the same string # '-file' and '-file-id' are those strings, so that time-hash-file-id # is something like: # # 1345131658.602529-60f6bee3404d...407399-file-id # # id_tag is time-hash # file_tag is time-hash-file # (this file_tag is also the name of the actual data file) # id_file is time-hash-file-id # (this id_file is also the name of the actual metadata file) id_tag = '%r-%s' % \ (time.time(), \ hashlib.sha256(os.urandom(info['random_length'])).hexdigest()) file_tag = id_tag + '-file' # The user directories are determined by their UID. They are created # when the account is created. Remember that UID is always an integer. file_out = os.path.join(info['users_root_dir_'], str(info['uid']), \ file_tag) id_file = file_out + '-id' fileitem = req.form['file_name'] # In order to substitute this value into the message strings it must # be decoded. file_in = fileitem.filename.decode('utf-8') if file_in: d = hashlib.md5() # Buffer size is set to 2^16, a magic number which seems OK, # but should be justified (or changed). Upping to 2**24 didn't # seem to make a difference. Note this also has to be changed # in the _fbuffer function above. # Browsers seem to be able to handle files up to 2GB in size. # Uploading a 2082168350b binary file on a 100Mb LAN took about # 4 minutes. # Progress bars are a no-go (for now?): # http://www.mailinglistarchive.com/[email protected]/msg01880.html logging.info('Starting to upload "%s" (%s)' % \ (file_out, info['login_name'])) try: f = open(file_out, 'wb', 2**16) for chunk in _fbuffer(fileitem.file): d.update(chunk) f.write(chunk) except Exception as reason: logging.error('Unable to upload the file type because \ "%s" (%s)' % (reason, info['login_name'])) raise GetFileError('Upload failed, closing "%s"' % file_out) finally: f.close() os.chmod(file_out, 0600) s = os.path.getsize(file_out) # You can add to the "id_info" dictionary any other metadata # you wish to save, but the following is the basic information # all files will contain. id_info = {} id_info['owner'] = info['login_name'] id_info['owner_uid'] = info['uid'] id_info['uid_shares'] = [] id_info['gid_shares'] = [] id_info['local_share'] = False id_info['world_share'] = False id_info['timestamp'] = time.time() id_info['file_title'] = req.form['file_title'].value id_info['file_description'] = req.form['file_description'].value file_type = req.form['file_type'].value if file_type not in info['file_types']: id_info['file_type'] = 'Unknown' else: id_info['file_type'] = file_type id_info['file_name'] = file_in id_info['file_md5sum'] = d.hexdigest() id_info['file_size'] = s id_info['file_tag'] = file_tag # Documentation for python-magic seems non-existent, but it seems # to be equivalent to the "file" command (which isn't that great). try: import magic magic_type = magic.open(magic.MAGIC_NONE) magic_type.load() id_info['magic_type'] = magic_type.file(file_out) except Exception as reason: logging.error('Unable to determine the file type because \ "%s" (%s)' % (reason, info['login_name'])) id_info['magic_type'] = 'Unknown' id_info['custom'] = {} for key in id_info: if isinstance(id_info[key], basestring): # We should use html.escape when migrating to python3 id_info[key] = cgi.escape(id_info[key], True) try: f = open(id_file, 'wb') json.dump(id_info, f) except Exception as reason: logging.error('Unable to create the metadata file because \ "%s" (%s)' % (reason, info['login_name'])) if os.isfile(file_out): os.remove(file_out) raise GetFileError('Metadata creation failed, deleted upload \ file "%s" and closing "%s"' % (file_out, id_file)) finally: f.close() os.chmod(id_file, 0600) n = id_info['file_name'] if s == 0: info['class'] = 'warning' info['details'] = 'File "%s" uploaded but empty (it may be \ client-side unreadable)' % n else: s = aux._bytes_string(s) info['class'] = 'success' info['details'] = aux._fill_str(info['successful_upload_'], \ {'name': \ id_info['file_name'], \ 'type': \ id_info['file_type'], \ 'size': s, \ 'md5': d.hexdigest()}) logging.info('Finished uploading and saving "%s" (%s)' % \ (file_out, info['login_name'])) else: info['class'] = 'fail' info['details'] = 'No file was specified.' info['title'] = 'File Upload' # We don't use the usual "go back" button because it will show the # upload animation. info['status_button_1'] = """ <form action="../upload.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Back" /> </form> """ % info info['status_button_2'] = '' return aux._fill_page(info['status_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 _register(req, info): """Perform the registration process. _register(req, info) Returns the registration status page. """ import logging import re import time import manage_users import manage_kbasix import aux # User names will be unique, case-insensitive and case-aware. info['user_name'] = req.form['user_name'].value info['login_name'] = info['user_name'].lower() logging.info('Registering new account 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 info['user_email'] = req.form['user_email'].value err = 0 info['details'] = '' # KBasix is not yet tested on different locales, so we enforce # ASCII here. try: info['login_name'].encode('ascii') info['user_email'].encode('ascii') except: err += 1 info['details'] += 'Your user name and email address must be \ ASCII.<br>' if info['login_name'] in info['reserved_login_names']: err += 1 # This might be a lie, but the user need not know that. info['details'] += 'That user name is already taken.<br>' if info['login_name'] in info['blacklisted_login_names']: err += 1 info['details'] += 'That user name is not permitted.<br>' # Keep in mind that, regardless of %(login_name_max_length)s, problems # may arise if the file paths are too long (this is an issue with # with the fact that the user name is present as part of the "world" # URN). Note that there is a 100 character hard limit, regardless # of 'login_name_max_length'. if len(info['login_name']) > info['login_name_max_length']: err += 1 info['details'] += 'That user name is too long.<br>' if not info['login_name'].isalnum(): err += 1 info['details'] += 'Your user name can only contain letters and/or \ numbers.<br>' # To avoid UID look-alike names we can set 'alpha_start_login_name' if info['alpha_start_login_name'] and len(info['login_name']) > 0 and \ not info['login_name'][0].isalpha(): err += 1 info['details'] += 'The user name must begin with a letter.<br>' if len(info['user_email']) > info['email_max_length']: err += 1 info['details'] += 'That email address is too long.<br>' if not re.match(info['valid_email_'], info['user_email'], re.I): err += 1 info['details'] += 'Please input a valid email address.<br>' if 'use_ldap' in req.form: info['user_password'] = '******' info['auth_method'] = 'ldap' info['ldap_server'] = req.form['ldap_server'].value if info['ldap_server']: info['user_ldap_name'] = req.form['user_ldap_name'].value info['user_ldap_password'] = \ req.form['user_ldap_password'].value (err, info['details']) = _check_ldap(info, err) else: err += 1 info['details'] += 'Please specify the LDAP server.<br>' elif allow_internal: info['user_password'] = req.form['user_password'].value info['user_password_check'] = req.form['user_password_check'].value info['auth_method'] = '' info['ldap_server'] = info['user_ldap_name'] = '' (err, info['details']) = _check_password(info, err) else: err += 1 info['details'] += 'Unavailable authentication scheme.<br>' if not err: deadline = time.time() + info['account_confirmation_timeout'] try: (OK, dt) = manage_users._user_add(login_name = \ info['login_name'], \ password = \ info['user_password'], \ auth_method = \ info['auth_method'], \ user_auth_name = \ info['user_ldap_name'], \ auth_server = \ info['ldap_server'], \ expires = deadline) if OK: now = time.time() profile = {'registered': now, \ 'login_name': info['login_name'], \ 'user_name': info['user_name'], \ 'user_email': info['user_email'], \ 'quota': info['default_quota'], \ 'last_login': now} manage_kbasix._account_add(info['login_name'], profile) except Exception as reason: raise RegisterError(reason) if not OK: err += 1 info['details'] += dt else: try: _confirmation_email(req, info) except: err += 1 info['details'] += 'Unable to send confirmation email.<br>' if err: info['class'] = 'fail' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' logging.info('Failed account registration for "%s" because "%s"' % \ (info['login_name'], \ info['details'].replace('<br>',' '))) else: info['class'] = 'success' info['details'] += \ aux._fill_str(info['successful_registration_blurb'], info) info['status_button_1'] = '' info['status_button_2'] = '' logging.info('Successful account registration for "%s"' % \ info['login_name']) info['title'] = 'Registration' 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)
def _initialize(req, info): """Initialize the file manager page. _initialize(req, info) Returns the KBasix file manager page. """ import logging import aux import cgi import manage_kbasix logging.debug('Starting the file manager (%s)' % info['login_name']) info['user_dir_size'] = aux._bytes_string(aux._get_dir_size(info)) info['quota'] = aux._bytes_string(info['quota']) # Check to see if the files have been bulk-selected. if 'toggle_select' in req.form and \ req.form['toggle_select'].value == 'checked': info['toggle_select'] = 'checked' else: info['toggle_select'] = '' # The file manager settings are stored in the user's preferences file. prefs = manage_kbasix._account_info(info['login_name'], 'prefs') # Default file manager settings. if 'file_manager' not in prefs: logging.debug('Setting first-time preferences (%s)' % \ info['login_name']) prefs['file_manager'] = {} prefs['file_manager']['hidden_gidloc_shared_files'] = [] prefs['file_manager']['sort_criteria'] = \ info['default_sort_criteria_'] prefs['file_manager']['reverse'] = '' prefs['file_manager']['hide_shared'] = '' prefs['file_manager']['condensed_view'] = '' prefs['file_manager']['keywords'] = '' # Update the settings if filtering has been requested. for key in prefs['file_manager']: if not info['filter']: continue # This key is not directly changed by editing form values, but by # deleting shared entries. if key == 'hidden_gidloc_shared_files': continue if key in req.form: # We should use html.escape when migrating to python3 val = cgi.escape(req.form[key].value, True) if key == 'keywords': # If the keywords are not decoded here there are problems # substituting a mix of unicode and str e.g.: # # return '<html><body><p>%s</p><p>%s</p></body></html>' % \ # (info['main_header'], prefs['file_manager']['keywords']) # # fails. val = val.decode('utf-8') elif val and val \ not in info['allowed_sort_criteria'] + ['checked']: val = '' prefs['file_manager'][key] = val # This enables toggling. elif key not in req.form and prefs['file_manager'][key]: prefs['file_manager'][key] = '' # If someone tried to inject an invalid "sort_criteria" it'd be blanked # out above, and we fall back on "default_sort_criteria_" (invalid # radio buttons are already blanked out). if not prefs['file_manager']['sort_criteria']: prefs['file_manager']['sort_criteria'] = \ info['default_sort_criteria_'] info['sort_criteria_list'] = '\n' # Create the sort criteria drop. for i in info['allowed_sort_criteria']: s = '' if i == prefs['file_manager']['sort_criteria']: s = 'selected' info['sort_criteria_list'] += \ '<option %s value="%s">%s</option>\n' % \ (s, i, i.split('_')[-1].capitalize()) manage_kbasix._account_mod(info['login_name'], 'prefs', prefs) info['file_list'] = _get_file_list(info) info.update(prefs['file_manager']) return aux._fill_page(info['file_manager_page_'], info)
def _update(req, info): import logging import re import time import manage_users import manage_kbasix import aux if '*' in info['allowed_internal_logins'] or \ info['login_name'] in info['allowed_internal_logins']: allow_internal = True else: allow_internal = False profile = {} account = {} account['first_name'] = req.form['first_name'].value account['last_name'] = req.form['last_name'].value profile['user_email'] = req.form['user_email'].value err = 0 info['details'] = '' if not re.match(info['valid_email_'], profile['user_email'], re.I): err += 1 info['details'] += 'Please input a valid email address.<br>' if 'user_password' in req.form: info['user_password'] = req.form['user_password'].value else: info['user_password'] = '' info['user_ldap_password'] = req.form['user_ldap_password'].value info['user_ldap_name'] = req.form['user_ldap_name'].value was_external = manage_users._info(info['login_name'])['auth_method'] if 'use_ldap' in req.form and info['user_ldap_password']: if not info['user_ldap_name']: err += 1 info['details'] += 'Please fill out all the LDAP credentials.<br>' else: from register import _check_ldap info['ldap_server'] = req.form['ldap_server'].value if info['ldap_server']: (err, info['details']) = _check_ldap(info, err) else: err += 1 info['details'] += 'Please specify the LDAP server.<br>' if not err: account['password'] = '******' account['auth_method'] = 'ldap' account['auth_server'] = info['ldap_server'] account['user_auth_name'] = info['user_ldap_name'] if not was_external: info[ 'details'] += 'Authentication is no longer internal.<br>' elif info['user_password'] and allow_internal: from register import _check_password info['user_password_check'] = req.form['user_password_check'].value (err, info['details']) = _check_password(info, err) if not err: account['auth_method'] = '' account['auth_server'] = account['user_auth_name'] = '' account['password'] = info['user_password'] if was_external: info['details'] += 'Authentication is now internal.<br>' else: info['details'] += 'Note: no authentication changes made.<br>' if not err: try: manage_kbasix._account_mod(info['login_name'], 'profile', profile) manage_users._mod(info['login_name'], account) except Exception as reason: raise UpdateError(reason) if err: info['class'] = 'fail' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' logging.debug('Failed profile update because "%s" (%s)' % \ (info['details'].replace('<br>',' '), info['login_name'])) else: info['class'] = 'success' info['details'] += aux._fill_str(info['successful_update_blurb'], info) if info['access'] == 'profile.py': info['status_button_1'] = """ <form action="../login.py/process?start" method="post"> <input type="submit" value="Login" /> </form><br> """ % info else: info['status_button_1'] = '' info['status_button_2'] = '' logging.debug('Successful profile update (%s)' % info['login_name']) info['title'] = 'User Profile' return aux._fill_page(info['status_page_'], info)
def _confirm_bulk_delete(req, info): """Query for a multiple file deletion confirmation. _confirm_bulk_delete(req, info) Returns a status page. """ import logging import aux logging.debug('Bulk file deletion requested (%s)' % info['login_name']) info['title'] = 'Confirm the deletion of selected files' files = info['file_tags'] = '' n = 0 if 'bulk' not in req.form or not req.form['bulk']: info['details'] = 'No files selected' info['class'] = 'warning' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' return aux._fill_page(info['status_page_'], info) for file_tag in req.form['bulk']: # When only one file is selected. if not isinstance(req.form['bulk'], list): file_tag = req.form['bulk'].value _check_file_tag(file_tag, info['login_name']) file_info = _get_file_info(file_tag, info) # We're not checking permissions on each file (see the # comment in '_confirm_delete'). It'd be a hassle to # mess up a carefully selected list of files to announce # some of them cannot be deleted, even if the final outcome is # the same i.e. getting rid of the file(s). In the end the file(s) # will not show up once the file managers refreshes, either # because they have been deleted or access to them denied. if file_info: info['file_tags'] += ' ' + file_tag files += file_info['file_name'] + '<br>' n += 1 if not isinstance(req.form['bulk'], list): break if n == 0: # This could happen is one file is selected via checkbox having # been unshared from underneath the user. logging.warn('No deletable files found (%s)' % info['login_name']) info['class'] = 'warning' info['details'] = 'No files which you can delete were found.' info['status_button_1'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Back" /> </form> """ % info info['status_button_2'] = '' return aux._fill_page(info['status_page_'], info) elif n == 1: q = 'file' else: q = '%s files' % n info['details'] = 'Really delete the following %s?<br>%s' % (q, files) info['status_button_1'] = """ <form action="../file_manager.py/process?action=delete" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="hidden" name="file_tags" value="%(file_tags)s" /> <input type="submit" value="Delete" /> </form><br> """ % info info['status_button_2'] = """ <form action="../file_manager.py/process?start" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Cancel" /> </form> """ % info info['class'] = 'information' 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 _update(req, info): import logging import re import time import manage_users import manage_kbasix import aux if '*' in info['allowed_internal_logins'] or \ info['login_name'] in info['allowed_internal_logins']: allow_internal = True else: allow_internal = False profile = {} account = {} account['first_name'] = req.form['first_name'].value account['last_name'] = req.form['last_name'].value profile['user_email'] = req.form['user_email'].value err = 0 info['details'] = '' if not re.match(info['valid_email_'], profile['user_email'], re.I): err += 1 info['details'] += 'Please input a valid email address.<br>' if 'user_password' in req.form: info['user_password'] = req.form['user_password'].value else: info['user_password'] = '' info['user_ldap_password'] = req.form['user_ldap_password'].value info['user_ldap_name'] = req.form['user_ldap_name'].value was_external = manage_users._info(info['login_name'])['auth_method'] if 'use_ldap' in req.form and info['user_ldap_password']: if not info['user_ldap_name']: err += 1 info['details'] += 'Please fill out all the LDAP credentials.<br>' else: from register import _check_ldap info['ldap_server'] = req.form['ldap_server'].value if info['ldap_server']: (err, info['details']) = _check_ldap(info, err) else: err += 1 info['details'] += 'Please specify the LDAP server.<br>' if not err: account['password'] = '******' account['auth_method'] = 'ldap' account['auth_server'] = info['ldap_server'] account['user_auth_name'] = info['user_ldap_name'] if not was_external: info['details'] += 'Authentication is no longer internal.<br>' elif info['user_password'] and allow_internal: from register import _check_password info['user_password_check'] = req.form['user_password_check'].value (err, info['details']) = _check_password(info, err) if not err: account['auth_method'] = '' account['auth_server'] = account['user_auth_name'] = '' account['password'] = info['user_password'] if was_external: info['details'] += 'Authentication is now internal.<br>' else: info['details'] += 'Note: no authentication changes made.<br>' if not err: try: manage_kbasix._account_mod(info['login_name'], 'profile', profile) manage_users._mod(info['login_name'], account) except Exception as reason: raise UpdateError(reason) if err: info['class'] = 'fail' info['status_button_1'] = aux._go_back_button(req, info['token']) info['status_button_2'] = '' logging.debug('Failed profile update because "%s" (%s)' % \ (info['details'].replace('<br>',' '), info['login_name'])) else: info['class'] = 'success' info['details'] += aux._fill_str(info['successful_update_blurb'], info) if info['access'] == 'profile.py': info['status_button_1'] = """ <form action="../login.py/process?start" method="post"> <input type="submit" value="Login" /> </form><br> """ % info else: info['status_button_1'] = '' info['status_button_2'] = '' logging.debug('Successful profile update (%s)' % info['login_name']) info['title'] = 'User Profile' return aux._fill_page(info['status_page_'], info)
def _update(req, info): """Update the metadata. _update(req, info) Returns a status web page. """ import logging import aux import cgi import file_manager info['file_tag'] = req.form['file_tag'].value logging.debug('Updating metadata of "%s" (%s)' % \ (info['file_tag'], info['login_name'])) info['title'] = 'Metadata Editor' # This is a generic status button, we make the variable substitutions # below. info['status_button_1'] = """ <form action="../metaeditor.py/process?start&file_tag=%(file_tag)s" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="submit" value="Back" /> </form> """ info['status_button_2'] = '' file_manager._check_file_tag(info['file_tag'], info['login_name']) file_info = file_manager._get_file_info(info['file_tag'], info) if not file_info or file_info['owner_uid'] != info['uid']: raise UpdateError('Invalid file ownership "%s" (%s)' % \ (info['file_tag'], info['login_name'])) info['details'] = '' form_dict = req.form if 'local_share' not in form_dict: form_dict['local_share'] = '' if 'world_share' not in form_dict: form_dict['world_share'] = '' # Single quotes are escaped (string is inside '') # Without .value: 'file_type': Field('file_type', 'Unknown') # With .value: 'file_type': 'Unknown' for key, rawval in form_dict.items(): val = cgi.escape(rawval.value, True) if key in info['basic_meta'] or key in \ ['local_share', 'world_share']: if key in ['uid_shares', 'gid_shares', 'local_share', \ 'world_share']: err = '' try: val.encode('ascii') except: info['class'] = 'fail' info['details'] += 'Shares must be ASCII-encoded' info['status_button_1'] = info['status_button_1'] % info logging.debug('Non-ASCII shares found (%s)' % \ info['login_name']) return aux._fill_page(info['status_page_'], info) logging.debug('Processing share "%s" with input value \ "%s" (%s)' % (key, val, info['login_name'])) # Point shares are done with specific users and groups. # Wide shares are with all registered users or the world. if key in ['uid_shares', 'gid_shares']: (file_info[key], err) = \ _set_point_shares(file_info[key], val, key, info) if key in ['local_share', 'world_share']: file_info[key] = \ _set_wide_share(file_info[key], val, key, info) info['details'] += err else: file_info[key] = val elif key in file_info['custom']: file_info[key] = val elif key in ['token', 'file_tag', 'action']: pass else: try: key.encode('ascii') except: key = 'non-ASCII key' try: val.encode('ascii') except: val = 'non-ASCII value' logging.warn('Ignoring unknown key pair "%s: %s" (%s)' % \ (key, val, info['login_name'])) _save_file_info(file_info, info['file_tag'], info) logging.debug('Successful metadata update (%s)' % info['login_name']) info['class'] = 'success' info['details'] += aux._fill_str(info['successful_update_blurb'], info) info['status_button_1'] = info['status_button_1'] % info return aux._fill_page(info['status_page_'], info)