Mercurial > hg > bitsyblog
view bitsyblog/user.py @ 112:d1114b31aaf1
py3
author | Jeff Hammel <k0scist@gmail.com> |
---|---|
date | Tue, 03 Nov 2020 14:05:41 -0800 |
parents | 67e01fc5e360 |
children | 7306d47d2667 |
line wrap: on
line source
""" bitsyblog users """ import os import random import settings import shutil from roles import roles from webob import exc class BitsyUser(object): """interface class for a bitsyblog user""" settings = {} def __init__(self, name, password=''): self.name = name self.password = password # user behaves like a dictionary of settings def __getitem__(self, key): return self.settings[key] def get(self, key, default=None): return self.settings.get(key, default) class BitsyUsers(object): """abstract class for bitsyblog user management""" def __iter__(self): return self.users() def __contains__(self, user): return user in self.users() def __getitem__(self, user): """return a user""" if user not in self.users(): raise KeyError("User '{}' not found".format(user)) user = BitsyUser(user, self.password(user)) user.settings = self.settings(user.name) return user def passwords(self): """returns a dictionary of { user: password }""" passwords = {} for user in self.users(): passwords[user] = self.password(user) return passwords ### interface methods to be specified by the child class def new(self, name, password): """create a new user""" def users(self): """returns the ids of all users (generator)""" def password(self, user): """return the password for the user""" def settings(self, user): """get user settings""" def write_settings(self, user, **kw): """set attributes on a user""" class FilespaceUsers(BitsyUsers): """users that live on the filesystem""" def __init__(self, directory): BitsyUsers.__init__(self) self.directory = directory # directory to store user information in def home(self, user, *path): return os.path.join(self.directory, user, *path) def pw_file(self, user): return self.home(user, '.password') def secret(self, user): secretfile = self.home(user, '.secret') if os.path.exists(secretfile): secret = int(file(secretfile).read().strip()) else: secret = random.randint(1024, 1024**4) secretfile = file(secretfile, 'w') print >> secretfile, secret return secret def preferences_file(self, user): return self.home(user, 'preferences.txt') def css(self, user, default): css_dir = self.home(user, 'css') css_files = [i for i in os.listdir(css_dir) if i.endswith('.css')] if default: default = '%s.css' % default try: index = css_files.index(default) css_files.insert(0, css_files.pop(index)) except ValueError: pass retval = [ dict(filename=i, name=i.rsplit('.css',1)[0], css=file(os.path.join(css_dir, i)).read()) for i in css_files ] return retval ### interfaces for BitsyUsers def new(self, name, password): """create a new user account""" # XXX this shouldn't use HTTP exceptions # instead, the web handler should listen for exceptions # and it should raise HTTP exceptions if name in self.users(): raise exc.HTTPForbidden("The name %s is already taken" % name).exception # characters forbidden in user name forbidden = ' |<>./?,' urls = [ 'join', 'login', 'logout', 'css', 'rss', 'atom', 'help' ] if [ i for i in forbidden if i in name ]: raise exc.HTTPForbidden("The name '%s' contains forbidden characters [%s]" % (user, forbidden)).exception if name in urls: raise exc.HTTPForbidden("The name '%s' is already used for a url" % user).exception # create user directory home = self.home(name) os.mkdir(home) pw_file = file(self.pw_file(name), 'w') print >> pw_file, password # setup entries structure for blog entries = os.path.join(home, 'entries') os.mkdir(entries) for setting in roles['author']: os.mkdir(os.path.join(entries, setting)) # setup user CSS css_dir = os.path.join(home, 'css') os.mkdir(css_dir) shutil.copyfile(os.path.join(self.directory, 'site.css'), os.path.join(css_dir, 'default.css')) def users(self): ignores = set(['.svn']) for user in os.listdir(self.directory): # ensure integrity of user folder if user in ignores: continue if os.path.isdir(os.path.join(self.directory, user)): yield user def password(self, user): pw_file = '.password' # name of the password file on the filesystem password = self.home(user, pw_file) if os.path.exists(password): return file(password).read().strip() return '' # unspecified password def settings(self, name): """returns a dictionary of user preferences from a file""" filename = self.home(name, 'preferences.txt') prefs = {} if os.path.exists(filename): prefs = file(filename).read().split('\n') prefs = [ i for i in prefs if i.strip() ] prefs = [ [ j.strip() for j in i.split(':', 1) ] for i in prefs if ':' in i] prefs = dict(prefs) # assemble friends from a list friends = prefs.get('Friends') # can see secret blog posts if friends: prefs['Friends'] = friends.split(', ') else: prefs['Friends'] = [] # CSS files prefs['CSS'] = self.css(name, prefs.get('Stylesheet')) return prefs def write_settings(self, name, **kw): """write user settings to disk""" # generic stuff; could factor out newsettings = {} errors = {} for setting in settings.form: if kw.has_key(setting.name): try: setting.set(kw[setting.name]) newsettings[setting.name] = setting.value except settings.InvalidSettingError, e: errors[setting.name] = str(e) if errors: return errors # this makes the function depend on implemention # i don't like this new_css = newsettings.pop('CSS file') if new_css: filename = new_css['filename'] css_file = file(self.home(name, 'css', filename), 'w') print >> css_file, new_css['css'] newsettings['CSS'] = filename.rsplit('.css', 1)[0] prefs = self.settings(name) prefs.update(newsettings) # write the preferences to a file preferences = file(self.preferences_file(name), 'w') for key, value in prefs.items(): print >> preferences, '%s: %s' % ( key, value )