diff --git a/controller/Users.py b/controller/Users.py index 5983821..6a29f72 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -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: diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 9e5ccd2..45079cc 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -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 ) diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index 69debc9..38dc020 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -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 ): diff --git a/model/User.py b/model/User.py index aa07d44..817bb3c 100644 --- a/model/User.py +++ b/model/User.py @@ -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 ): """