def request_signup_email_confirmation(user, template=None, subject=None): secret_info = { 'userId': user.id, 'email': user.email, 'action': 'email_confirmation', } hours_duration = 24*14 secret = Secret.create_secret(secret_info, hours_duration) url = '{BASEURL}/#/confirmemail?key={secret_key}'.format( BASEURL=config.BASEURL, secret_key=secret.key) if template is None: template = '''<p>A community share account has been created and attached to this email address.<p> <p>To confirm that you created the account, please click on the following link.</p> <p><a href={url}>{url}</a></p> <p>If you did not create this account, simply ignore this email.</p> ''' content = template.format(url=url) if subject is None: subject = 'CommunityShare Account Creation' email = mail.Email( from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.email, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def request_password_reset(user): secret_info = { 'userId': user.id, 'action': 'password_reset', } hours_duration = 48 secret = Secret.create_secret(secret_info, hours_duration) url = '{BASEURL}/#/resetpassword?key={secret_key}'.format( BASEURL=config.BASEURL, secret_key=secret.key) content = '''<p>We received a request to reset your password for CommunityShare.</p> <p>To reset your password please click on the following link and follow the instructions.</p> <a href={url}>{url}</a> <p>If you cannot click on the link copy it into the addressbar of your browser.</p> ''' content = content.format(url=url) email = mail.Email( from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.email, subject='CommunityShare Password Reset Request', content=content, new_content=content, ) error_message = mail.get_mailer().send(email) return error_message
def setUp(self): data = { 'DB_CONNECTION': 'sqlite:///{}'.format(self.SQLLITE_FILE), 'MAILER_TYPE': 'QUEUE', 'MAILGUN_API_KEY': 'whatever', 'MAILGUN_DOMAIN': 'whatever', 'LOGGING_LEVEL': 'DEBUG', 'DONOTREPLY_EMAIL_ADDRESS': '*****@*****.**', 'SUPPORT_EMAIL_ADDRESS': '*****@*****.**', 'BUG_EMAIL_ADDRESS': '*****@*****.**', 'ABUSE_EMAIL_ADDRESS': '*****@*****.**', 'NOTIFY_EMAIL_ADDRESS': '*****@*****.**', 'ADMIN_EMAIL_ADDRESSES': '*****@*****.**', 'BASEURL': 'localhost:5000/', 'S3_BUCKETNAME': os.environ['COMMUNITYSHARE_S3_BUCKETNAME'], 'S3_KEY': os.environ['COMMUNITYSHARE_S3_KEY'], 'S3_USERNAME': os.environ['COMMUNITYSHARE_S3_USERNAME'], 'UPLOAD_LOCATION': os.environ['COMMUNITYSHARE_UPLOAD_LOCATION'], 'COMMIT_HASH': 'dummy123', 'ENCRYPTION_KEY': CryptHelper.generate_key(), } config.load_from_dict(data) setup.init_db() # Clear mail queue mailer = mail.get_mailer() while len(mailer.queue): mailer.pop() self.app = app.make_app().test_client()
def test_password_reset(self): # Signup userA userA_id, userA_api_key, userA_email_key = self.sign_up(sample_userA) self.confirm_email(userA_email_key) rv = self.app.get('/api/requestresetpassword/{0}'.format( sample_userA['email'])) assert(rv.status_code == 200) mailer = mail.get_mailer() # Check that we can authenticate with email and password headers = make_headers(email=sample_userA['email'], password=sample_userA['password']) rv = self.app.get('/api/requestapikey/', headers=headers) assert(rv.status_code == 200) # We should have one email in queue (email from password reset request) assert(len(mailer.queue) == 1) email = mailer.pop() links = email.find_links() assert(len(links)==1) email_key = re.search('key=(.*)', links[0]).groups()[0] logger.debug('email key is {0}'.format(email_key)) # Now try to reset password new_password = '******' headers = make_headers() rv = self.app.post( '/api/resetpassword', data=json.dumps({'key': email_key, 'password': new_password}), headers=headers) assert(rv.status_code==200) # Check that we can't authenticate with email and old password headers = make_headers(email=sample_userA['email'], password=sample_userA['password']) rv = self.app.get('/api/requestapikey/', headers=headers) assert(rv.status_code == 401) # Check that we can authenticate with email and new password headers = make_headers(email=sample_userA['email'], password=new_password) rv = self.app.get('/api/requestapikey/', headers=headers) assert(rv.status_code == 200)
def send_conversation_message(message): logger.debug('send_conversation_email begins') error_message = '' sender_user = message.sender_user conversation = message.get_conversation() subject = None subject = conversation.title from_address = message.generate_from_address() to_address = message.receiver_user().confirmed_email conversation_url = '{0}/api/conversation/{1}'.format( config.BASEURL, conversation.id) content = append_conversation_link(message.content, conversation) logger.info('Sending conversation message with content - {}'.format(content)) if not to_address: error_message = 'Recipient has not confirmed their email address' else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def send_event_reminder_message(event): share = event.share receivers = [share.educator, share.community_partner] other_users = [share.community_partner, share.educator] from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = EVENT_EDIT_TEMPLATE.format(event=event) url = share.conversation.get_url() subject = 'Reminder for Share on {}'.format( time_format.to_pretty(event.datetime_start)) error_messages = [] for receiver, other_user in zip(receivers, other_users): content = EVENT_REMINDER_TEMPLATE.format( share=share, eventdetails=event_details, url=url, other_user=other_user) to_address = receiver.confirmed_email if not to_address: logger.warning('Will not send event reminder to unconfirmed email address.') error_message = '{0} is not a confirmed email address'.format( receiver.email) else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) error_messages.append(error_message) combined_error_message = ', '.join( [e for e in error_messages if e is not None]) return combined_error_message
def sign_up(self, user_data): data = { 'password': user_data['password'], 'user': user_data, } serialized = json.dumps(data) headers = [('Content-Type', 'application/json')] rv = self.app.post('/api/usersignup', data=serialized, headers=headers) assert(rv.status_code == 200) data = json.loads(rv.data.decode('utf8')) user_id = data['data']['id'] api_key = data['apiKey'] authorization_header = 'Basic:api:{0}'.format(api_key) headers = [('Authorization', authorization_header)] # Create an authentication header # And try to retrieve user rv = self.app.get('/api/user/{0}'.format(user_id), headers=headers) assert(rv.status_code == 200) data = json.loads(rv.data.decode('utf8')) # User details should match compare_data(user_data, data['data'], exclusions=['password']) mailer = mail.get_mailer() # We should have one email in queue (email confimation from signup) assert(len(mailer.queue) == 1) email = mailer.pop() links = email.find_links() assert(len(links)==1) email_key = re.search('key=(.*)', links[0]).groups()[0] return user_id, api_key, email_key
def request_password_reset(user): secret_info = { 'userId': user.id, 'action': 'password_reset', } hours_duration = 48 secret = Secret.create_secret(secret_info, hours_duration) url = '{BASEURL}/#/resetpassword?key={secret_key}'.format( BASEURL=config.BASEURL, secret_key=secret.key, ) content = '''<p>We received a request to reset your password for CommunityShare.</p> <p>To reset your password please click on the following link and follow the instructions.</p> <a href={url}>{url}</a> <p>If you cannot click on the link copy it into the addressbar of your browser.</p> ''' content = content.format(url=url) email = mail.Email( from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.email, subject='CommunityShare Password Reset Request', content=content, new_content=content, ) error_message = mail.get_mailer().send(email) return error_message
def send_conversation_message(message): logger.debug('send_conversation_email begins') error_message = '' sender_user = message.sender_user conversation = message.get_conversation() subject = None subject = conversation.title from_address = message.generate_from_address() to_address = message.receiver_user().confirmed_email conversation_url = '{0}/api/conversation/{1}'.format( config.BASEURL, conversation.id) content = append_conversation_link(message.content, conversation) logger.info( 'Sending conversation message with content - {}'.format(content)) if not to_address: error_message = 'Recipient has not confirmed their email address' else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content, ) error_message = mail.get_mailer().send(email) return error_message
def request_signup_email_confirmation(user, template=None, subject=None): secret_info = { 'userId': user.id, 'email': user.email, 'action': 'email_confirmation', } hours_duration = 24 * 14 secret = Secret.create_secret(secret_info, hours_duration) url = '{BASEURL}/#/confirmemail?key={secret_key}'.format( BASEURL=config.BASEURL, secret_key=secret.key, ) if template is None: template = '''<p>A community share account has been created and attached to this email address.<p> <p>To confirm that you created the account, please click on the following link.</p> <p><a href={url}>{url}</a></p> <p>If you did not create this account, simply ignore this email.</p> ''' content = template.format(url=url) if subject is None: subject = 'CommunityShare Account Creation' email = mail.Email(from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.email, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) return error_message
def request_signup_email_confirmation(user): secret_info = { 'userId': user.id, 'email': user.email, 'action': 'email_confirmation', } hours_duration = 48 secret = Secret.create_secret(secret_info, hours_duration) content = '''A community share account has been created and attached to this email address. To confirm that you created the account, please click on the following link. {BASEURL}/#/confirmemail?key={secret_key} If you did not create this account, simply ignore this email. ''' content = content.format(BASEURL=config.BASEURL, secret_key=secret.key) email = mail.Email( from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.email, subject='CommunityShare Account Creation', content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def request_password_reset(user): secret_info = { 'userId': user.id, 'action': 'password_reset', } hours_duration = 48 secret = Secret.create_secret(secret_info, hours_duration) content = '''We received a request to reset your password for CommunityShare. To reset your password please click on the following link and follow the instructions. {BASEURL}/#/resetpassword?key={secret_key} If you cannot click on the link copy it into the addressbar of your browser. ''' content = content.format(BASEURL=config.BASEURL, secret_key=secret.key) if not user.email_confirmed: error_message = 'The email address is not confirmed.' else: email = mail.Email( from_address=config.DONOTREPLY_EMAIL_ADDRESS, to_address=user.confirmed_email, subject='CommunityShare Password Reset Request', content=content, new_content=content, ) error_message = mail.get_mailer().send(email) return error_message
def send_event_reminder_message(event): share = event.share receivers = [share.educator, share.community_partner] other_users = [share.community_partner, share.educator] from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = EVENT_EDIT_TEMPLATE.render(event=event) url = share.conversation.get_url() subject = 'Reminder for Share on {}'.format( time_format.to_pretty(event.datetime_start)) error_messages = [] for receiver, other_user in zip(receivers, other_users): content = EVENT_REMINDER_TEMPLATE.render( share=share, eventdetails=event_details, url=url, other_user=other_user, ) to_address = receiver.confirmed_email if not to_address: logger.warning( 'Will not send event reminder to unconfirmed email address.') error_message = '{0} is not a confirmed email address'.format( receiver.email) else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) error_messages.append(error_message) combined_error_message = ', '.join( [e for e in error_messages if e is not None]) return combined_error_message
def test_reminders(self): user_datas = { 'userA': sample_userA, 'userB': sample_userB, } user_ids, user_headers = self.create_users(user_datas) searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB']) conversation_id = conversation_data['id'] share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], starting_in_hours=0.5) mailer = mail.get_mailer() while mailer.queue: mailer.pop() events = EventReminder.get_oneday_reminder_events() assert(len(events) == 1) worker.work_loop(target_time_between_calls=datetime.timedelta(seconds=1), max_loops=2) events = EventReminder.get_oneday_reminder_events() assert(len(events) == 0) # Two reminder emails should have been sent. assert(len(mailer.queue) == 2) email1 = mailer.pop() email2 = mailer.pop() expected_email_addresses = set([sample_userA['email'], sample_userB['email']]) rcvd_email_addresses = set([email1.to_address, email2.to_address]) assert(expected_email_addresses == rcvd_email_addresses)
def send_share_message(share, editer, new_share=False, is_confirmation=False, is_delete=False): receivers = [share.conversation.userA, share.conversation.userB] receivers = [r for r in receivers if (editer.id != r.id)] from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = "".join([EVENT_EDIT_TEMPLATE.format(event=event) for event in share.events]) url = share.conversation.get_url() if is_confirmation: subject = "Share Details Confirmed: {0}".format(share.title) content = SHARE_CONFIRMATION_TEMPLATE.format(share=share, eventdetails=event_details, url=url, editer=editer) elif is_delete: subject = "Share Canceled: {0}".format(share.title) content = SHARE_DELETION_TEMPLATE.format(share=share, eventdetails=event_details, url=url, editer=editer) elif new_share: subject = "Share Details Suggested: {0}".format(share.title) content = SHARE_CREATION_TEMPLATE.format(share=share, eventdetails=event_details, url=url, editer=editer) else: subject = "Share Details Edited: {0}".format(share.title) content = SHARE_EDIT_TEMPLATE.format(share=share, eventdetails=event_details, url=url, editer=editer) error_messages = [] for receiver in receivers: to_address = receiver.confirmed_email if not to_address: error_message = "{0} is not a confirmed email address".format(receiver.email) else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) error_messages.append(error_message) combined_error_message = ", ".join([e for e in error_messages if e is not None]) return combined_error_message
def test_account_deletion(self): user_datas = { 'userA': sample_userA, 'userB': sample_userB, } user_ids, user_headers = self.create_users(user_datas) searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB'], ) conversation_id = conversation_data['id'] share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], starting_in_hours=0.5, ) mailer = mail.get_mailer() while mailer.queue: mailer.pop() # userB is deleting their account rv = self.app.delete( '/api/user/{0}'.format(user_ids['userB']), headers=user_headers['userB'], ) self.assertEqual(rv.status_code, 200) # Emails should have been sent to userB (confirming deletion) # and UserA since they had an event scheduled. self.assertEqual(len(mailer.queue), 2) # userB should not longer be able to do things. data = { 'searcher_user_id': user_ids['userB'], 'searcher_role': 'educator', 'searching_for_role': 'partner', 'labels': 'Whatever', 'zipcode': 12345 } serialized = json.dumps(data) rv = self.app.post('/api/search', data=serialized, headers=user_headers['userB']) self.assertEqual(rv.status_code, 401) # And we shouldn't be able to login anymore headers = make_headers(email=sample_userB['email'], password=sample_userB['password']) rv = self.app.get('/api/requestapikey/', headers=headers) self.assertEqual(rv.status_code, 404)
def setUp(self): config.load_config('./config.dev.json') config.MAILER_TYPE = 'QUEUE' config.DB_CONNECTION = 'sqlite:///{}'.format(self.SQLLITE_FILE) # When config.load_config is called, it sets self as the config in the # global store. Since we have mutated the config manually, we need to # re-set_config it in store. store.set_config(config) setup.init_db() # Clear mail queue mailer = mail.get_mailer() while len(mailer.queue): mailer.pop() self.app = app.make_app().test_client()
def send_account_deletion_message(user): admin_email = config.SUPPORT_EMAIL_ADDRESS subject = "Community Share Account Deletion" content = ACCOUNT_DELETION_TEMPLATE.format(admin_email=admin_email) to_address = user.confirmed_email from_address = config.SUPPORT_EMAIL_ADDRESS if not to_address: error_message = "{0} is not a confirmed email address".format(user.email) else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def send_partner_deletion_message(user, canceled_user, conversation): subject = "Community Share Account Deletion" url = conversation.get_url() content = PARTNER_DELETION_TEMPLATE.format(canceled_user=canceled_user, url=url) to_address = user.confirmed_email from_address = config.DONOTREPLY_EMAIL_ADDRESS if not to_address: error_message = "{0} is not a confirmed email address".format(receiver.email) else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def send_notify_share_creation(share, requester): to_address = config.NOTIFY_EMAIL_ADDRESS from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = "".join([EVENT_EDIT_TEMPLATE.format(event=event) for event in share.events]) url = share.conversation.get_url() subject = "Share Created: {0}".format(share.title) content = NOTIFY_SHARE_CREATION_TEMPLATE.format(share=share, eventdetails=event_details, url=url) if not to_address: error_message = "Recipient has not confirmed their email address" else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def send_review_reminder_message(user, event): subject = "Review Community Share Event" url = event.get_url() event_details = EVENT_EDIT_TEMPLATE.format(event=event) content = REVIEW_REMINDER_TEMPLATE.format(url=url, share=event.share, eventdetails=event_details) to_address = user.confirmed_email from_address = config.DONOTREPLY_EMAIL_ADDRESS if not to_address: error_message = "{0} is not a confirmed email address".format(receiver.email) else: email = mail.Email( from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content ) error_message = mail.get_mailer().send(email) return error_message
def receive_email(): logger.debug('Received an email.') if config.MAILER_TYPE == 'MAILGUN': verify = True else: verify = False email = Email.from_mailgun_data(request.values, verify=verify) logger.info('Received an email with content "{}"'.format( email.content)) message = None message_id = Message.process_from_address(email.to_address) if message_id is not None: message = store.session.query(Message).filter_by( id=message_id).first() if message is None: logger.warning( 'Received an email but did not find corresponding message.') else: logger.debug('Creating a new email to send') # Create a new message new_message = Message( conversation_id=message.conversation_id, sender_user_id=message.receiver_user().id, content=email.new_content, ) store.session.add(new_message) store.session.commit() # Create an email to send to the recipient forward_to_address = message.sender_user.email forward_from_address = new_message.generate_from_address() forward_content = append_conversation_link(email.content, message.conversation) forward_new_content = append_conversation_link( email.new_content, message.conversation) forward_email = Email( from_address=forward_from_address, to_address=forward_to_address, subject=email.subject, content=forward_content, new_content=forward_new_content, ) error_message = get_mailer().send(forward_email) response = base_routes.make_OK_response() return response
def send_account_deletion_message(user): admin_email = config.SUPPORT_EMAIL_ADDRESS subject = 'Community Share Account Deletion' content = ACCOUNT_DELETION_TEMPLATE.render(admin_email=admin_email) to_address = user.confirmed_email from_address = config.SUPPORT_EMAIL_ADDRESS if not to_address: error_message = '{0} is not a confirmed email address'.format( user.email) else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) return error_message
def receive_email(): logger.debug('Received an email.') if config.MAILER_TYPE == 'MAILGUN': verify = True else: verify = False email = Email.from_mailgun_data(request.values, verify=verify) logger.info('Received an email with content "{}"'.format(email.content)) message = None message_id = Message.process_from_address(email.to_address) if message_id is not None: message = store.session.query(Message).filter_by(id=message_id).first() if message is None: logger.warning('Received an email but did not find corresponding message.') else: logger.debug('Creating a new email to send') # Create a new message new_message = Message( conversation_id=message.conversation_id, sender_user_id=message.receiver_user().id, content=email.new_content, ) store.session.add(new_message) store.session.commit() # Create an email to send to the recipient forward_to_address = message.sender_user.email forward_from_address = new_message.generate_from_address() forward_content = append_conversation_link(email.content, message.conversation) forward_new_content = append_conversation_link(email.new_content, message.conversation) forward_email = Email( from_address=forward_from_address, to_address=forward_to_address, subject=email.subject, content=forward_content, new_content=forward_new_content, ) error_message = get_mailer().send(forward_email) response = base_routes.make_OK_response() return response
def send_partner_deletion_message(user, canceled_user, conversation): subject = 'Community Share Account Deletion' url = conversation.get_url() content = PARTNER_DELETION_TEMPLATE.render(canceled_user=canceled_user, url=url) if user is None: error_message = '{0} other user in share does not exist' else: to_address = user.confirmed_email from_address = config.DONOTREPLY_EMAIL_ADDRESS if not to_address: error_message = '{0} is not a confirmed email address'.format( receiver.email) else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) return error_message
def test_password_reset(self): # Signup userA userA_id, userA_api_key, userA_email_key = self.sign_up(sample_userA) self.confirm_email(userA_email_key) rv = self.app.get('/api/requestresetpassword/{0}'.format( sample_userA['email'])) self.assertEqual(rv.status_code, 200) mailer = mail.get_mailer() # Check that we can authenticate with email and password headers = make_headers(email=sample_userA['email'], password=sample_userA['password']) rv = self.app.get('/api/requestapikey', headers=headers) self.assertEqual(rv.status_code, 200) # We should have one email in queue (email from password reset request) self.assertEqual(len(mailer.queue), 1) email = mailer.pop() links = email.find_links() self.assertEqual(len(links), 1) email_key = re.search('key=([a-zA-Z0-9]*)', links[0]).groups()[0] logger.debug('email key is {0}'.format(email_key)) # Now try to reset password new_password = '******' headers = make_headers() rv = self.app.post('/api/resetpassword', data=json.dumps({ 'key': email_key, 'password': new_password, }), headers=headers) self.assertEqual(rv.status_code, 200) # Check that we can't authenticate with email and old password headers = make_headers(email=sample_userA['email'], password=sample_userA['password']) rv = self.app.get('/api/requestapikey', headers=headers) self.assertEqual(rv.status_code, 401) # Check that we can authenticate with email and new password headers = make_headers(email=sample_userA['email'], password=new_password) rv = self.app.get('/api/requestapikey', headers=headers) self.assertEqual(rv.status_code, 200)
def test_reminders(self): user_datas = { 'userA': sample_userA, 'userB': sample_userB, } user_ids, user_headers = self.create_users(user_datas) searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB'], ) conversation_id = conversation_data['id'] share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], starting_in_hours=0.5, ) mailer = mail.get_mailer() while mailer.queue: mailer.pop() events = EventReminder.get_oneday_reminder_events() self.assertEqual(len(events), 1) worker.work_loop( target_time_between_calls=datetime.timedelta(seconds=1), max_loops=2) events = EventReminder.get_oneday_reminder_events() self.assertEqual(len(events), 0) # Two reminder emails should have been sent. self.assertEqual(len(mailer.queue), 2) email1 = mailer.pop() email2 = mailer.pop() expected_email_addresses = set( [sample_userA['email'], sample_userB['email']]) rcvd_email_addresses = set([email1.to_address, email2.to_address]) self.assertEqual(expected_email_addresses, rcvd_email_addresses)
def send_notify_share_creation(share, requester): to_address = config.NOTIFY_EMAIL_ADDRESS from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = ''.join( [EVENT_EDIT_TEMPLATE.render(event=event) for event in share.events]) url = share.conversation.get_url() subject = 'Share Created: {0}'.format(share.title) content = NOTIFY_SHARE_CREATION_TEMPLATE.render( share=share, eventdetails=event_details, url=url, ) if not to_address: error_message = 'Recipient has not confirmed their email address' else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) return error_message
def send_review_reminder_message(user, event): subject = 'Review Community Share Event' url = event.get_url() event_details = EVENT_EDIT_TEMPLATE.render(event=event) content = REVIEW_REMINDER_TEMPLATE.render( url=url, share=event.share, eventdetails=event_details, ) to_address = user.confirmed_email from_address = config.DONOTREPLY_EMAIL_ADDRESS if not to_address: error_message = '{0} is not a confirmed email address'.format( receiver.email) else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) return error_message
def sign_up(self, user_data): data = { 'password': user_data['password'], 'user': user_data, } serialized = json.dumps(data) headers = [('Content-Type', 'application/json')] rv = self.app.post('/api/usersignup', data=serialized, headers=headers) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8')) user_id = data['data']['id'] api_key = data['apiKey'] authorization_header = 'Basic:api:{0}'.format(api_key) headers = [('Authorization', authorization_header)] # Create an authentication header # And try to retrieve user rv = self.app.get('/api/user/{0}'.format(user_id), headers=headers) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8')) # User details should match # yapf: disable relevant_keys = user_data.keys() - {'password'} self.assertDictEqual( {k: v for k, v in user_data.items() if k in relevant_keys}, {k: v for k, v in data['data'].items() if k in relevant_keys} ) # yapf: enable mailer = mail.get_mailer() # We should have one email in queue (email confimation from signup) self.assertEqual(len(mailer.queue), 1) email = mailer.pop() links = email.find_links() self.assertEqual(len(links), 1) email_key = re.search('key=([a-zA-Z0-9]*)', links[0]).groups()[0] return user_id, api_key, email_key
def test_one(self): # Make sure we get an OK when requesting index. rv = self.app.get('/') self.assertEqual(rv.status_code, 200) # Now try to get user from API # Expect forbidden (401) rv = self.app.get('/api/user/1') self.assertEqual(rv.status_code, 401) # Now sign up UserA userA_id, userA_api_key, userA_email_key = self.sign_up(sample_userA) # Get userA and check that email is not confirmed userA_headers = make_headers(userA_api_key) rv = self.app.get('/api/user/1', headers=userA_headers) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] # Confirm email self.confirm_email(userA_email_key) # Get userA and check that email is confirmed rv = self.app.get('/api/user/1', headers=userA_headers) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] self.assertTrue(data['email_confirmed']) # Sign up UserB userB_id, userB_api_key, userB_email_key = self.sign_up(sample_userB) self.confirm_email(userB_email_key) # Create Searches userB_headers = make_headers(api_key=userB_api_key) user_headers = { 'userA': userA_headers, 'userB': userB_headers, } user_ids = {'userA': userA_id, 'userB': userB_id} searchA_id, searchB_id = self.create_searches(user_ids, user_headers) # Get all the results for userA's search rv = self.app.get( '/api/search/{0}/0/results'.format(searchA_id), headers=user_headers['userA'], ) data = json.loads(rv.data.decode('utf8')) searches = data['data'] self.assertEqual(len(searches), 1) self.assertEqual(searches[0]['searcher_user_id'], userB_id) # Now userA starts a conversation with userB conversation_title = 'Trip to moon.' conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title=conversation_title, userA_id=user_ids['userA'], userB_id=user_ids['userB'], ) conversation_id = conversation_data['id'] # And send the first message message_content = 'Are you interested in going to the moon?' rv = self.send_message( conversation_id=conversation_id, sender_user_id=userA_id, content=message_content, api_key=userA_api_key, ) data = json.loads(rv.data.decode('utf8')) message_id = data['data']['id'] mailer = mail.get_mailer() # We should have one email in queue (email about new message) self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.subject, conversation_title) self.assertTrue(email.content.startswith(message_content)) self.assertEqual(email.to_address, sample_userB['email']) new_reply_content = 'Sure, sounds great!' reply_email = email.make_reply(new_reply_content) self.assertEqual(reply_email.subject, conversation_title) self.assertTrue(reply_email.content.startswith(new_reply_content)) self.assertEqual(reply_email.from_address, email.to_address) self.assertEqual(reply_email.to_address, email.from_address) # That email should contain a link to the conversation # We're not running the javascript so we can't test it properly. links = email.find_links() self.assertEqual(len(links), 1) chopped_link = chop_link(links[0]) rv = self.app.get(chopped_link) self.assertEqual(rv.status_code, 200) # Send the reply email to our email API in the form of a Mailgun # request. rv = self.app.post('/api/email', data=reply_email.make_mailgun_data()) self.assertEqual(rv.status_code, 200) # It should have been forwarded to the other user. self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.subject, conversation_title) self.assertTrue(email.content.startswith(new_reply_content)) self.assertEqual(email.to_address, sample_userA['email']) # And we should now have two messages in the conversation # We'll hit the conversation API to confirm this. rv = self.app.get( '/api/conversation/{0}'.format(conversation_id), headers=user_headers['userA'], ) rcvd_conversation_data = json.loads(rv.data.decode('utf8'))['data'] self.assertEqual(rcvd_conversation_data['id'], conversation_id) self.assertEqual(rcvd_conversation_data['title'], conversation_title) messages_data = rcvd_conversation_data['messages'] self.assertEqual(len(messages_data), 2) self.assertEqual(messages_data[0]['content'], message_content) self.assertEqual(messages_data[0]['sender_user_id'], userA_id) self.assertEqual(messages_data[1]['content'], new_reply_content) self.assertEqual(messages_data[1]['sender_user_id'], userB_id)
def test_share(self): # Signup users and confirm emails user_datas = { 'userA': sample_userA, 'userB': sample_userB, 'userC': sample_userC, } user_ids, user_headers = self.create_users(user_datas) user_headers['noone'] = make_headers() searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB'], ) conversation_id = conversation_data['id'] # userA creates a share share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], ) share_id = share_data['id'] self.assertEqual(len(share_data['events']), 1) # This should send an email to userB that a share has been created. # This also sends an email to the "notify" address. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 2) email = [email for email in mailer.queue if email.to_address == sample_userB['email']][0] mailer.queue.pop() mailer.queue.pop() self.assertEqual(email.to_address, sample_userB['email']) # Check link is valid links = email.find_links() self.assertEqual(len(links), 1) chopped_link = chop_link(links[0]) rv = self.app.get(chopped_link) self.assertEqual(rv.status_code, 200) # We should not be able to get this share if unauthenticated rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['noone']) self.assertEqual(rv.status_code, 401) # Logged on users can access share info # FIXME: Need to check if this should be more private. rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['userC']) self.assertEqual(rv.status_code, 200) # User B should be able to access it. rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['userB']) self.assertEqual(rv.status_code, 200) share_data = json.loads(rv.data.decode('utf8'))['data'] self.assertEqual(share_data['id'], share_id) self.assertTrue(share_data['educator_approved']) self.assertFalse(share_data['community_partner_approved']) # Now let's edit the share. share_data = { 'description': 'Is the moon made of Cheese? There is only one way to find out!', } serialized = json.dumps(share_data) # Unauthenticated person should not be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['noone'], data=serialized, ) self.assertEqual(rv.status_code, 401) # User C should not be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userC'], data=serialized, ) self.assertEqual(rv.status_code, 403) # User B should be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] # There should still be one event there. self.assertEqual(len(data['events']), 1) event_id = data['events'][0]['id'] # This should send an email to userA that a share has been edited. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # User B edits it and adds an additional event existing_event = data['events'][0] now = datetime.datetime.utcnow() starting = now + datetime.timedelta(hours=3) ending = now + datetime.timedelta(hours=4) data['events'].append( { 'location': 'Somewhere Else', 'datetime_start': time_format.to_iso8601(starting), 'datetime_stop': time_format.to_iso8601(ending), } ) serialized = json.dumps(data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) data = json.loads(rv.data.decode('utf8'))['data'] ids = set([e['id'] for e in data['events']]) self.assertIn(event_id, ids) self.assertEqual(len(ids), 2) self.assertEqual(rv.status_code, 200) # This should send an email to userA that a share has been edited. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # And who has given approval is switched. self.assertFalse(data['educator_approved']) self.assertTrue(data['community_partner_approved']) # User A can do a put with no changes to approve. serialized = json.dumps(data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userA'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] self.assertTrue(data['educator_approved']) self.assertTrue(data['community_partner_approved']) # This should send an email to userB that changes have been approved. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userB['email']) # Now userB deletes the events share_data = {'events': []} serialized = json.dumps(share_data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] self.assertEqual(len(data['events']), 0) # User A should have received an email mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # UserA cancels the share rv = self.app.delete('api/share/{0}'.format(share_id), headers=user_headers['userA']) self.assertEqual(rv.status_code, 200) # User B should have received an email. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userB['email'])
def send_share_message( share, editer, new_share=False, is_confirmation=False, is_delete=False, ): receivers = [share.conversation.userA, share.conversation.userB] receivers = [r for r in receivers if (editer.id != r.id)] from_address = config.DONOTREPLY_EMAIL_ADDRESS event_details = ''.join( [EVENT_EDIT_TEMPLATE.render(event=event) for event in share.events]) url = share.conversation.get_url() if is_confirmation: subject = 'Share Details Confirmed: {0}'.format(share.title) content = SHARE_CONFIRMATION_TEMPLATE.render( share=share, eventdetails=event_details, url=url, editer=editer, ) elif is_delete: subject = 'Share Canceled: {0}'.format(share.title) content = SHARE_DELETION_TEMPLATE.render( share=share, eventdetails=event_details, url=url, editer=editer, ) elif new_share: subject = 'Share Details Suggested: {0}'.format(share.title) content = SHARE_CREATION_TEMPLATE.render( share=share, eventdetails=event_details, url=url, editer=editer, ) else: subject = 'Share Details Edited: {0}'.format(share.title) content = SHARE_EDIT_TEMPLATE.render( share=share, eventdetails=event_details, url=url, editer=editer, ) error_messages = [] for receiver in receivers: to_address = receiver.confirmed_email if not to_address: error_message = '{0} is not a confirmed email address'.format( receiver.email) else: email = mail.Email(from_address=from_address, to_address=to_address, subject=subject, content=content, new_content=content) error_message = mail.get_mailer().send(email) error_messages.append(error_message) combined_error_message = ', '.join( [e for e in error_messages if e is not None]) return combined_error_message
def test_share(self): # Signup users and confirm emails user_datas = { 'userA': sample_userA, 'userB': sample_userB, 'userC': sample_userC, } user_ids, user_headers = self.create_users(user_datas) user_headers['noone'] = make_headers() searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB'], ) conversation_id = conversation_data['id'] # userA creates a share share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], ) share_id = share_data['id'] self.assertEqual(len(share_data['events']), 1) # This should send an email to userB that a share has been created. # This also sends an email to the "notify" address. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 2) email = [ email for email in mailer.queue if email.to_address == sample_userB['email'] ][0] mailer.queue.pop() mailer.queue.pop() self.assertEqual(email.to_address, sample_userB['email']) # Check link is valid links = email.find_links() self.assertEqual(len(links), 1) chopped_link = chop_link(links[0]) rv = self.app.get(chopped_link) self.assertEqual(rv.status_code, 200) # We should not be able to get this share if unauthenticated rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['noone']) self.assertEqual(rv.status_code, 401) # Logged on users can access share info # FIXME: Need to check if this should be more private. rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['userC']) self.assertEqual(rv.status_code, 200) # User B should be able to access it. rv = self.app.get('/api/share/{0}'.format(share_id), headers=user_headers['userB']) self.assertEqual(rv.status_code, 200) share_data = json.loads(rv.data.decode('utf8'))['data'] self.assertEqual(share_data['id'], share_id) self.assertTrue(share_data['educator_approved']) self.assertFalse(share_data['community_partner_approved']) # Now let's edit the share. share_data = { 'description': 'Is the moon made of Cheese? There is only one way to find out!', } serialized = json.dumps(share_data) # Unauthenticated person should not be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['noone'], data=serialized, ) self.assertEqual(rv.status_code, 401) # User C should not be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userC'], data=serialized, ) self.assertEqual(rv.status_code, 403) # User B should be able to edit it. rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] # There should still be one event there. self.assertEqual(len(data['events']), 1) event_id = data['events'][0]['id'] # This should send an email to userA that a share has been edited. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # User B edits it and adds an additional event existing_event = data['events'][0] now = datetime.datetime.utcnow() starting = now + datetime.timedelta(hours=3) ending = now + datetime.timedelta(hours=4) data['events'].append({ 'location': 'Somewhere Else', 'datetime_start': time_format.to_iso8601(starting), 'datetime_stop': time_format.to_iso8601(ending), }) serialized = json.dumps(data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) data = json.loads(rv.data.decode('utf8'))['data'] ids = set([e['id'] for e in data['events']]) self.assertIn(event_id, ids) self.assertEqual(len(ids), 2) self.assertEqual(rv.status_code, 200) # This should send an email to userA that a share has been edited. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # And who has given approval is switched. self.assertFalse(data['educator_approved']) self.assertTrue(data['community_partner_approved']) # User A can do a put with no changes to approve. serialized = json.dumps(data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userA'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] self.assertTrue(data['educator_approved']) self.assertTrue(data['community_partner_approved']) # This should send an email to userB that changes have been approved. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userB['email']) # Now userB deletes the events share_data = {'events': []} serialized = json.dumps(share_data) rv = self.app.put( '/api/share/{0}'.format(share_id), headers=user_headers['userB'], data=serialized, ) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode('utf8'))['data'] self.assertEqual(len(data['events']), 0) # User A should have received an email mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userA['email']) # UserA cancels the share rv = self.app.delete('api/share/{0}'.format(share_id), headers=user_headers['userA']) self.assertEqual(rv.status_code, 200) # User B should have received an email. mailer = mail.get_mailer() self.assertEqual(len(mailer.queue), 1) email = mailer.pop() self.assertEqual(email.to_address, sample_userB['email'])
def test_user_review(self): user_datas = { 'userA': sample_userA, 'userB': sample_userB, 'userC': sample_userC, } user_ids, user_headers = self.create_users(user_datas) searchA_id, searchB_id = self.create_searches(user_ids, user_headers) conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB']) conversation_id = conversation_data['id'] share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], starting_in_hours=0.5, force_past_events=False) # We shouldn't be able to save a review for the current event # because it is in the future share_id = share_data['id'] eventA_id = share_data['events'][0]['id'] review_data = { 'event_id': eventA_id, 'user_id': user_ids['userA'], 'rating': 3, } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 403) # Two reminder emails should have been sent. # This check is mostly here since we need to flush the emails out # to test review reminder emails later. mailer = mail.get_mailer() while mailer.queue: mailer.pop() worker.work_loop(target_time_between_calls=datetime.timedelta(seconds=1), max_loops=2) assert(len(mailer.queue) == 2) while mailer.queue: mailer.pop() # Let's make another conversation and share this time but put event in past. conversation_data = self.make_conversation( user_headers['userA'], search_id=searchA_id, title='Trip to moon.', userA_id=user_ids['userA'], userB_id=user_ids['userB']) conversation_id = conversation_data['id'] share_data = self.make_share( user_headers['userA'], conversation_id, educator_user_id=user_ids['userA'], community_partner_user_id=user_ids['userB'], starting_in_hours=-26, force_past_events=True) eventA_id = share_data['events'][0]['id'] # We should receive an email telling us to review it. events = EventReminder.get_review_reminder_events() assert(len(events) == 1) while mailer.queue: mailer.pop() worker.work_loop(target_time_between_calls=datetime.timedelta(seconds=1), max_loops=2) events = EventReminder.get_review_reminder_events() assert(len(events) == 0) # Two reminder emails should have been sent. assert(len(mailer.queue) == 2) # Now try to make a review for the wrong user review_data = { 'event_id': eventA_id, 'user_id': user_ids['userC'], 'rating': 3, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 403) # Now try to make a review for oneself review_data = { 'event_id': eventA_id, 'user_id': user_ids['userB'], 'rating': 3, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 403) # Now try to make a review with invalid rating review_data = { 'event_id': eventA_id, 'user_id': user_ids['userA'], 'rating': 6, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 400) # Now try to make a review with invalid rating again review_data = { 'event_id': eventA_id, 'user_id': user_ids['userA'], 'rating': -1, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 400) # Make a valid review review_data = { 'event_id': eventA_id, 'user_id': user_ids['userA'], 'rating': 3, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 200) # We can't make a second review based off the same event review_data = { 'event_id': eventA_id, 'user_id': user_ids['userA'], 'rating': 3, 'review': 'Was really super' } serialized = json.dumps(review_data) rv = self.app.post('/api/user_review', data=serialized, headers=user_headers['userB']) assert(rv.status_code == 403)