def password_email(user): """ For resetting a user's password. """ from r2.lib.pages import PasswordReset user_reset_ratelimit = SimpleRateLimit( name="email_reset_count_%s" % user._id36, seconds=int(datetime.timedelta(hours=12).total_seconds()), limit=3, ) if not user_reset_ratelimit.record_and_check(): return False global_reset_ratelimit = SimpleRateLimit( name="email_reset_count_global", seconds=int(datetime.timedelta(hours=1).total_seconds()), limit=1000, ) if not global_reset_ratelimit.record_and_check(): raise ValueError("password reset ratelimit exceeded") token = PasswordResetToken._new(user) base = g.https_endpoint or g.origin passlink = base + '/resetpassword/' + token._id g.log.info("Generated password reset link: " + passlink) _system_email(user.email, PasswordReset(user=user, passlink=passlink).render(style='email'), Email.Kind.RESET_PASSWORD, user=user, ) return True
def POST_mute_participant(self, conversation): if conversation.is_internal or conversation.is_auto: return self.send_error(400, errors.CANT_RESTRICT_MODERATOR) sr = Subreddit._by_fullname(conversation.owner_fullname) try: participant = conversation.get_participant_account() except NotFound: return self.send_error(404, errors.USER_DOESNT_EXIST) if not sr.can_mute(c.user, participant): return self.send_error(400, errors.CANT_RESTRICT_MODERATOR) if not c.user_is_admin: if not sr.is_moderator_with_perms(c.user, 'access', 'mail'): return self.send_error(403, errors.INVALID_MOD_PERMISSIONS) if sr.use_quotas: sr_ratelimit = SimpleRateLimit( name="sr_muted_%s" % sr._id36, seconds=g.sr_quota_time, limit=g.sr_muted_quota, ) if not sr_ratelimit.record_and_check(): return self.send_error(403, errors.SUBREDDIT_RATELIMIT) # Add the mute record but only if successful create the # appropriate notifications, this prevents duplicate # notifications from being sent added = sr.add_muted(participant) if not added: return simplejson.dumps( self._convo_to_serializable(conversation, all_messages=True)) MutedAccountsBySubreddit.mute(sr, participant, c.user) permalink = conversation.make_permalink() # Create the appropriate objects to be displayed on the # mute moderation log, use the permalink to the new modmail # system ModAction.create(sr, c.user, 'muteuser', target=participant, description=permalink) sr.add_rel_note('muted', participant, permalink) # Add the muted mod action to the conversation conversation.add_action(c.user, 'muted', commit=True) result = self._get_updated_convo(conversation.id, c.user) result['user'] = self._get_modmail_userinfo(conversation, sr=sr) return simplejson.dumps(result)
def password_email(user): """ For resetting a user's password. """ from r2.lib.pages import PasswordReset user_reset_ratelimit = SimpleRateLimit( name="email_reset_count_%s" % user._id36, seconds=int(datetime.timedelta(hours=12).total_seconds()), limit=3, ) if not user_reset_ratelimit.record_and_check(): return False global_reset_ratelimit = SimpleRateLimit( name="email_reset_count_global", seconds=int(datetime.timedelta(hours=1).total_seconds()), limit=1000, ) if not global_reset_ratelimit.record_and_check(): raise ValueError("password reset ratelimit exceeded") token = PasswordResetToken._new(user) base = g.https_endpoint or g.origin passlink = base + '/resetpassword/' + token._id g.log.info("Generated password reset link: " + passlink) _system_email( user.email, PasswordReset(user=user, passlink=passlink).render(style='email'), Email.Kind.RESET_PASSWORD, user=user, ) return True
def message_notification_email(data): """Queues a system email for a new message notification.""" from r2.lib.pages import MessageNotificationEmail timer_start = time.time() MAX_EMAILS_PER_USER = 30 MAX_MESSAGES_PER_BATCH = 5 total_messages_sent = 0 inbox_item_lookup_count = 0 unique_user_list = make_message_dict_unique(data) g.log.info("there are %s users for this batch of emails" % len(unique_user_list)) for datum in unique_user_list.itervalues(): user = Account._byID36(datum['to'], data=True) g.log.info('user fullname: %s' % user._fullname) # In case a user has enabled the preference while it was enabled for # them, but we've since turned it off. We need to explicitly state the # user because we're not in the context of an HTTP request from them. if not feature.is_enabled('orangereds_as_emails', user=user): g.log.info('feature not enabled for user: %s' % user._fullname) continue # Don't send more than MAX_EMAILS_PER_USER per user per day user_notification_ratelimit = SimpleRateLimit( name="email_message_notification_%s" % user._id36, seconds=int(datetime.timedelta(days=1).total_seconds()), limit=MAX_EMAILS_PER_USER, ) if not user_notification_ratelimit.check(): g.log.info('message blocked at user_notification_ratelimit: %s' % user_notification_ratelimit) continue # Get all new messages that haven't been emailed inbox_items = get_unread_and_unemailed(user) inbox_item_lookup_count += 1 if not inbox_items: g.log.info('no inbox items found for %s' % user._fullname) continue newest_inbox_rel = inbox_items[-1][0] oldest_inbox_rel = inbox_items[0][0] now = datetime.datetime.now(g.tz) start_date = datetime.datetime.strptime( datum['start_date'], "%Y-%m-%d %H:%M:%S").replace(tzinfo=g.tz) # If messages are still being queued within the cooling period or # messages have been queued past the max delay, then keep waiting # a little longer to batch all of the messages up if (start_date != newest_inbox_rel._date and now < newest_inbox_rel._date + NOTIFICATION_EMAIL_COOLING_PERIOD and now < oldest_inbox_rel._date + NOTIFICATION_EMAIL_MAX_DELAY): g.log.info('messages still being batched for: %s' % user._fullname) continue messages = [] message_count = 0 more_unread_messages = False non_preview_usernames = set() # Batch messages to email starting with older messages for inbox_rel, message in inbox_items: # Get sender_name, replacing with display_author if it exists g.log.info('user fullname: %s, message fullname: %s' % (user._fullname, message._fullname)) sender_name = get_sender_name(message) if message_count >= MAX_MESSAGES_PER_BATCH: # prevent duplicate usernames for template display non_preview_usernames.add(sender_name) more_unread_messages = True else: link = None parent = None if isinstance(message, Comment): permalink = message.make_permalink_slow(context=1, force_domain=True) if message.parent_id: parent = Comment._byID(message.parent_id, data=True) else: link = Link._byID(message.link_id, data=True) else: permalink = message.make_permalink(force_domain=True) message_type = get_message_type(message, parent, user, link) messages.append({ "author_name": sender_name, "message_type": message_type, "body": message.body, "date": long_datetime(message._date), "permalink": permalink, "id": message._id, "fullname": message._fullname, "subject": getattr(message, 'subject', ''), }) inbox_rel.emailed = True inbox_rel._commit() message_count += 1 mac = generate_notification_email_unsubscribe_token( datum['to'], user_email=user.email, user_password_hash=user.password) base = g.https_endpoint or g.origin unsubscribe_link = base + '/mail/unsubscribe/%s/%s' % (datum['to'], mac) inbox_url = base + '/message/inbox' # unique email_hash for emails, to be used in utm tags id_str = ''.join(str(message['id'] for message in messages)) email_hash = hashlib.sha1(id_str).hexdigest() base_utm_query = { 'utm_name': email_hash, 'utm_source': 'email', 'utm_medium': 'message_notification', } non_preview_usernames_str = generate_non_preview_usernames_str( non_preview_usernames) templateData = { 'messages': messages, 'unsubscribe_link': unsubscribe_link, 'more_unread_messages': more_unread_messages, 'message_count': message_count, 'max_message_display_count': MAX_MESSAGES_PER_BATCH, 'non_preview_usernames_str': non_preview_usernames_str, 'base_url': base, 'base_utm_query': base_utm_query, 'inbox_url': inbox_url, } custom_headers = {'List-Unsubscribe': "<%s>" % unsubscribe_link} g.log.info('sending message for user: %s' % user._fullname) g.email_provider.send_email( to_address=user.email, from_address="Reddit <%s>" % g.notification_email, subject=Email.subjects[Email.Kind.MESSAGE_NOTIFICATION], text=MessageNotificationEmail(**templateData).render( style='email'), html=MessageNotificationEmail(**templateData).render(style='html'), custom_headers=custom_headers, email_type='message_notification_email', ) total_messages_sent += 1 # report the email event to data pipeline g.events.orangered_email_event( request=request, context=c, user=user, messages=messages, email_hash=email_hash, reply_count=message_count, newest_reply_age=newest_inbox_rel._date, oldest_reply_age=oldest_inbox_rel._date, ) g.stats.simple_event('email.message_notification.queued') user_notification_ratelimit.record_usage() timer_end = time.time() g.log.info("Took %s seconds to send orangered emails" % (timer_end - timer_start)) g.log.info("Total number of messages sent: %s" % total_messages_sent) g.log.info("Total count of inbox lookups: %s" % inbox_item_lookup_count)
def message_notification_email(data): """Queues a system email for a new message notification.""" from r2.lib.pages import MessageNotificationEmail timer_start = time.time() MAX_EMAILS_PER_USER = 30 MAX_MESSAGES_PER_BATCH = 5 total_messages_sent = 0 inbox_item_lookup_count = 0 unique_user_list = make_message_dict_unique(data) g.log.info( "there are %s users for this batch of emails" % len(unique_user_list)) for datum in unique_user_list.itervalues(): user = Account._byID36(datum['to'], data=True) g.log.info('user fullname: %s' % user._fullname) # In case a user has enabled the preference while it was enabled for # them, but we've since turned it off. We need to explicitly state the # user because we're not in the context of an HTTP request from them. if not feature.is_enabled('orangereds_as_emails', user=user): g.log.info('feature not enabled for user: %s' % user._fullname) continue # Don't send more than MAX_EMAILS_PER_USER per user per day user_notification_ratelimit = SimpleRateLimit( name="email_message_notification_%s" % user._id36, seconds=int(datetime.timedelta(days=1).total_seconds()), limit=MAX_EMAILS_PER_USER, ) if not user_notification_ratelimit.check(): g.log.info('message blocked at user_notification_ratelimit: %s' % user_notification_ratelimit) continue # Get all new messages that haven't been emailed inbox_items = get_unread_and_unemailed(user) inbox_item_lookup_count += 1 if not inbox_items: g.log.info('no inbox items found for %s' % user._fullname) continue newest_inbox_rel = inbox_items[-1][0] oldest_inbox_rel = inbox_items[0][0] now = datetime.datetime.now(g.tz) start_date = datetime.datetime.strptime(datum['start_date'], "%Y-%m-%d %H:%M:%S").replace(tzinfo=g.tz) # If messages are still being queued within the cooling period or # messages have been queued past the max delay, then keep waiting # a little longer to batch all of the messages up if (start_date != newest_inbox_rel._date and now < newest_inbox_rel._date + NOTIFICATION_EMAIL_COOLING_PERIOD and now < oldest_inbox_rel._date + NOTIFICATION_EMAIL_MAX_DELAY): g.log.info('messages still being batched for: %s' % user._fullname) continue messages = [] message_count = 0 more_unread_messages = False non_preview_usernames = set() # Batch messages to email starting with older messages for inbox_rel, message in inbox_items: # Get sender_name, replacing with display_author if it exists g.log.info('user fullname: %s, message fullname: %s' % ( user._fullname, message._fullname)) sender_name = get_sender_name(message) if message_count >= MAX_MESSAGES_PER_BATCH: # prevent duplicate usernames for template display non_preview_usernames.add(sender_name) more_unread_messages = True else: link = None parent = None if isinstance(message, Comment): permalink = message.make_permalink_slow(context=1, force_domain=True) if message.parent_id: parent = Comment._byID(message.parent_id, data=True) else: link = Link._byID(message.link_id, data=True) else: permalink = message.make_permalink(force_domain=True) message_type = get_message_type(message, parent, user, link) messages.append({ "author_name": sender_name, "message_type": message_type, "body": message.body, "date": long_datetime(message._date), "permalink": permalink, "id": message._id, "fullname": message._fullname, "subject": getattr(message, 'subject', ''), }) inbox_rel.emailed = True inbox_rel._commit() message_count += 1 mac = generate_notification_email_unsubscribe_token( datum['to'], user_email=user.email, user_password_hash=user.password) base = g.https_endpoint or g.origin unsubscribe_link = base + '/mail/unsubscribe/%s/%s' % (datum['to'], mac) inbox_url = base + '/message/inbox' # unique email_hash for emails, to be used in utm tags id_str = ''.join(str(message['id'] for message in messages)) email_hash = hashlib.sha1(id_str).hexdigest() base_utm_query = { 'utm_name': email_hash, 'utm_source': 'email', 'utm_medium':'message_notification', } non_preview_usernames_str = generate_non_preview_usernames_str( non_preview_usernames) templateData = { 'messages': messages, 'unsubscribe_link': unsubscribe_link, 'more_unread_messages': more_unread_messages, 'message_count': message_count, 'max_message_display_count': MAX_MESSAGES_PER_BATCH, 'non_preview_usernames_str': non_preview_usernames_str, 'base_url': base, 'base_utm_query': base_utm_query, 'inbox_url': inbox_url, } custom_headers = { 'List-Unsubscribe': "<%s>" % unsubscribe_link } g.log.info('sending message for user: %s' % user._fullname) g.email_provider.send_email( to_address=user.email, from_address="Reddit <%s>" % g.notification_email, subject=Email.subjects[Email.Kind.MESSAGE_NOTIFICATION], text=MessageNotificationEmail(**templateData).render(style='email'), html=MessageNotificationEmail(**templateData).render(style='html'), custom_headers=custom_headers, email_type='message_notification_email', ) total_messages_sent += 1 # report the email event to data pipeline g.events.orangered_email_event( request=request, context=c, user=user, messages=messages, email_hash=email_hash, reply_count=message_count, newest_reply_age=newest_inbox_rel._date, oldest_reply_age=oldest_inbox_rel._date, ) g.stats.simple_event('email.message_notification.queued') user_notification_ratelimit.record_usage() timer_end = time.time() g.log.info( "Took %s seconds to send orangered emails" % (timer_end - timer_start)) g.log.info("Total number of messages sent: %s" % total_messages_sent) g.log.info("Total count of inbox lookups: %s" % inbox_item_lookup_count)