witten
/
luminotes
Archived
1
0
Fork 0

A user can now revoke their own access. Protecting users from the own

stupidity proved too complex and fragile. So now they have the full power to
shoot themselves in the foot.

A user can no longer, however, redeem an invite that they send to themself.
This commit is contained in:
Dan Helfman 2008-01-06 21:59:22 +00:00
parent 179f7ea4d1
commit 7bdc228d09
4 changed files with 71 additions and 102 deletions

View File

@ -764,8 +764,8 @@ class Users( object ):
self.__database.save( similar, commit = False )
# if the invite is already redeemed, then update the relevant entry in the user_notebook
# access table as well. prevent the user from updating their own access
if similar.redeemed_user_id is not None and similar.redeemed_user_id != user_id:
# access table as well
if similar.redeemed_user_id is not None:
redeemed_user = self.__database.load( User, similar.redeemed_user_id )
if redeemed_user:
self.__database.execute( redeemed_user.sql_update_access( notebook_id, read_write, owner ) )
@ -838,7 +838,7 @@ class Users( object ):
raise Access_error()
self.__database.execute(
User.sql_revoke_invite_access( notebook_id, notebook.trash_id, invite.email_address, user_id ),
User.sql_revoke_invite_access( notebook_id, notebook.trash_id, invite.email_address ),
commit = False,
)
self.__database.execute( invite.sql_revoke_invites(), commit = False )
@ -927,6 +927,10 @@ class Users( object ):
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
@raise Invite_error: an error occured when redeeming the invite
"""
# prevent a user from redeeming their own invite
if invite.from_user_id == user_id:
return
user = self.__database.load( User, user_id )
notebook = self.__database.load( Notebook, invite.notebook_id )
if not user or not notebook:

View File

@ -122,11 +122,10 @@ class Test_controller( object ):
User.sql_update_access = lambda self, notebook_id, read_write = False, owner = False: \
lambda database: sql_update_access( self, notebook_id, read_write, owner, database )
def sql_revoke_invite_access( notebook_id, trash_id, email_address, excluded_user_id, database ):
def sql_revoke_invite_access( notebook_id, trash_id, email_address, database ):
invites = []
for ( user_id, notebook_infos ) in database.user_notebook.items():
if user_id == excluded_user_id: continue
for notebook_info in list( notebook_infos ):
( db_notebook_id, read_write, owner ) = notebook_info
if db_notebook_id not in ( notebook_id, trash_id ): continue
@ -136,8 +135,8 @@ class Test_controller( object ):
obj.email_address == email_address:
database.user_notebook[ user_id ].remove( notebook_info )
User.sql_revoke_invite_access = staticmethod( lambda notebook_id, trash_id, email_address, excluded_user_id: \
lambda database: sql_revoke_invite_access( notebook_id, trash_id, email_address, excluded_user_id, database ) )
User.sql_revoke_invite_access = staticmethod( lambda notebook_id, trash_id, email_address: \
lambda database: sql_revoke_invite_access( notebook_id, trash_id, email_address, database ) )
def sql_load_revisions( self, database ):
note_list = database.objects.get( self.object_id )

View File

@ -1266,86 +1266,6 @@ class Test_users( Test_controller ):
) )
assert access is True
def test_send_invites_similar_downgrade_self( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.login()
self.user.rate_plan = 1
self.database.save( self.user )
email_addresses_list = [ u"foo@example.com" ]
email_addresses = email_addresses_list[ 0 ]
# first send an invite with read_write and owner set to False
self.http_post( "/users/send_invites", dict(
notebook_id = self.notebooks[ 0 ].object_id,
email_addresses = email_addresses,
access = u"collaborator",
invite_button = u"send invites",
), session_id = self.session_id )
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
invite_id1 = matches.group( 2 )
assert invite_id1
# redeem the invite as the same user
result = self.http_post( "/users/redeem_invite", dict(
invite_id = invite_id1,
), session_id = self.session_id )
# then send a similar invite to the same email address with read_write and owner set to False
result = self.http_post( "/users/send_invites", dict(
notebook_id = self.notebooks[ 0 ].object_id,
email_addresses = email_addresses,
access = u"viewer",
invite_button = u"send invites",
), session_id = self.session_id )
# assert that both invites have the read_write / owner flags set to False now
invites = result[ u"invites" ]
assert len( invites ) == 1
invite = invites[ 0 ]
assert invite
assert invite.read_write is False
assert invite.owner is False
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
invite_id2 = matches.group( 2 )
assert invite_id2
# assert that both invites have the read_write / owner flags set to False now
invite1_list = self.database.objects.get( invite_id1 )
assert invite1_list
assert len( invite1_list ) >= 2
invite1 = invite1_list[ -1 ]
assert invite1
assert invite1.read_write is False
assert invite1.owner is False
invite2_list = self.database.objects.get( invite_id2 )
assert invite2_list
assert len( invite2_list ) >= 1
invite2 = invite2_list[ -1 ]
assert invite2
assert invite2.read_write is False
assert invite2.owner is False
# since the user is trying to downgrade their own access, assert that the downgrade was
# prevented and the user still retains their original access
access = self.database.select_one( bool, self.user.sql_has_access(
self.notebooks[ 0 ].object_id,
read_write = True,
owner = True,
) )
assert access is True
access = self.database.select_one( bool, self.user.sql_has_access(
self.notebooks[ 0 ].trash_id,
read_write = True,
owner = True,
) )
assert access is True
def test_send_invites_with_generic_from_address( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
@ -1773,7 +1693,7 @@ class Test_users( Test_controller ):
result = self.http_post( "/users/send_invites", dict(
notebook_id = self.notebooks[ 0 ].object_id,
email_addresses = email_addresses,
access = u"viewer",
access = u"owner",
invite_button = u"send invites",
), session_id = self.session_id )
@ -1781,13 +1701,15 @@ class Test_users( Test_controller ):
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
invite_id = matches.group( 2 )
self.login2()
result = self.http_post( "/users/redeem_invite", dict(
invite_id = invite_id,
), session_id = self.session_id )
assert cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id )
assert cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].trash_id )
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
# as user2, revoke that user's own invite
result = self.http_post( "/users/revoke_invite", dict(
notebook_id = self.notebooks[ 0 ].object_id,
invite_id = invite_id,
@ -1796,9 +1718,9 @@ class Test_users( Test_controller ):
assert result[ u"message" ]
assert len( result[ u"invites" ] ) == 0
# the user should've been prevented from revoking their own access
assert cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id )
assert cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].trash_id )
# the user should no longer have any access
assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
def test_revoke_invite_without_login( self ):
Stub_smtp.reset()
@ -2175,9 +2097,53 @@ class Test_users( Test_controller ):
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
invite_id = matches.group( 2 )
# convert the invite to access for a different user
invite = self.database.load( Invite, invite_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user2.object_id )
access = self.database.select_one( bool, self.user2.sql_has_access(
invite.notebook_id,
invite.read_write,
invite.owner,
) )
assert access is True
notebook = self.database.load( Notebook, invite.notebook_id )
access = self.database.select_one( bool, self.user2.sql_has_access(
notebook.trash_id,
invite.read_write,
invite.owner,
) )
assert access is True
assert invite.redeemed_user_id == self.user2.object_id
def test_convert_invite_to_access_same_user( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.login()
self.user.rate_plan = 1
self.database.save( self.user )
email_addresses_list = [ u"foo@example.com" ]
email_addresses = email_addresses_list[ 0 ]
self.http_post( "/users/send_invites", dict(
notebook_id = self.notebooks[ 0 ].object_id,
email_addresses = email_addresses,
access = u"viewer",
invite_button = u"send invites",
), session_id = self.session_id )
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
invite_id = matches.group( 2 )
# here, the same user that sent the invite is trying to convert it to access
invite = self.database.load( Invite, invite_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user.object_id )
# assert that the user retains the access they already had
access = self.database.select_one( bool, self.user.sql_has_access(
invite.notebook_id,
invite.read_write,
@ -2193,7 +2159,8 @@ class Test_users( Test_controller ):
) )
assert access is True
assert invite.redeemed_user_id == self.user.object_id
# assert that the invite was not actually redeemed
assert invite.redeemed_user_id == None
def test_convert_invite_to_access_twice( self ):
Stub_smtp.reset()
@ -2217,10 +2184,10 @@ class Test_users( Test_controller ):
invite_id = matches.group( 2 )
invite = self.database.load( Invite, invite_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user.object_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user.object_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user2.object_id )
cherrypy.root.users.convert_invite_to_access( invite, self.user2.object_id )
access = self.database.select_one( bool, self.user.sql_has_access(
access = self.database.select_one( bool, self.user2.sql_has_access(
invite.notebook_id,
invite.read_write,
invite.owner,
@ -2228,14 +2195,14 @@ class Test_users( Test_controller ):
assert access is True
notebook = self.database.load( Notebook, invite.notebook_id )
access = self.database.select_one( bool, self.user.sql_has_access(
access = self.database.select_one( bool, self.user2.sql_has_access(
notebook.trash_id,
invite.read_write,
invite.owner,
) )
assert access is True
assert invite.redeemed_user_id == self.user.object_id
assert invite.redeemed_user_id == self.user2.object_id
@raises( Invite_error )
def test_convert_invite_with_unknown_user( self ):

View File

@ -195,14 +195,13 @@ class User( Persistent ):
quote( notebook_id ) )
@staticmethod
def sql_revoke_invite_access( notebook_id, trash_id, email_address, excluded_user_id ):
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 != %s and
user_notebook.user_id in (
select
redeemed_user_id
@ -212,7 +211,7 @@ class User( Persistent ):
notebook_id = %s and
email_address = %s
);
""" % ( quote( notebook_id ), quote( trash_id ), quote( excluded_user_id ), quote( notebook_id ), quote( email_address ) )
""" % ( quote( notebook_id ), quote( trash_id ), quote( notebook_id ), quote( email_address ) )
def sql_calculate_storage( self ):
"""