393 lines
14 KiB
Python
393 lines
14 KiB
Python
import sha
|
|
import random
|
|
from copy import copy
|
|
from Persistent import Persistent, quote
|
|
|
|
|
|
class User( Persistent ):
|
|
"""
|
|
A Luminotes user.
|
|
"""
|
|
SALT_CHARS = [ chr( c ) for c in range( ord( "!" ), ord( "~" ) + 1 ) ]
|
|
SALT_SIZE = 12
|
|
|
|
def __init__( self, object_id, revision = None, username = None, salt = None, password_hash = None,
|
|
email_address = None, storage_bytes = None, rate_plan = None ):
|
|
"""
|
|
Create a new user with the given credentials and information.
|
|
|
|
@type object_id: unicode
|
|
@param object_id: id of the user
|
|
@type revision: datetime or NoneType
|
|
@param revision: revision timestamp of the object (optional, defaults to now)
|
|
@type username: unicode or NoneType
|
|
@param username: unique user identifier for login purposes (optional)
|
|
@type salt: unicode or NoneType
|
|
@param salt: salt to use when hashing the password (optional, defaults to random)
|
|
@type password_hash: unicode or NoneType
|
|
@param password_hash: cryptographic hash of secret password for login purposes (optional)
|
|
@type email_address: unicode or NoneType
|
|
@param email_address: a hopefully valid email address (optional)
|
|
@type storage_bytes: int or NoneType
|
|
@param storage_bytes: count of bytes that the user is currently using for storage (optional)
|
|
@type rate_plan: int or NoneType
|
|
@param rate_plan: index into the rate plan array in config/Common.py (optional, defaults to 0)
|
|
@rtype: User
|
|
@return: newly created user
|
|
"""
|
|
Persistent.__init__( self, object_id, revision )
|
|
self.__username = username
|
|
self.__salt = salt
|
|
self.__password_hash = password_hash
|
|
self.__email_address = email_address
|
|
self.__storage_bytes = storage_bytes or 0
|
|
self.__group_storage_bytes = 0
|
|
self.__rate_plan = rate_plan or 0
|
|
|
|
@staticmethod
|
|
def create( object_id, username = None, password = None, email_address = None, rate_plan = None ):
|
|
"""
|
|
Convenience constructor for creating a new user.
|
|
|
|
@type object_id: unicode
|
|
@param object_id: id of the user
|
|
@type username: unicode or NoneType
|
|
@param username: unique user identifier for login purposes (optional)
|
|
@type password: unicode or NoneType
|
|
@param password: secret password for login purposes (optional)
|
|
@type email_address: unicode or NoneType
|
|
@param email_address: a hopefully valid email address (optional)
|
|
@type rate_plan: int or NoneType
|
|
@param rate_plan: index into the rate plan array in config/Common.py (optional, defaults to 0)
|
|
@rtype: User
|
|
@return: newly created user
|
|
"""
|
|
salt = User.__create_salt()
|
|
password_hash = User.__hash_password( salt, password )
|
|
|
|
return User( object_id, None, username, salt, password_hash, email_address, rate_plan = rate_plan )
|
|
|
|
@staticmethod
|
|
def __create_salt():
|
|
return "".join( [ random.choice( User.SALT_CHARS ) for i in range( User.SALT_SIZE ) ] )
|
|
|
|
@staticmethod
|
|
def __hash_password( salt, password ):
|
|
if password is None or len( password ) == 0:
|
|
return None
|
|
|
|
return sha.new( ( salt + password ).encode( "utf8" ) ).hexdigest()
|
|
|
|
def check_password( self, password ):
|
|
"""
|
|
Check that the given password matches this user's password.
|
|
|
|
@type password: unicode
|
|
@param password: password to check
|
|
@rtype: bool
|
|
@return: True if the password matches
|
|
"""
|
|
if self.__password_hash == None:
|
|
return False
|
|
|
|
hash = User.__hash_password( self.__salt, password )
|
|
if hash == self.__password_hash:
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def sql_load( object_id, revision = None ):
|
|
if revision:
|
|
return "select * from luminotes_user where id = %s and revision = %s;" % ( quote( object_id ), quote( revision ) )
|
|
|
|
return "select * from luminotes_user_current where id = %s;" % quote( object_id )
|
|
|
|
@staticmethod
|
|
def sql_id_exists( object_id, revision = None ):
|
|
if revision:
|
|
return "select id from luminotes_user where id = %s and revision = %s;" % ( quote( object_id ), quote( revision ) )
|
|
|
|
return "select id from luminotes_user_current where id = %s;" % quote( object_id )
|
|
|
|
def sql_exists( self ):
|
|
return User.sql_id_exists( self.object_id, self.revision )
|
|
|
|
def sql_create( self ):
|
|
return \
|
|
"insert into luminotes_user ( id, revision, username, salt, password_hash, email_address, storage_bytes, rate_plan ) " + \
|
|
"values ( %s, %s, %s, %s, %s, %s, %s, %s );" % \
|
|
( quote( self.object_id ), quote( self.revision ), quote( self.__username ),
|
|
quote( self.__salt ), quote( self.__password_hash ), quote( self.__email_address ),
|
|
self.__storage_bytes, self.__rate_plan )
|
|
|
|
def sql_update( self ):
|
|
return self.sql_create()
|
|
|
|
@staticmethod
|
|
def sql_load_by_username( username ):
|
|
return "select * from luminotes_user_current where username = %s;" % quote( username )
|
|
|
|
@staticmethod
|
|
def sql_load_by_email_address( email_address ):
|
|
return "select * from luminotes_user_current where email_address = %s;" % quote( email_address )
|
|
|
|
def sql_load_notebooks( self, parents_only = False, undeleted_only = False, read_write = False ):
|
|
"""
|
|
Return a SQL string to load a list of the notebooks to which this user has access.
|
|
"""
|
|
if parents_only:
|
|
parents_only_clause = " and trash_id is not null"
|
|
else:
|
|
parents_only_clause = ""
|
|
|
|
if undeleted_only:
|
|
undeleted_only_clause = " and deleted = 'f'"
|
|
else:
|
|
undeleted_only_clause = ""
|
|
|
|
if read_write:
|
|
read_write_clause = " and user_notebook.read_write = 't'"
|
|
else:
|
|
read_write_clause = ""
|
|
|
|
return \
|
|
"""
|
|
select
|
|
notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank
|
|
from
|
|
user_notebook, notebook_current
|
|
where
|
|
user_notebook.user_id = %s%s%s%s and
|
|
user_notebook.notebook_id = notebook_current.id
|
|
order by user_notebook.rank;
|
|
""" % ( quote( self.object_id ), parents_only_clause, undeleted_only_clause, read_write_clause )
|
|
|
|
def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None ):
|
|
"""
|
|
Return a SQL string to save the id of a notebook to which this user has access.
|
|
"""
|
|
if rank is None: rank = quote( None )
|
|
|
|
return \
|
|
"insert into user_notebook ( user_id, notebook_id, read_write, owner, rank ) values " + \
|
|
"( %s, %s, %s, %s, %s );" % ( quote( self.object_id ), quote( notebook_id ), quote( read_write and 't' or 'f' ),
|
|
quote( owner and 't' or 'f' ), rank )
|
|
|
|
def sql_remove_notebook( self, notebook_id ):
|
|
"""
|
|
Return a SQL string to remove this user's access to a particular notebook.
|
|
"""
|
|
return \
|
|
"delete from user_notebook where user_id = %s and notebook_id = %s;" % ( quote( self.object_id ), quote( notebook_id ) )
|
|
|
|
def sql_has_access( self, notebook_id, read_write = False, owner = False ):
|
|
"""
|
|
Return a SQL string to determine whether this user has access to the given notebook.
|
|
"""
|
|
if read_write is True and owner is True:
|
|
return \
|
|
"select user_id from user_notebook where user_id = %s and notebook_id = %s and read_write = 't' and owner = 't';" % \
|
|
( quote( self.object_id ), quote( notebook_id ) )
|
|
elif read_write is True:
|
|
return \
|
|
"select user_id from user_notebook where user_id = %s and notebook_id = %s and read_write = 't';" % \
|
|
( quote( self.object_id ), quote( notebook_id ) )
|
|
elif owner is True:
|
|
return \
|
|
"select user_id from user_notebook where user_id = %s and notebook_id = %s and owner = 't';" % \
|
|
( quote( self.object_id ), quote( notebook_id ) )
|
|
else:
|
|
return \
|
|
"select user_id from user_notebook where user_id = %s and notebook_id = %s;" % \
|
|
( quote( self.object_id ), quote( notebook_id ) )
|
|
|
|
def sql_update_access( self, notebook_id, read_write = False, owner = False ):
|
|
"""
|
|
Return a SQL string to update the user's notebook access to the given read_write and owner level.
|
|
"""
|
|
return \
|
|
"update user_notebook set read_write = %s, owner = %s where user_id = %s and notebook_id = %s;" % \
|
|
( quote( read_write and 't' or 'f' ), quote( owner and 't' or 'f' ), quote( self.object_id ),
|
|
quote( notebook_id ) )
|
|
|
|
def sql_update_notebook_rank( self, notebook_id, rank ):
|
|
"""
|
|
Return a SQL string to update the user's rank for the given notebook.
|
|
"""
|
|
return \
|
|
"update user_notebook set rank = %s where user_id = %s and notebook_id = %s;" % \
|
|
( quote( rank ), quote( self.object_id ), quote( notebook_id ) )
|
|
|
|
def sql_highest_notebook_rank( self ):
|
|
"""
|
|
Return a SQL string to determine the highest numbered rank of all notebooks the user has access to."
|
|
"""
|
|
return "select coalesce( max( rank ), -1 ) from user_notebook where user_id = %s;" % quote( self.object_id )
|
|
|
|
def sql_load_groups( self ):
|
|
"""
|
|
Return a SQL string to load a list of the groups to which this user has membership.
|
|
"""
|
|
return \
|
|
"""
|
|
select
|
|
luminotes_group_current.*, user_group.admin
|
|
from
|
|
user_group, luminotes_group_current
|
|
where
|
|
user_group.user_id = %s and
|
|
user_group.group_id = luminotes_group_current.id
|
|
order by luminotes_group_current.name;
|
|
""" % quote( self.object_id )
|
|
|
|
def sql_save_group( self, group_id, admin = False ):
|
|
"""
|
|
Return a SQL string to save the id of a group to which this user has membership.
|
|
"""
|
|
return \
|
|
"insert into user_group ( user_id, group_id, admin ) values " + \
|
|
"( %s, %s, %s );" % ( quote( self.object_id ), quote( group_id ), quote( admin and 't' or 'f' ) )
|
|
|
|
def sql_remove_group( self, group_id ):
|
|
"""
|
|
Return a SQL string to remove this user's membership to a particular group.
|
|
"""
|
|
return \
|
|
"delete from user_group where user_id = %s and group_id = %s;" % ( quote( self.object_id ), quote( group_id ) )
|
|
|
|
def sql_in_group( self, group_id, admin = False ):
|
|
"""
|
|
Return a SQL string to determine whether this has membership to the given group.
|
|
"""
|
|
if admin is True:
|
|
return \
|
|
"select user_id from user_group where user_id = %s and group_id = %s and admin = 't';" % \
|
|
( quote( self.object_id ), quote( group_id ) )
|
|
else:
|
|
return \
|
|
"select user_id from user_group where user_id = %s and group_id = %s;" % \
|
|
( quote( self.object_id ), quote( group_id ) )
|
|
|
|
def sql_update_group_admin( self, group_id, admin = False ):
|
|
"""
|
|
Return a SQL string to update the user's group membership to have the given admin flag.
|
|
"""
|
|
return \
|
|
"update user_group set admin = %s where user_id = %s and group_id = %s;" % \
|
|
( quote( admin and 't' or 'f' ), quote( self.object_id ), quote( group_id ) )
|
|
|
|
@staticmethod
|
|
def sql_revoke_invite_access( notebook_id, trash_id, email_address ):
|
|
return \
|
|
"""
|
|
delete from
|
|
user_notebook
|
|
where
|
|
notebook_id in ( %s, %s ) and
|
|
user_notebook.user_id in (
|
|
select
|
|
redeemed_user_id
|
|
from
|
|
invite
|
|
where
|
|
notebook_id = %s and
|
|
email_address = %s
|
|
);
|
|
""" % ( quote( notebook_id ), quote( trash_id ), quote( notebook_id ), quote( email_address ) )
|
|
|
|
def sql_calculate_storage( self ):
|
|
"""
|
|
Return a SQL string to calculate the total bytes of storage usage by this user. This includes
|
|
storage for all the user's notes (including past revisions) and their uploaded files. It does
|
|
not include storage for the notebooks themselves.
|
|
"""
|
|
return \
|
|
"""
|
|
select * from (
|
|
select
|
|
coalesce( sum( pg_column_size( note.* ) ), 0 )
|
|
from
|
|
user_notebook, note
|
|
where
|
|
user_notebook.user_id = %s and
|
|
user_notebook.owner = 't' and
|
|
note.notebook_id = user_notebook.notebook_id
|
|
) as note_storage,
|
|
(
|
|
select
|
|
coalesce( sum( file.size_bytes ), 0 )
|
|
from
|
|
user_notebook, file
|
|
where
|
|
user_notebook.user_id = %s and
|
|
user_notebook.owner = 't' and
|
|
file.notebook_id = user_notebook.notebook_id
|
|
) as file_storage;
|
|
""" % ( quote( self.object_id ), quote( self.object_id ) )
|
|
|
|
def sql_calculate_group_storage( self ):
|
|
"""
|
|
Return a SQL string to calculate the total bytes of storage usage for all groups that this user
|
|
is a member of. This includes the cumulative storage of all users in these groups.
|
|
"""
|
|
return \
|
|
"""
|
|
select coalesce( sum( storage_bytes ), 0 ) from (
|
|
select
|
|
distinct user_id, storage_bytes
|
|
from
|
|
user_group, luminotes_user_current
|
|
where
|
|
group_id in (
|
|
select
|
|
group_id
|
|
from
|
|
user_group
|
|
where
|
|
user_id = %s
|
|
) and
|
|
user_id = luminotes_user_current.id
|
|
) as sub;
|
|
""" % quote( self.object_id )
|
|
|
|
def to_dict( self ):
|
|
d = Persistent.to_dict( self )
|
|
d.update( dict(
|
|
username = self.username,
|
|
storage_bytes = self.__storage_bytes,
|
|
group_storage_bytes = self.__group_storage_bytes,
|
|
rate_plan = self.__rate_plan,
|
|
) )
|
|
|
|
return d
|
|
|
|
def __set_email_address( self, email_address ):
|
|
self.update_revision()
|
|
self.__email_address = email_address
|
|
|
|
def __set_password( self, password ):
|
|
self.update_revision()
|
|
self.__salt = User.__create_salt()
|
|
self.__password_hash = User.__hash_password( self.__salt, password )
|
|
|
|
def __set_storage_bytes( self, storage_bytes ):
|
|
self.update_revision()
|
|
self.__storage_bytes = storage_bytes
|
|
|
|
def __set_group_storage_bytes( self, group_storage_bytes ):
|
|
# The group_storage_bytes member isn't actually saved to the database, so setting it doesn't
|
|
# need to call update_revision().
|
|
self.__group_storage_bytes = group_storage_bytes
|
|
|
|
def __set_rate_plan( self, rate_plan ):
|
|
self.update_revision()
|
|
self.__rate_plan = rate_plan
|
|
|
|
username = property( lambda self: self.__username )
|
|
email_address = property( lambda self: self.__email_address, __set_email_address )
|
|
password = property( None, __set_password )
|
|
storage_bytes = property( lambda self: self.__group_storage_bytes or self.__storage_bytes, __set_storage_bytes )
|
|
group_storage_bytes = property( lambda self: self.__group_storage_bytes, __set_group_storage_bytes )
|
|
rate_plan = property( lambda self: self.__rate_plan, __set_rate_plan )
|