import re import cherrypy import smtplib from pytz import utc from nose.tools import raises from datetime import datetime, timedelta from Test_controller import Test_controller from Stub_smtp import Stub_smtp from model.User import User from model.Notebook import Notebook from model.Note import Note from model.Password_reset import Password_reset from model.Invite import Invite from controller.Users import Invite_error class Test_users( Test_controller ): RESET_LINK_PATTERN = re.compile( "(https?://\S+)?/r/(\S+)" ) INVITE_LINK_PATTERN = re.compile( "(https?://\S+)?/i/(\S+)" ) def setUp( self ): Test_controller.setUp( self ) self.username = u"mulder" self.password = u"trustno1" self.email_address = u"out-there@example.com" self.new_username = u"reynolds" self.new_password = u"shiny" self.new_email_address = u"capn@example.com" self.username2 = u"scully" self.password2 = u"trustsome1" self.email_address2 = u"out-there@example.com" self.user = None self.user2 = None self.anonymous = None self.notebooks = None self.session_id = None self.make_users() def make_users( self ): notebook_id1 = self.database.next_id( Notebook ) notebook_id2 = self.database.next_id( Notebook ) trash_id1 = self.database.next_id( Notebook ) trash_id2 = self.database.next_id( Notebook ) self.database.save( Notebook.create( trash_id1, u"trash" ) ) self.database.save( Notebook.create( trash_id2, u"trash" ) ) self.notebooks = [ Notebook.create( notebook_id1, u"my notebook", trash_id = trash_id1 ), Notebook.create( notebook_id2, u"my other notebook", trash_id = trash_id2 ), ] self.database.save( self.notebooks[ 0 ] ) self.database.save( self.notebooks[ 1 ] ) self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon notebook" ) self.database.save( self.anon_notebook ) self.startup_note = Note.create( self.database.next_id( Note ), u"

login

