def _account_mod(login_name, ext, settings): """Modify a KBasix user account. _account_mod(login_name, ext, settings) The 'ext' can be 'profile' or 'prefs' depending if a user's profile information or preferences are being changed. The 'settings' is a dictionary. Nothing is returned. """ (OK, status) = _check_args(locals()) if not OK: raise AccountModError(status) uid = str(_info(login_name)['uid']) f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext) try: extfile = _read_file(f) except Exception as reason: raise AccountModError(reason) for key in settings: extfile[key] = settings[key] try: _save_file(extfile, f) except Exception as reason: raise AccountModError(reason) return
def _finger(login_name): """Interactively retrieve information about a user account. info = _finger(login_name) The return type may vary, should only be used interactively. """ (OK, status) = _check_args(locals()) if not OK: raise FingerError(status) import pprint user_info = _info(login_name) if not user_info: return 'User "%s" not found.' % login_name uid = str(user_info['uid']) f = os.path.join(users_root_dir_, uid, uid + '.%s' % 'profile') try: return pprint.pprint(_read_file(f, lock=False)) except Exception as reason: raise FingerError(reason)
def _get_file_info(file_tag, info): """Read the file metadata. file_info = _get_file_info(file_tag, info) Returns a dictionary with the file metadata (an empty one if either the metadata or content file isn't there). """ import logging import os import manage_users logging.debug('Getting file metadata for "%s" (%s)' % \ (file_tag, info['login_name'])) _check_file_tag(file_tag, info['login_name']) the_file = os.path.join(info['users_root_dir_'], str(info['uid']), \ file_tag) the_id_file = the_file + '-id' if not os.path.exists(the_id_file) or not os.path.exists(the_file): logging.warn('Unable to retrieve file metadata and/or content \ for "%s" (%s)' % (the_id_file, info['login_name'])) return {} return manage_users._read_file(the_id_file, lock=False)
def _account_info(login_name, ext): """Retrieve information about a user account. info = _account_info(login_name, ext) The 'ext' can be 'profile' or 'prefs' depending if a user's profile information or preferences are being accessed. Return is a dictionary (possibly an empty one). This function is suitable for function calls from other programs. """ (OK, status) = _check_args(locals()) if not OK: raise AccountInfoError(status) user_info = _info(login_name) if not user_info: return {} uid = str(user_info['uid']) f = os.path.join(users_root_dir_, uid, uid + '.%s' % ext) try: return _read_file(f, lock=False) except Exception as reason: raise AccountInfoError(reason)
def _copy_file(req, info): """Copy a file internally (within KBasix). _copy_file(req, info) Depending on the situation it may return an over-quota page, an access denied page or, if successful, the file manager page. """ import logging import os import hashlib import time import shutil import json import manage_users import upload (info['user_dir_size'], over_page) = upload._check_quota(req, 'copy', \ info) if info['user_dir_size'] < 0: return over_page user_dir = os.path.join(info['users_root_dir_'], str(info['uid'])) hash_val = hashlib.sha256(os.urandom(info['random_length'])).hexdigest() dst_file_tag = '%r-%s-file' % (time.time(), hash_val) src_file_tag = req.form['file_tag'].value _check_file_tag(src_file_tag, info['login_name']) file_info = _get_file_info(src_file_tag, info) denied_page = _check_permissions(file_info, info) if denied_page: return denied_page src_file = os.path.join(user_dir, src_file_tag) src_id_file = src_file + '-id' dst_file = os.path.join(user_dir, dst_file_tag) dst_id_file = dst_file + '-id' id_info = manage_users._read_file(src_id_file, lock=False) # We massage the appropriate metadata, removing all previous private # information. 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_tag'] = dst_file_tag id_info['custom'] = {} try: f = open(dst_id_file, 'wb') json.dump(id_info, f) f.close() os.chmod(dst_id_file, 0600) except Exception as reason: logging.critical('Unable to create id file "%s" because "%s" \ (%s)' % (dst_id_file, reason, info['login_name'])) raise CopyFileError('Unable to create id file') try: shutil.copy2(src_file, dst_file) except Exception as reason: logging.critical('Unable to copy file "%s" because "%s" (%s)' % \ (src_file, reason, info['login_name'])) raise CopyFileError('Unable to copy file') logging.debug('Copied file "%s" -> "%s" (%s)' % \ (src_file, dst_file, info['login_name'])) return _initialize(req, info)
def _get_file_list(info): """Get the file listing. file_list = _get_file_list(info) Returns a string. """ # This function is way too big, it needs to be sensibly split up. import logging import os import fnmatch import time import aux import manage_users import manage_kbasix login_name = info['login_name'] logging.debug('Creating a file list (%s)' % login_name) prefs = manage_kbasix._account_info(login_name, 'prefs') user_dir = os.path.join(info['users_root_dir_'], str(info['uid'])) entries = {} count = {} gids = manage_users._info(login_name)['gids'] # Check for local- and GID-shared files. for subdir in ['local'] + [str(i) for i in gids]: subpath = os.path.join(info['shared_dir_'], subdir) if os.path.exists(subpath): # Some house cleaning: delete broken symlinks. for i in os.listdir(subpath): f = os.path.join(subpath, i) # We are lenient with file removals in shared directories # because another user might have beat us to it. if not os.path.exists(f): try: os.remove(f) logging.info('Deleted broken shared symlink "%s" \ (%s)' % (f, login_name)) except Exception as reason: logging.warn(reason) for i in fnmatch.filter(os.listdir(subpath), '*-file'): rel_path = os.path.relpath(subpath, user_dir) src = os.path.join(rel_path, i) dst = os.path.join(user_dir, i) # Shared entries which are deleted by the user (the # sharee) are actually hidden from view by adding them # to the 'hidden_gidloc_shared_files' list when the # sharee deletes them. # Once hidden, local/gid-shared files cannot be recovered # unless explicitly re-shared with that user via uid_shares. # Note that if the file exists (important if it's a uid # point share, which takes precedence) we leave it alone. if not os.path.exists(dst) and \ i not in \ prefs['file_manager']['hidden_gidloc_shared_files']: try: # The target may not exist, but if the link does, # delete it. if os.path.islink(dst + '-id'): os.remove(dst + '-id') os.symlink(src + '-id', dst + '-id') except: logging.error('Cannot link shared id file "%s" \ (%s)' % (src + '-id', login_name)) else: if os.path.islink(dst): os.remove(dst) os.symlink(src, dst) logging.debug('Added shared file "%s" -> "%s" \ (%s)' % (src, dst, login_name)) # Create the listing of the files/symlinks in the user's directory. for i in os.listdir(user_dir): f = os.path.join(user_dir, i) if not os.path.exists(f): try: os.remove(f) logging.info('Deleted broken user symlink "%s" (%s)' % \ (f, login_name)) except Exception as reason: logging.error(reason) for i in fnmatch.filter(os.listdir(user_dir), '*-id'): f = os.path.join(user_dir, i) # This should not normally happen. The magic [:-3] deletes '-id' # and leaves the content file name. if not os.path.exists(f[:-3]): try: os.remove(f) logging.error('Deleted orphan id file "%s" (%s)' % \ (f, login_name)) except Exception as reason: logging.error(reason) continue details = manage_users._read_file(f, lock=False) # A symlink implies the file is being shared with the user. if os.path.islink(f): details['shared_with_me'] = True else: details['shared_with_me'] = False for j in ['file_title', 'file_description']: if not details[j]: details[j] = info['empty_placeholder_'] details['file_date'] = \ time.strftime(info['file_manager_time_format_'], \ time.localtime(details['timestamp'])) # We need the token for the buttons. details['token'] = info['token'] sort_key = details[prefs['file_manager']['sort_criteria']] # We pad with zeros to obtain a natural sorting for entries with # the same non-string keys e.g. size or timestamp. Padding to 50 # seems safe, although this is admittedly a magic number # (timestamps have less than 20 digits, and files size are smaller # than that). Worst case scenario is that the sorting on huge # numeric sort keys (larger than 50 digits) will be wrong, but this # is unlikely to happen and has no other consequences. if not isinstance(sort_key, basestring): sort_key = '%r' % sort_key sort_key = sort_key.zfill(50) # We internally distinguish between identical entry values # (say, identical file names), but although lumped together # in their proper place within the overall list they are # not sub-sorted in any deliberate way. if sort_key not in entries: count[sort_key] = 1 entries[sort_key] = details else: count[sort_key] += 1 entries[sort_key + ' (%s)' % count[sort_key]] = details if prefs['file_manager']['condensed_view']: info['template'] = 'condensed_entry_template_' else: info['template'] = 'entry_template_' file_list = '' for key in sorted(entries, \ reverse=bool(prefs['file_manager']['reverse'])): entries[key]['shared_status'] = '' entries[key]['file_colour'] = info['my_file_colour_'] if entries[key]['shared_with_me']: # World shares are not symlinked with the individual accounts, # and entries which cannot be read any longer are deleted. if not entries[key]['local_share'] and \ not info['uid'] in entries[key]['uid_shares'] and not \ set(gids).intersection(set(entries[key]['gid_shares'])): shared_file = \ os.path.join(user_dir, entries[key]['file_tag']) try: os.remove(shared_file) os.remove(shared_file + '-id') except Exception as reason: logging.error(reason) continue # Don't show the shares made by no-longer-exsting users if # 'exusers_cannot_share' is True. elif not manage_users._info(entries[key]['owner_uid']) and \ info['exusers_cannot_share']: continue else: entries[key]['shared_status'] = """ <img src="%s" title="File shared with me by: %s" alt="[File shared with me by: %s]" /> """ % (info['shared_icon_with_me_'], entries[key]['owner'], \ entries[key]['owner']) entries[key]['file_colour'] = info['other_file_colour_'] if prefs['file_manager']['hide_shared'] and \ entries[key]['shared_with_me']: continue # Shared files can be copied internally (it's more efficient than # downloading and uploading again). entries[key]['copy_file'] = '' if entries[key]['shared_with_me']: entries[key]['copy_file_icon_'] = info['copy_file_icon_'] entries[key]['copy_file_form_style_'] = \ info['copy_file_form_style_'] entries[key]['copy_file'] = """ <form %(copy_file_form_style_)s action="../file_manager.py/process?action=copy_file" method="post"> <input type="hidden" name="token" value="%(token)s" /> <input type="hidden" name="file_tag" value="%(file_tag)s" /> <input title="Make a local copy" type="image" alt="Make a local copy" src="%(copy_file_icon_)s" /> </form> """ % entries[key] # Files that are shared (the user being the sharer) are tagged # in various ways to indicate this. else: if entries[key]['world_share']: entries[key]['shared_status'] += """ <img src="%s" title="File is shared with the world" alt="[File is shared with the world]" /> """ % info['shared_icon_by_me_to_world_'] if entries[key]['local_share']: entries[key]['shared_status'] += """ <img src="%s" title="File is shared with registered users" alt="[File is shared with registered users]" /> """ % info['shared_icon_by_me_locally_'] if entries[key]['gid_shares']: entries[key]['shared_status'] += """ <img src="%s" title="File is shared with selected groups" alt="[File is shared with selected groups]" /> """ % info['shared_icon_by_me_to_groups_'] if entries[key]['uid_shares']: entries[key]['shared_status'] += """ <img src="%s" title="File is shared with selected users" alt="[File is shared with selected users]" /> """ % info['shared_icon_by_me_selectively_'] # Convert the size to a string with the appropriate unit suffix e.g. # 'MB'. entries[key]['file_size_str'] = \ aux._bytes_string(entries[key]['file_size']) entries[key]['toggle_select'] = info['toggle_select'] # Limit the length of titles and descriptions. for i in ['description', 'title']: if info['max_chars_in_' + i]: max_char = len(entries[key]['file_' + i]) char_num = min(max_char, info['max_chars_in_' + i]) entries[key][i + '_blurb'] = \ entries[key]['file_' + i][:char_num] if max_char > char_num: entries[key][i + '_blurb'] += '...' else: entries[key][i + '_blurb'] = entries[key]['file_' + i] # The keyword filter. This is actually a very important # functionality, and should be split into its own function. # Furthermore, it is currently very weak, and should be drastically # improved. Right now is just filters on words in the file title # and description, ignoring punctuation. It is also # case-insensitive. if prefs['file_manager']['keywords']: import re # Use '[\W_]+' to eliminate underscores. See also: # # http://stackoverflow.com/questions/6631870/strip-non-alpha-numeric-characters-from-string-in-python-but-keeping-special-cha # # The 're.U' works on unicode strings. nonalnum = re.compile('[\W]+', re.U) keywords = set([i.lower() for i in \ prefs['file_manager']['keywords'].split()]) words = [nonalnum.sub('', i.lower()) for i in \ entries[key]['file_title'].split()] words += [nonalnum.sub('', i.lower()) for i in \ entries[key]['file_description'].split()] if keywords <= set(words): file_list += aux._fill_str(info[info['template']], \ entries[key]) continue file_list += aux._fill_str(info[info['template']], entries[key]) if not file_list: return info['no_files_found_'] else: return file_list