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:
parent
179f7ea4d1
commit
7bdc228d09
|
@ -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:
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 ):
|
||||
|
|
|
@ -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 ):
|
||||
"""
|
||||
|
|
Reference in New Issue