", notebook_id = self.anon_notebook.object_id, startup = True, ) self.database.save( self.startup_note ) self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address ) self.database.save( self.user, commit = False ) self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True, owner = True ), commit = False ) self.database.execute( self.user.sql_save_notebook( trash_id1, read_write = True, owner = True ), commit = False ) self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True, owner = True ), commit = False ) self.database.execute( self.user.sql_save_notebook( trash_id2, read_write = True, owner = True ), commit = False ) self.user2 = User.create( self.database.next_id( User ), self.username2, self.password2, self.email_address2 ) self.database.save( self.user2, commit = False ) self.anonymous = User.create( self.database.next_id( User ), u"anonymous" ) self.database.save( self.anonymous, commit = False ) self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id ), commit = False ) self.database.commit() def test_signup( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password, email_address = self.new_email_address, signup_button = u"sign up", ) ) assert result[ u"redirect" ].startswith( u"/notebooks/" ) def test_signup_without_email_address( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password, email_address = u"", signup_button = u"sign up", ) ) assert result[ u"redirect" ].startswith( u"/notebooks/" ) def test_signup_with_invalid_email_address( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password, email_address = u"foo@", signup_button = u"sign up", ) ) assert u"error" in result def test_current_after_signup( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password, email_address = self.new_email_address, signup_button = u"sign up", ) ) session_id = result[ u"session_id" ] new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] user = self.database.last_saved_obj assert isinstance( user, User ) result = cherrypy.root.users.current( user.object_id ) assert result[ u"user" ].object_id == user.object_id assert result[ u"user" ].username == self.new_username assert result[ u"user" ].email_address == self.new_email_address notebooks = result[ u"notebooks" ] notebook = notebooks[ 0 ] assert notebook.object_id == new_notebook_id assert notebook.revision assert notebook.name == u"my notebook" assert notebook.trash_id assert notebook.read_write == True assert notebook.owner == True notebook = notebooks[ 1 ] assert notebook.object_id == notebooks[ 0 ].trash_id assert notebook.revision assert notebook.name == u"trash" assert notebook.trash_id == None assert notebook.read_write == True assert notebook.owner == True notebook = notebooks[ 2 ] assert notebook.object_id == self.anon_notebook.object_id assert notebook.revision == self.anon_notebook.revision assert notebook.name == self.anon_notebook.name assert notebook.trash_id == None assert notebook.read_write == False assert notebook.owner == False assert result.get( u"login_url" ) is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_current_after_signup_with_invite_id( self ): # trick send_invites() into using a fake SMTP server 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 ] 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 ) matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password, email_address = self.new_email_address, signup_button = u"sign up", invite_id = invite_id, ) ) invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] assert invite_notebook_id == self.notebooks[ 0 ].object_id user = self.database.last_saved_user assert isinstance( user, User ) result = cherrypy.root.users.current( user.object_id ) assert result[ u"user" ].object_id == user.object_id assert result[ u"user" ].username == self.new_username assert result[ u"user" ].email_address == self.new_email_address assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].object_id ) assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].trash_id ) # the notebook that the user was invited to should be in the list of returned notebooks notebooks = dict( [ ( notebook.object_id, notebook ) for notebook in result[ u"notebooks" ] ] ) notebook = notebooks.get( invite_notebook_id ) assert notebook assert notebook.revision assert notebook.name == self.notebooks[ 0 ].name assert notebook.trash_id assert notebook.read_write == False assert notebook.owner == False notebook = notebooks.get( self.notebooks[ 0 ].trash_id ) assert notebook.revision assert notebook.name == u"trash" assert notebook.trash_id == None assert notebook.read_write == False assert notebook.owner == False notebook = notebooks.get( self.anon_notebook.object_id ) assert notebook.revision == self.anon_notebook.revision assert notebook.name == self.anon_notebook.name assert notebook.trash_id == None assert notebook.read_write == False assert notebook.owner == False assert result.get( u"login_url" ) is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_signup_with_different_passwords( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, password_repeat = self.new_password + u"nomatch", email_address = self.new_email_address, signup_button = u"sign up", ) ) assert result[ u"error" ] def test_demo( self ): result = self.http_post( "/users/demo", dict() ) assert result[ u"redirect" ].startswith( u"/notebooks/" ) def test_current_after_demo( self ): result = self.http_post( "/users/demo", dict() ) session_id = result[ u"session_id" ] new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] user = self.database.last_saved_obj assert isinstance( user, User ) result = cherrypy.root.users.current( user.object_id ) assert result[ u"user" ].object_id == user.object_id assert result[ u"user" ].username is None assert result[ u"user" ].email_address is None notebooks = result[ u"notebooks" ] assert len( notebooks ) == 3 notebook = notebooks[ 0 ] assert notebook.object_id == new_notebook_id assert notebook.revision assert notebook.name == u"my notebook" assert notebook.trash_id assert notebook.read_write == True assert notebook.owner == True notebook = notebooks[ 1 ] assert notebook.object_id == notebooks[ 0 ].trash_id assert notebook.revision assert notebook.name == u"trash" assert notebook.trash_id == None assert notebook.read_write == True assert notebook.owner == True notebook = notebooks[ 2 ] assert notebook.object_id == self.anon_notebook.object_id assert notebook.revision == self.anon_notebook.revision assert notebook.name == self.anon_notebook.name assert notebook.trash_id == None assert notebook.read_write == False assert notebook.owner == False assert result.get( u"login_url" ) is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_current_after_demo_twice( self ): result = self.http_post( "/users/demo", dict() ) session_id = result[ u"session_id" ] new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] user = self.database.last_saved_obj assert isinstance( user, User ) result = cherrypy.root.users.current( user.object_id ) user_id = result[ u"user" ].object_id assert user_id == user.object_id # request a demo for a second time result = self.http_post( "/users/demo", dict(), session_id = session_id ) assert result[ u"redirect" ].startswith( u"/notebooks/" ) notebook_id_again = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] assert notebook_id_again == new_notebook_id result = cherrypy.root.users.current( user_id ) user_id_again = result[ u"user" ].object_id # since we're already logged in as a guest user with a demo notebook, requesting a demo again # should just use the same guest user with the same notebook assert user_id_again == user_id def test_login( self ): result = self.http_post( "/users/login", dict( username = self.username, password = self.password, login_button = u"login", ) ) assert result[ u"redirect" ] == u"/notebooks/%s" % self.notebooks[ 0 ].object_id def test_login_with_unknown_user( self ): result = self.http_post( "/users/login", dict( username = u"nosuchuser", password = self.password, login_button = u"login", ) ) assert result[ u"error" ] def test_login_with_invalid_password( self ): result = self.http_post( "/users/login", dict( username = self.username, password = u"wrongpass", login_button = u"login", ) ) assert result[ u"error" ] def test_logout( self ): result = self.http_post( "/users/logout", dict() ) assert result[ u"redirect" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) + u"/" def test_current( self ): result = cherrypy.root.users.current( self.user.object_id ) assert result[ u"user" ] assert result[ u"user" ].object_id == self.user.object_id assert result[ u"user" ].username == self.user.username assert len( result[ u"notebooks" ] ) == 5 assert result[ u"notebooks" ][ 0 ].object_id == self.notebooks[ 0 ].object_id assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name assert result[ u"notebooks" ][ 0 ].read_write == True assert result[ u"notebooks" ][ 0 ].owner == True assert result[ u"notebooks" ][ 1 ].object_id assert result[ u"notebooks" ][ 1 ].name == u"trash" assert result[ u"notebooks" ][ 1 ].read_write == True assert result[ u"notebooks" ][ 1 ].owner == True assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 1 ].object_id assert result[ u"notebooks" ][ 2 ].name == self.notebooks[ 1 ].name assert result[ u"notebooks" ][ 2 ].read_write == True assert result[ u"notebooks" ][ 2 ].owner == True assert result[ u"notebooks" ][ 3 ].object_id assert result[ u"notebooks" ][ 3 ].name == u"trash" assert result[ u"notebooks" ][ 3 ].read_write == True assert result[ u"notebooks" ][ 3 ].owner == True assert result[ u"notebooks" ][ 4 ].object_id == self.anon_notebook.object_id assert result[ u"notebooks" ][ 4 ].name == self.anon_notebook.name assert result[ u"notebooks" ][ 4 ].read_write == False assert result[ u"notebooks" ][ 4 ].owner == False assert result[ u"login_url" ] is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_current_anonymous( self ): result = cherrypy.root.users.current( self.anonymous.object_id ) assert result[ u"user" ].username == "anonymous" assert len( result[ u"notebooks" ] ) == 1 assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name assert result[ u"notebooks" ][ 0 ].read_write == False assert result[ u"notebooks" ][ 0 ].owner == False login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) ) assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % ( self.settings[ u"global" ][ u"luminotes.https_url" ], self.anon_notebook.object_id, login_note.object_id, ) assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_current_after_login_with_invite_id( self ): # trick send_invites() into using a fake SMTP server 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 ] 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 ) matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/login", dict( username = self.username2, password = self.password2, invite_id = invite_id, login_button = u"login", ) ) invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] assert invite_notebook_id == self.notebooks[ 0 ].object_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 ) def test_current_after_login_with_after_login( self ): after_login = u"/foo/bar" result = self.http_post( "/users/login", dict( username = self.username2, password = self.password2, after_login = after_login, login_button = u"login", ) ) assert result[ u"redirect" ] == after_login def test_current_after_login_with_after_login_with_full_url( self ): after_login = u"http://this_url/does/not/start/with/a/slash" result = self.http_post( "/users/login", dict( username = self.username2, password = self.password2, after_login = after_login, login_button = u"login", ) ) assert result[ u"redirect" ] == u"/" def test_update_storage( self ): previous_revision = self.user.revision cherrypy.root.users.update_storage( self.user.object_id ) expected_size = cherrypy.root.users.calculate_storage( self.user ) user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == expected_size assert user.revision > previous_revision def test_update_storage_with_unknown_user_id( self ): original_revision = self.user.revision cherrypy.root.users.update_storage( 77 ) expected_size = cherrypy.root.users.calculate_storage( self.user ) user = self.database.load( User, self.user.object_id ) assert self.user.storage_bytes == 0 assert self.user.revision == original_revision def test_check_access( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id ) assert access is True def test_check_access_read_write( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True ) assert access is True def test_check_access_owner( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, owner = True ) assert access is True def test_check_access_full( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True, owner = True ) assert access is True def test_check_access_anon( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id ) assert access is True def test_check_access_anon_read_write( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True ) assert access is False def test_check_access_anon_owner( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, owner = True ) assert access is False def test_check_access_anon_full( self ): access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True, owner = True ) assert access is False def test_send_reset( self ): # trick send_reset() into using a fake SMTP server Stub_smtp.reset() smtplib.SMTP = Stub_smtp result = self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) session_id = result[ u"session_id" ] assert u"has been sent to" in result[ u"message" ] assert smtplib.SMTP.connected == False assert "<%s>" % self.settings[ u"global" ][ u"luminotes.support_email" ] in smtplib.SMTP.from_address assert smtplib.SMTP.to_addresses == [ self.user.email_address ] assert u"password reset" in smtplib.SMTP.message assert self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) def test_send_reset_to_unknown_email_address( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp result = self.http_post( "/users/send_reset", dict( email_address = u"unknown@example.com", send_reset_button = u"email me", ) ) assert u"no Luminotes user" in result[ u"error" ] assert smtplib.SMTP.connected == False assert smtplib.SMTP.from_address == None assert smtplib.SMTP.to_addresses == None assert smtplib.SMTP.message == None def test_redeem_reset( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) assert result[ u"user" ].username == "anonymous" assert len( result[ u"notebooks" ] ) == 1 assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name assert result[ u"notebooks" ][ 0 ].read_write == False assert result[ u"notebooks" ][ 0 ].owner == False login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) ) assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % ( self.settings[ u"global" ][ u"luminotes.https_url" ], self.anon_notebook.object_id, login_note.object_id, ) assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 assert result[ u"notebook" ].object_id == self.anon_notebook.object_id assert len( result[ u"startup_notes" ] ) == 1 assert result[ u"startup_notes" ][ 0 ].object_id == self.startup_note.object_id assert result[ u"startup_notes" ][ 0 ].title == self.startup_note.title assert result[ u"startup_notes" ][ 0 ].contents == self.startup_note.contents assert result[ u"note_read_write" ] is False assert result[ u"notes" ] assert len( result[ u"notes" ] ) == 1 assert result[ u"notes" ][ 0 ].title == u"complete your password reset" assert result[ u"notes" ][ 0 ].notebook_id == self.anon_notebook.object_id assert u"password reset" in result[ u"notes" ][ 0 ].contents assert self.user.username in result[ u"notes" ][ 0 ].contents assert self.user2.username in result[ u"notes" ][ 0 ].contents def test_redeem_reset_unknown( self ): password_reset_id = u"unknownresetid" result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) assert u"expired" in result[ u"error" ] def test_redeem_reset_expired( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id # to trigger expiration, pretend that the password reset was made 25 hours ago password_reset = self.database.load( Password_reset, password_reset_id ) password_reset._Persistent__revision = datetime.now( tz = utc ) - timedelta( hours = 25 ) self.database.save( password_reset ) result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) assert u"expired" in result[ u"error" ] def test_redeem_reset_already_redeemed( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id password_reset = self.database.load( Password_reset, password_reset_id ) password_reset.redeemed = True self.database.save( password_reset ) result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) assert u"already" in result[ u"error" ] def test_redeem_reset_unknown_email( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id password_reset = self.database.load( Password_reset, password_reset_id ) password_reset._Password_reset__email_address = u"unknown@example.com" self.database.save( password_reset ) result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) assert u"email address" in result[ u"error" ] def test_reset_password( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id new_password = u"newpass" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, new_password ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) assert result[ u"redirect" ] # check that the password reset is now marked as redeemed password_reset = self.database.load( Password_reset, password_reset_id ) assert password_reset.redeemed # check that the password was actually reset for one of the users, but not the other user = self.database.load( User, self.user.object_id ) assert user.check_password( new_password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( self.password2 ) def test_reset_password_unknown_reset_id( self ): new_password = u"newpass" password_reset_id = u"unknownresetid" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, new_password ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) assert u"expired" in result[ "error" ] # check that neither user's password has changed user = self.database.load( User, self.user.object_id ) assert user.check_password( self.password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( self.password2 ) def test_reset_password_invalid_reset_id( self ): new_password = u"newpass" password_reset_id = u"invalid reset id" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, new_password ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) assert u"valid" in result[ "error" ] # check that neither user's password has changed user = self.database.load( User, self.user.object_id ) assert user.check_password( self.password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( self.password2 ) def test_reset_password_expired( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id # to trigger expiration, pretend that the password reset was made 25 hours ago password_reset = self.database.load( Password_reset, password_reset_id ) password_reset._Persistent__revision = datetime.now( tz = utc ) - timedelta( hours = 25 ) self.database.save( password_reset ) new_password = u"newpass" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, new_password ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) # check that the password reset is not marked as redeemed password_reset = self.database.load( Password_reset, password_reset_id ) assert password_reset.redeemed == False assert u"expired" in result[ "error" ] # check that neither user's password has changed user = self.database.load( User, self.user.object_id ) assert user.check_password( self.password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( self.password2 ) def test_reset_password_non_matching( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id new_password = u"newpass" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, u"nonmatchingpass" ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) assert u"password" in result[ "error" ] # check that neither user's password has changed user = self.database.load( User, self.user.object_id ) assert user.check_password( self.password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( self.password2 ) def test_reset_password_blank( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, u"" ), ( self.user.object_id, u"" ), ( self.user2.object_id, u"" ), ( self.user2.object_id, u"" ), ) ) assert result[ "error" ] # check that neither user's password has changed assert self.user.check_password( self.password ) assert self.user2.check_password( self.password2 ) def test_reset_password_multiple_users( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.http_post( "/users/send_reset", dict( email_address = self.user.email_address, send_reset_button = u"email me", ) ) matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message ) password_reset_id = matches.group( 2 ) assert password_reset_id new_password = u"newpass" new_password2 = u"newpass2" result = self.http_post( "/users/reset_password", ( ( u"password_reset_id", password_reset_id ), ( u"reset_button", u"reset passwords" ), ( self.user.object_id, new_password ), ( self.user.object_id, new_password ), ( self.user2.object_id, new_password2 ), ( self.user2.object_id, new_password2 ), ) ) assert result[ u"redirect" ] # check that the password reset is now marked as redeemed password_reset = self.database.load( Password_reset, password_reset_id ) assert password_reset.redeemed # check that the password was actually reset for both users user = self.database.load( User, self.user.object_id ) assert user.check_password( new_password ) user2 = self.database.load( User, self.user2.object_id ) assert user2.check_password( new_password2 ) def test_send_invites( self ): # trick send_invites() into using a fake SMTP server 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 ] 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 ) session_id = result[ u"session_id" ] assert u"An invitation has been sent." in result[ u"message" ] invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is False assert invite.owner is False assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == 1 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] assert self.email_address in from_address assert to_addresses == email_addresses_list assert self.notebooks[ 0 ].name in message matches = self.INVITE_LINK_PATTERN.search( message ) invite_id = matches.group( 2 ) assert invite_id # assert that the invite has the read_write / owner flags set appropriately invites = self.database.objects.get( invite_id ) assert invites assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is False assert invite.owner is False def test_send_invites_collaborator( self ): # trick send_invites() into using a fake SMTP server 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 ] result = 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 ) session_id = result[ u"session_id" ] assert u"An invitation has been sent." in result[ u"message" ] invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is True assert invite.owner is False assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == 1 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] assert self.email_address in from_address assert to_addresses == email_addresses_list assert self.notebooks[ 0 ].name in message matches = self.INVITE_LINK_PATTERN.search( message ) invite_id = matches.group( 2 ) assert invite_id # assert that the invite has the read_write / owner flags set appropriately invites = self.database.objects.get( invite_id ) assert invites assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is True assert invite.owner is False def test_send_invites_owner( self ): # trick send_invites() into using a fake SMTP server 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 ] result = self.http_post( "/users/send_invites", dict( notebook_id = self.notebooks[ 0 ].object_id, email_addresses = email_addresses, access = u"owner", invite_button = u"send invites", ), session_id = self.session_id ) session_id = result[ u"session_id" ] assert u"An invitation has been sent." in result[ u"message" ] invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is True assert invite.owner is True assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == 1 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] assert self.email_address in from_address assert to_addresses == email_addresses_list assert self.notebooks[ 0 ].name in message matches = self.INVITE_LINK_PATTERN.search( message ) invite_id = matches.group( 2 ) assert invite_id # assert that the invite has the read_write / owner flags set appropriately invites = self.database.objects.get( invite_id ) assert invites assert len( invites ) == 1 invite = invites[ -1 ] assert invite assert invite.read_write is True assert invite.owner is True def test_send_invites_multiple( 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", u"bar@example.com", u"baz@example.com" ] email_addresses = u"Bob <%s>,%s\n %s " % \ ( email_addresses_list[ 0 ], email_addresses_list[ 1 ], email_addresses_list[ 2 ] ) 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 ) session_id = result[ u"session_id" ] email_count = len( email_addresses_list ) invites = result[ u"invites" ] assert len( invites ) == email_count for invite in invites: assert invite assert invite.read_write is False assert invite.owner is False assert u"%s invitations have been sent." % email_count in result[ u"message" ] assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == email_count for ( from_address, to_addresses, message ) in smtplib.SMTP.emails: assert self.email_address in from_address assert len( to_addresses ) == 1 assert to_addresses[ 0 ] in email_addresses_list email_addresses_list.remove( to_addresses[ 0 ] ) assert self.notebooks[ 0 ].name in message assert self.INVITE_LINK_PATTERN.search( message ) def test_send_invites_duplicate( 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", u"bar@example.com", u"foo@example.com" ] email_addresses = u" %s N.E. One <%s>,\n%s" % \ ( email_addresses_list[ 0 ], email_addresses_list[ 1 ], email_addresses_list[ 2 ] ) 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 ) session_id = result[ u"session_id" ] email_count = len( email_addresses_list ) - 1 # -1 because of the duplicate invites = result[ u"invites" ] assert len( invites ) == email_count for invite in invites: assert invite assert invite.read_write is False assert invite.owner is False assert u"%s invitations have been sent." % email_count in result[ u"message" ] assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == email_count for ( from_address, to_addresses, message ) in smtplib.SMTP.emails: assert self.email_address in from_address assert len( to_addresses ) == 1 assert to_addresses[ 0 ] in email_addresses_list email_addresses_list.remove( to_addresses[ 0 ] ) assert self.notebooks[ 0 ].name in message assert self.INVITE_LINK_PATTERN.search( message ) def test_send_invites_similar( 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"viewer", 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 # login as another user and redeem the invite self.login2() 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 True self.login() result = self.http_post( "/users/send_invites", dict( notebook_id = self.notebooks[ 0 ].object_id, email_addresses = email_addresses, access = u"owner", invite_button = u"send invites", ), session_id = self.session_id ) invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ 0 ] assert invite assert invite.read_write is True assert invite.owner is True 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 True 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 True assert invite1.owner is True 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 True assert invite2.owner is True # assert that the user_notebook table has also been updated accordingly access = self.database.select_one( bool, self.user2.sql_has_access( self.notebooks[ 0 ].object_id, read_write = True, owner = True, ) ) assert access is True access = self.database.select_one( bool, self.user2.sql_has_access( self.notebooks[ 0 ].trash_id, read_write = True, owner = True, ) ) assert access is True def test_send_invites_similar_downgrade( 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 # login as another user and redeem the invite self.login2() 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 self.login() 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 ) 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 # assert that the user_notebook table has also been updated accordingly access = self.database.select_one( bool, self.user2.sql_has_access( self.notebooks[ 0 ].object_id, read_write = False, owner = False, ) ) assert access is True access = self.database.select_one( bool, self.user2.sql_has_access( self.notebooks[ 0 ].trash_id, read_write = False, owner = False, ) ) assert access is True def test_send_invites_with_generic_from_address( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() # setting the user's email address to None means the invite will be sent # with a "generic" From address (in this case, Luminotes support) self.user._User__email_address = None self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ 0 ] assert invite assert invite.read_write is False assert invite.owner is False assert u"An invitation has been sent." in result[ u"message" ] assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == 1 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] assert self.settings[ u"global" ][ u"luminotes.support_email" ] in from_address assert to_addresses == email_addresses_list assert self.notebooks[ 0 ].name in message assert self.INVITE_LINK_PATTERN.search( message ) def test_send_invites_without_login( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_too_short( 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@" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] def test_send_invites_too_long( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"x" * 5001 ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] def test_send_invites_no_addresses( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"this is not an @email address!" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] def test_send_invites_without_username( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.user._User__username = None self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_without_any_access( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.database.user_notebook = {} self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_without_read_write_access( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.database.user_notebook = {} self.database.execute( self.user.sql_save_notebook( self.notebooks[ 0 ].object_id, read_write = False, owner = True ) ) self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_without_owner_access( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() self.database.user_notebook = {} self.database.execute( self.user.sql_save_notebook( self.notebooks[ 0 ].object_id, read_write = True, owner = False ) ) self.user.rate_plan = 1 self.database.save( self.user ) email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_viewer_with_lowest_rate_plan( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] 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 ) session_id = result[ u"session_id" ] invites = result[ u"invites" ] assert len( invites ) == 1 invite = invites[ 0 ] assert invite assert invite.read_write is False assert invite.owner is False assert u"An invitation has been sent." in result[ u"message" ] assert smtplib.SMTP.connected == False assert len( smtplib.SMTP.emails ) == 1 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] assert self.email_address in from_address assert to_addresses == email_addresses_list assert self.notebooks[ 0 ].name in message assert self.INVITE_LINK_PATTERN.search( message ) def test_send_invites_collaborator_with_lowest_rate_plan( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] result = 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 ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_owner_with_lowest_rate_plan( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] result = self.http_post( "/users/send_invites", dict( notebook_id = self.notebooks[ 0 ].object_id, email_addresses = email_addresses, access = u"owner", invite_button = u"send invites", ), session_id = self.session_id ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_send_invites_with_unknown_notebook( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp self.login() email_addresses_list = [ u"foo@example.com" ] email_addresses = email_addresses_list[ 0 ] unknown_notebook_id = u"neverheardofit" result = self.http_post( "/users/send_invites", dict( notebook_id = unknown_notebook_id, email_addresses = email_addresses, access = u"viewer", invite_button = u"send invites", ), session_id = self.session_id ) session_id = result[ u"session_id" ] assert result[ u"error" ] assert "access" in result[ u"error" ] def test_revoke_invite( self ): # trick send_invite() into using a fake SMTP server 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 ] 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 len( result[ u"invites" ] ) == 1 matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 0 ].object_id, invite_id = invite_id, ), session_id = self.session_id ) assert result[ u"message" ] assert len( result[ u"invites" ] ) == 0 def test_revoke_invite_multiple( 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", u"bar@example.com", u"foo@example.com" ] email_addresses = u" ".join( email_addresses_list ) 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 len( result[ u"invites" ] ) == 2 ( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ] matches = self.INVITE_LINK_PATTERN.search( message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 0 ].object_id, invite_id = invite_id, ), session_id = self.session_id ) assert result[ u"message" ] assert len( result[ u"invites" ] ) == 1 assert result[ u"invites" ][ 0 ].email_address == email_addresses_list[ 1 ] def test_revoke_invite_redeemed( 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 ] 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 len( result[ u"invites" ] ) == 1 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.user2.object_id, self.notebooks[ 0 ].object_id ) assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id ) self.login() result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 0 ].object_id, invite_id = invite_id, ), session_id = self.session_id ) assert result[ u"message" ] assert len( result[ u"invites" ] ) == 0 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_redeemed_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 ] result = self.http_post( "/users/send_invites", dict( notebook_id = self.notebooks[ 0 ].object_id, email_addresses = email_addresses, access = u"owner", invite_button = u"send invites", ), session_id = self.session_id ) assert len( result[ u"invites" ] ) == 1 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.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, ), session_id = self.session_id ) assert result[ u"message" ] assert len( result[ u"invites" ] ) == 0 # 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() smtplib.SMTP = Stub_smtp # login to send the invites, but don't send the logged-in session id for revoke_invite() below 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 ] 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 len( result[ u"invites" ] ) == 1 matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 0 ].object_id, invite_id = invite_id, ) ) assert result[ u"error" ] assert "access" in result[ u"error" ] def test_revoke_invite_unknown( self ): self.login() invite_id = u"unknowninviteid" result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 0 ].object_id, invite_id = invite_id, ), session_id = self.session_id ) assert result[ u"error" ] assert "access" in result[ u"error" ] def test_revoke_invite_for_incorrect_notebook( 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 ] 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 len( result[ u"invites" ] ) == 1 matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) invite_id = matches.group( 2 ) result = self.http_post( "/users/revoke_invite", dict( notebook_id = self.notebooks[ 1 ].object_id, invite_id = invite_id, ), session_id = self.session_id ) assert result[ u"error" ] assert "access" in result[ u"error" ] def test_redeem_invite( self ): # trick send_invite() into using a fake SMTP server 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 ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ) ) # assert that a redeem invite page is returned with sign up / login links assert result[ u"notebook" ].object_id == self.anon_notebook.object_id assert result[ u"notebook" ].name == self.anon_notebook.name assert len( result[ u"startup_notes" ] ) == 1 assert result[ u"total_notes_count" ] == 1 assert result[ u"note_read_write" ] == False assert len( result[ u"notes" ] ) == 1 assert result[ u"notes" ][ 0 ].object_id == "redeem_invite" assert u"sign up" in result[ u"notes" ][ 0 ].contents assert u"login" in result[ u"notes" ][ 0 ].contents assert result[ u"notes" ][ 0 ].notebook_id == self.anon_notebook.object_id assert len( result[ u"invites" ] ) == 0 def test_redeem_invite_unknown( self ): result = self.http_post( "/users/redeem_invite", dict( invite_id = "unknowninviteid", ) ) assert result[ u"error" ] assert u"unknown" in result[ u"error" ] def test_redeem_invite_after_login( 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 ) self.login2() result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) # assert that access has been granted 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 ) # assert that the user is redirected to the notebook that the invite is for assert result[ u"redirect"].startswith( u"/notebooks/" ) notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] assert notebook_id == self.notebooks[ 0 ].object_id def test_redeem_invite_after_login_already_redeemed( 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 ) self.login2() self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) # assert that access is still granted 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 ) # assert that the user is redirected to the notebook that the invite is for assert result[ u"redirect"].startswith( u"/notebooks/" ) notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] assert notebook_id == self.notebooks[ 0 ].object_id def test_redeem_invite_after_login_already_redeemed_by_different_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 ) self.login2() self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) self.login() result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) assert u"already" in result[ u"error" ] def test_redeem_invite_already_redeemed( 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 ) self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ), session_id = self.session_id ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ) ) assert result[ u"error" ] assert u"already" in result[ u"error" ] def test_redeem_invite_already_redeemed( 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 ) del( self.database.objects[ self.notebooks[ 0 ].object_id ] ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ) ) assert result[ u"error" ] assert u"unknown" in result[ u"error" ] def test_redeem_invite_missing_anonymous_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 ) del( self.database.objects[ self.anonymous.object_id ] ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ) ) assert result[ u"error" ] def test_redeem_invite_missing_invite_notebook( 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 ) del( self.database.objects[ self.notebooks[ 0 ].object_id ] ) result = self.http_post( "/users/redeem_invite", dict( invite_id = invite_id, ) ) assert result[ u"error" ] def test_convert_invite_to_access( self ): # trick send_invites() into using a fake SMTP server 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 ) # 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, invite.owner, ) ) assert access is True notebook = self.database.load( Notebook, invite.notebook_id ) access = self.database.select_one( bool, self.user.sql_has_access( notebook.trash_id, invite.read_write, invite.owner, ) ) assert access is True # 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() 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 ) invite = self.database.load( Invite, invite_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.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 @raises( Invite_error ) def test_convert_invite_with_unknown_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 ) invite = self.database.load( Invite, invite_id ) cherrypy.root.users.convert_invite_to_access( invite, u"unknown_user_id" ) def login( self ): result = self.http_post( "/users/login", dict( username = self.username, password = self.password, login_button = u"login", ) ) self.session_id = result[ u"session_id" ] def login2( self ): result = self.http_post( "/users/login", dict( username = self.username2, password = self.password2, login_button = u"login", ) ) self.session_id = result[ u"session_id" ]