""" open/dulcinea/lib/user.py """ from durus.utils import as_bytes from qp.lib.spec import add_getters_and_setters, specify from qp.lib.spec import spec, pattern, Specified from qp.lib.util import randbytes, sha1 from qp.pub.common import get_publisher, get_users from qp.pub.user import User import binascii def hash_password(password): """Apply a one way hash function to a password and return the result.""" return sha1(as_bytes(password)).hexdigest() def get_realm(): return get_publisher().get_site().get_name() class DulcineaUser (User, Specified): """ a registered user. """ global_permissions = { "act-as": "Allow to act as another user.", "create-users": "Allow the creation of other users.", "manage-permissions": "Allow changing of permissions.", "staff": "Is a member of the staff. user.is_admin() permission.", "system": "Allow to do things normally done by the software system.", } id_is = spec( pattern('^[-A-Za-z0-9_@. ]*$'), "unique among users here, spaces allowed") def __str__(self): return self.id format = __str__ # subclasses should override def get_key(self): """ used for forming component representing this user in URLs """ return self.get_id() def set_id(self, user_id): assert self.id == None specify(self, id=user_id) def set_password(self, new_password, realm=None, check=True): """Set the user's password to 'new_password'.""" if check and self.check_new_password(new_password) != "": raise ValueError('invalid password') User.set_password(self, new_password, realm=realm) def has_password(self, password, realm=None): """Return true if the provided password is correct.""" digester = self.get_digester() if realm is None: realm = get_realm() digest = digester.get_digest(realm) if User.has_password(self, password, realm=realm): return True elif digest == hash_password(password): # If the digest uses the older Dulcinea hash, set # a new digest using the QP hash. User.set_password(self, password, realm=realm) return True else: return False def generate_password(self, realm=None, length=6): """Set the password to a random value and return the new password.""" password = binascii.b2a_base64(binascii.unhexlify(randbytes(length))) password = password[:length] self.set_password(password, realm=realm) return password def check_new_password(self, new_password): """(string) -> string Check if a new password is valid. Returns the empty string if the password is okay otherwise returns a string that describes what is wrong with the entered password. """ return "" def format_realname(self): return '' def is_null(self): return not self def is_disabled(self, realm=None): return not bool(self.get_digester().get_digest(realm or get_realm())) def is_system(self): return self.id == 'SYSTEM' def is_admin(self): return self.is_granted('staff') def is_granted(self, permission, granter=True): return self.get_permissions().is_granted(permission, granter) def can_manage_permissions(self): return self.is_granted('manage-permissions') def get_email_realname_pair(self): return self.get_email(), self.format_realname() or '' add_getters_and_setters(DulcineaUser) def list_users(): return [user for user in get_users().itervalues() if not (user.is_null() or user.is_system() or user.is_disabled())] def gen_users_granted(permission, granter=True): for user in get_users().itervalues(): if user.is_granted(permission, granter): yield user def get_matching_user(identifier): """(identifier : string) -> DulcineaUser | None Return a user with matching id or email address, or None if no such user is found. """ return lookup_user(id=identifier, email=identifier) def add_user(user): return get_publisher().add_user(user) def lookup_user(id=None, email=None, **kwargs): return get_publisher().lookup_user(id=id, email=email, **kwargs) def text_format_user(user, email=True): if not user: return 'None' realname = user.format_realname() if realname: text = '%s (%s)' % (realname, user.get_id()) else: text = user.get_id() if email and user.get_email(): text += ' <%s>' % user.get_email() return text