def confirm_email_change(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse user_profile = request.user if user_profile.realm.email_changes_disabled: raise JsonableError(_("Email address changes are disabled in this organization.")) confirmation_key = confirmation_key.lower() obj = EmailChangeConfirmation.objects.confirm(confirmation_key) confirmed = False new_email = old_email = None # type: Text if obj: confirmed = True assert isinstance(obj, EmailChangeStatus) new_email = obj.new_email old_email = obj.old_email do_change_user_email(obj.user_profile, obj.new_email) context = {'realm': obj.realm, 'new_email': new_email, } send_email('zerver/emails/notify_change_in_email', old_email, from_email=settings.DEFAULT_FROM_EMAIL, context=context) ctx = { 'confirmed': confirmed, 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError(_("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {'realm_name': user_profile.realm.name, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_emails=[old_email], from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, language=user_profile.default_language, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def email_on_new_login(sender, user, request, **kwargs): # type: (Any, UserProfile, Any, **Any) -> None # We import here to minimize the dependencies of this module, # since it runs as part of `manage.py` initialization from zerver.context_processors import common_context if not settings.SEND_LOGIN_EMAILS: return if request: # If the user's account was just created, avoid sending an email. if getattr(user, "just_registered", False): return login_time = timezone_now().strftime('%A, %B %d, %Y at %I:%M%p ') + \ timezone_get_current_timezone_name() user_agent = request.META.get('HTTP_USER_AGENT', "").lower() device_browser = get_device_browser(user_agent) device_os = get_device_os(user_agent) device_ip = request.META.get('REMOTE_ADDR') or "Uknown IP address" device_info = {"device_browser": device_browser, "device_os": device_os, "device_ip": device_ip, "login_time": login_time } context = common_context(user) context['device_info'] = device_info context['user'] = user send_email('zerver/emails/notify_new_login', to_user_id=user.id, from_name='Zulip Account Security', from_address=FromAddress.NOREPLY, context=context)
def confirm_email_change(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse user_profile = request.user if user_profile.realm.email_changes_disabled: raise JsonableError(_("Email address changes are disabled in this organization.")) confirmation_key = confirmation_key.lower() try: obj = get_object_from_key(confirmation_key) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) assert isinstance(obj, EmailChangeStatus) new_email = obj.new_email old_email = obj.old_email do_change_user_email(obj.user_profile, obj.new_email) context = {'realm': obj.realm, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_email=old_email, from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def send_mail(self, subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None): # type: (str, str, Dict[str, Any], str, str, str) -> None """ Currently we don't support accounts in multiple subdomains using a single email address. We override this function so that we do not send a reset link to an email address if the reset attempt is done on the subdomain which does not match user.realm.subdomain. Once we start supporting accounts with the same email in multiple subdomains, we may be able to refactor this function. A second reason we override this function is so that we can send the mail through the functions in zerver.lib.send_email, to match how we send all other mail in the codebase. """ user = get_user_profile_by_email(to_email) attempted_subdomain = get_subdomain(getattr(self, 'request')) context['attempted_realm'] = False if not check_subdomain(user.realm.subdomain, attempted_subdomain): context['attempted_realm'] = get_realm(attempted_subdomain) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)
def find_my_team(request): # type: (HttpRequest) -> HttpResponse url = reverse('zerver.views.registration.find_my_team') emails = [] # type: List[Text] if request.method == 'POST': form = FindMyTeamForm(request.POST) if form.is_valid(): emails = form.cleaned_data['emails'] for user_profile in UserProfile.objects.filter(email__in=emails): send_email('zerver/emails/find_team', to_user_id=user_profile.id, context={'user_profile': user_profile}) # Note: Show all the emails in the result otherwise this # feature can be used to ascertain which email addresses # are associated with Zulip. data = urllib.parse.urlencode({'emails': ','.join(emails)}) return redirect(url + "?" + data) else: form = FindMyTeamForm() result = request.GET.get('emails') if result: for email in result.split(','): try: validators.validate_email(email) emails.append(email) except ValidationError: pass return render(request, 'zerver/find_my_team.html', context={'form': form, 'current_url': lambda: url, 'emails': emails},)
def send_registration_completion_email(email, request, realm_creation=False): # type: (str, HttpRequest, bool) -> None """ Send an email with a confirmation link to the provided e-mail so the user can complete their registration. """ prereg_user = create_preregistration_user(email, request, realm_creation) activation_url = Confirmation.objects.get_link_for_object(prereg_user, host=request.get_host()) send_email('zerver/emails/confirm_registration', email, from_email=settings.DEFAULT_FROM_EMAIL, context={'activate_url': activation_url}) if settings.DEVELOPMENT and realm_creation: request.session['confirmation_key'] = {'confirmation_key': activation_url.split('/')[-1]}
def send_registration_completion_email(email, request, realm_creation=False): # type: (str, HttpRequest, bool) -> None """ Send an email with a confirmation link to the provided e-mail so the user can complete their registration. """ prereg_user = create_preregistration_user(email, request, realm_creation) activation_url = create_confirmation_link(prereg_user, request.get_host(), Confirmation.USER_REGISTRATION) send_email('zerver/emails/confirm_registration', to_email=email, from_address=FromAddress.NOREPLY, context={'activate_url': activation_url}) if settings.DEVELOPMENT and realm_creation: request.session['confirmation_key'] = {'confirmation_key': activation_url.split('/')[-1]}
def send(self, users: List[UserProfile]) -> None: """Sends one-use only links for resetting password to target users """ for user_profile in users: context = { 'email': user_profile.email, 'reset_url': generate_password_reset_url(user_profile, default_token_generator), 'realm_uri': user_profile.realm.uri, 'active_account_in_realm': True, } send_email('zerver/emails/password_reset', to_user_id=user_profile.id, from_address=FromAddress.tokenized_no_reply_address(), from_name="Zulip Account Security", context=context)
def handle(self, *args: Any, **options: Any) -> None: if settings.EMAIL_DELIVERER_DISABLED: while True: time.sleep(10*9) with lockfile("/tmp/zulip_email_deliver.lockfile"): while True: email_jobs_to_deliver = ScheduledEmail.objects.filter(scheduled_timestamp__lte=timezone_now()) if email_jobs_to_deliver: for job in email_jobs_to_deliver: try: send_email(**loads(job.data)) job.delete() except EmailNotDeliveredException: logger.warning("%r not delivered" % (job,)) time.sleep(10) else: # Less load on the db during times of activity, and more responsiveness when the load is low time.sleep(2)
def send(self, users, subject_template_name='', email_template_name='', use_https=True, token_generator=default_token_generator, from_email=None, html_email_template_name=None): # type: (List[UserProfile], str, str, bool, PasswordResetTokenGenerator, Optional[Text], Optional[str]) -> None """Sends one-use only links for resetting password to target users """ for user_profile in users: context = { 'email': user_profile.email, 'domain': user_profile.realm.host, 'site_name': "zulipo", 'uid': urlsafe_base64_encode(force_bytes(user_profile.pk)), 'user': user_profile, 'token': token_generator.make_token(user_profile), 'protocol': 'https' if use_https else 'http', } logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,)) send_email('zerver/emails/password_reset', user_profile.email, context=context)
def find_account(request: HttpRequest) -> HttpResponse: url = reverse('zerver.views.registration.find_account') emails = [] # type: List[str] if request.method == 'POST': form = FindMyTeamForm(request.POST) if form.is_valid(): emails = form.cleaned_data['emails'] for user_profile in UserProfile.objects.filter( email__in=emails, is_active=True, is_bot=False, realm__deactivated=False): ctx = { 'full_name': user_profile.full_name, 'email': user_profile.email, 'realm_uri': user_profile.realm.uri, 'realm_name': user_profile.realm.name, } send_email('zerver/emails/find_team', to_user_id=user_profile.id, context=ctx) # Note: Show all the emails in the result otherwise this # feature can be used to ascertain which email addresses # are associated with Zulip. data = urllib.parse.urlencode({'emails': ','.join(emails)}) return redirect(url + "?" + data) else: form = FindMyTeamForm() result = request.GET.get('emails') # The below validation is perhaps unnecessary, in that we # shouldn't get able to get here with an invalid email unless # the user hand-edits the URLs. if result: for email in result.split(','): try: validators.validate_email(email) emails.append(email) except ValidationError: pass return render(request, 'zerver/find_account.html', context={'form': form, 'current_url': lambda: url, 'emails': emails},)
def send_registration_completion_email(email: str, request: HttpRequest, realm_creation: bool=False, streams: Optional[List[Stream]]=None) -> None: """ Send an email with a confirmation link to the provided e-mail so the user can complete their registration. """ prereg_user = create_preregistration_user(email, request, realm_creation) if streams is not None: prereg_user.streams = streams prereg_user.save() confirmation_type = Confirmation.USER_REGISTRATION if realm_creation: confirmation_type = Confirmation.REALM_CREATION activation_url = create_confirmation_link(prereg_user, request.get_host(), confirmation_type) send_email('zerver/emails/confirm_registration', to_email=email, from_address=FromAddress.NOREPLY, context={'activate_url': activation_url}) if settings.DEVELOPMENT and realm_creation: request.session['confirmation_key'] = {'confirmation_key': activation_url.split('/')[-1]}
def handle(self, *args: Any, **options: Any) -> None: if settings.EMAIL_DELIVERER_DISABLED: sleep_forever() while True: email_jobs_to_deliver = ScheduledEmail.objects.filter( scheduled_timestamp__lte=timezone_now()) if email_jobs_to_deliver: for job in email_jobs_to_deliver: data = loads(job.data) handle_send_email_format_changes(data) try: send_email(**data) job.delete() except EmailNotDeliveredException: logger.warning("%r not delivered" % (job,)) time.sleep(10) else: # Less load on the db during times of activity, # and more responsiveness when the load is low time.sleep(2)
def send(self, users: List[UserProfile], subject_template_name: str='', email_template_name: str='', use_https: bool=True, token_generator: PasswordResetTokenGenerator=default_token_generator, from_email: Optional[Text]=None, html_email_template_name: Optional[str]=None) -> None: """Sends one-use only links for resetting password to target users """ for user_profile in users: context = { 'email': user_profile.email, 'domain': user_profile.realm.host, 'site_name': "zulipo", 'uid': urlsafe_base64_encode(force_bytes(user_profile.id)), 'user': user_profile, 'token': token_generator.make_token(user_profile), 'protocol': 'https' if use_https else 'http', } logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,)) send_email('zerver/emails/password_reset', to_user_id=user_profile.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)
def handle(self, *args, **options): # type: (*Any, **Any) -> None # TODO: this only acquires a lock on the system, not on the DB: # be careful not to run this on multiple systems. # In the meantime, we have an option to prevent this job from # running on >1 machine if settings.EMAIL_DELIVERER_DISABLED: return with lockfile("/tmp/zulip_email_deliver.lockfile"): while True: email_jobs_to_deliver = ScheduledJob.objects.filter(type=ScheduledJob.EMAIL, scheduled_timestamp__lte=timezone_now()) if email_jobs_to_deliver: for job in email_jobs_to_deliver: if not send_email(**loads(job.data)): logger.warn("No exception raised, but %r sent as 0 bytes" % (job,)) else: job.delete() time.sleep(10) else: # Less load on the db during times of activity, and more responsiveness when the load is low time.sleep(2)
def save(self, domain_override: Optional[bool]=None, subject_template_name: str='registration/password_reset_subject.txt', email_template_name: str='registration/password_reset_email.html', use_https: bool=False, token_generator: PasswordResetTokenGenerator=default_token_generator, from_email: Optional[str]=None, request: HttpRequest=None, html_email_template_name: Optional[str]=None, extra_email_context: Optional[Dict[str, Any]]=None ) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info("Password reset attempted for %s even though password auth is disabled." % (email,)) return if email_belongs_to_ldap(realm, email): # TODO: Ideally, we'd provide a user-facing error here # about the fact that they aren't allowed to have a # password in the Zulip server and should change it in LDAP. logging.info("Password reset not allowed for user in LDAP domain") return if realm.deactivated: logging.info("Realm is deactivated") return if settings.RATE_LIMITING: try: rate_limit_password_reset_form_by_email(email) except RateLimited: # TODO: Show an informative, user-facing error message. logging.info("Too many password reset attempts for email %s" % (email,)) return user = None # type: Optional[UserProfile] try: user = get_user_by_delivery_email(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, 'realm_name': realm.name, } if user is not None and not user.is_active: context['user_deactivated'] = True user = None if user is not None: context['active_account_in_realm'] = True context['reset_url'] = generate_password_reset_url(user, token_generator) send_email('zerver/emails/password_reset', to_user_ids=[user.id], from_name=FromAddress.security_email_from_name(user_profile=user), from_address=FromAddress.tokenized_no_reply_address(), context=context) else: context['active_account_in_realm'] = False active_accounts_in_other_realms = UserProfile.objects.filter( delivery_email__iexact=email, is_active=True) if active_accounts_in_other_realms: context['active_accounts_in_other_realms'] = active_accounts_in_other_realms language = request.LANGUAGE_CODE send_email('zerver/emails/password_reset', to_emails=[email], from_name=FromAddress.security_email_from_name(language=language), from_address=FromAddress.tokenized_no_reply_address(), language=language, context=context)
def save(self, domain_override: Optional[bool]=None, subject_template_name: str='registration/password_reset_subject.txt', email_template_name: str='registration/password_reset_email.html', use_https: bool=False, token_generator: PasswordResetTokenGenerator=default_token_generator, from_email: Optional[str]=None, request: HttpRequest=None, html_email_template_name: Optional[str]=None, extra_email_context: Optional[Dict[str, Any]]=None ) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info("Password reset attempted for %s even though password auth is disabled." % (email,)) return if realm.deactivated: logging.info("Realm is deactivated") return user = None # type: Optional[UserProfile] try: user = get_active_user(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, } if user is not None: token = token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.id)).decode('ascii') endpoint = reverse('django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) context['no_account_in_realm'] = False context['reset_url'] = "{}{}".format(user.realm.uri, endpoint) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context) else: context['no_account_in_realm'] = True accounts = UserProfile.objects.filter(email__iexact=email) if accounts: context['accounts'] = accounts context['multiple_accounts'] = accounts.count() != 1 send_email('zerver/emails/password_reset', to_email=email, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)
def send_confirm_registration_email(email: str, activation_url: str) -> None: send_email('zerver/emails/confirm_registration', to_emails=[email], from_address=FromAddress.tokenized_no_reply_address(), context={'activate_url': activation_url})
def sponsorship( request: HttpRequest, user: UserProfile, organization_type: str = REQ("organization-type"), website: str = REQ(), description: str = REQ(), ) -> HttpResponse: realm = user.realm requested_by = user.full_name user_role = user.get_role_name() support_url = get_support_url(realm) post_data = request.POST.copy() # We need to do this because the field name in the template # for organization type contains a hyphen and the form expects # an underscore. post_data.update(organization_type=organization_type) form = SponsorshipRequestForm(post_data) if form.is_valid(): with transaction.atomic(): sponsorship_request = ZulipSponsorshipRequest( realm=realm, requested_by=user, org_website=form.cleaned_data["website"], org_description=form.cleaned_data["description"], org_type=form.cleaned_data["organization_type"], ) sponsorship_request.save() org_type = form.cleaned_data["organization_type"] if realm.org_type != org_type: realm.org_type = org_type realm.save(update_fields=["org_type"]) update_sponsorship_status(realm, True, acting_user=user) do_make_user_billing_admin(user) org_type_display_name = get_org_type_display_name(org_type) context = { "requested_by": requested_by, "user_role": user_role, "string_id": realm.string_id, "support_url": support_url, "organization_type": org_type_display_name, "website": website, "description": description, } send_email( "zerver/emails/sponsorship_request", to_emails=[FromAddress.SUPPORT], from_name="Zulip sponsorship", from_address=FromAddress.tokenized_no_reply_address(), reply_to_email=user.delivery_email, context=context, ) return json_success(request) else: messages = [] for error_list in form.errors.get_json_data().values(): for error in error_list: messages.append(error["message"]) message = " ".join(messages) raise BillingError("Form validation error", message=message)
def sponsorship( request: HttpRequest, user: UserProfile, organization_type: str = REQ("organization-type"), website: str = REQ(), description: str = REQ(), ) -> HttpResponse: realm = user.realm requested_by = user.full_name user_role = user.get_role_name() support_realm_uri = get_realm(settings.STAFF_SUBDOMAIN).uri support_url = urljoin( support_realm_uri, urlunsplit(("", "", reverse("support"), urlencode({"q": realm.string_id}), "")), ) post_data = request.POST.copy() # We need to do this because the field name in the template # for organization type contains a hyphen and the form expects # an underscore. post_data.update(organization_type=organization_type) form = SponsorshipRequestForm(post_data) with transaction.atomic(): if form.is_valid(): sponsorship_request = ZulipSponsorshipRequest( realm=realm, requested_by=user, org_website=form.cleaned_data["website"], org_description=form.cleaned_data["description"], org_type=form.cleaned_data["organization_type"], ) sponsorship_request.save() org_type = form.cleaned_data["organization_type"] if realm.org_type != org_type: realm.org_type = org_type realm.save(update_fields=["org_type"]) update_sponsorship_status(realm, True, acting_user=user) do_make_user_billing_admin(user) org_type_display_name = get_org_type_display_name(org_type) context = { "requested_by": requested_by, "user_role": user_role, "string_id": realm.string_id, "support_url": support_url, "organization_type": org_type_display_name, "website": website, "description": description, } send_email( "zerver/emails/sponsorship_request", to_emails=[FromAddress.SUPPORT], from_name="Zulip sponsorship", from_address=FromAddress.tokenized_no_reply_address(), reply_to_email=user.delivery_email, context=context, ) return json_success()
def send_confirm_registration_email(email: str, activation_url: str) -> None: send_email('zerver/emails/confirm_registration', to_email=email, from_address=FromAddress.NOREPLY, context={'activate_url': activation_url})
def save( self, domain_override: Optional[str] = None, subject_template_name: str = "registration/password_reset_subject.txt", email_template_name: str = "registration/password_reset_email.html", use_https: bool = False, token_generator: PasswordResetTokenGenerator = default_token_generator, from_email: Optional[str] = None, request: Optional[HttpRequest] = None, html_email_template_name: Optional[str] = None, extra_email_context: Optional[Dict[str, Any]] = None, ) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] # The form is only used in zerver.views.auth.password_rest, we know that # the request must not be None assert request is not None realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info( "Password reset attempted for %s even though password auth is disabled.", email) return if email_belongs_to_ldap(realm, email): # TODO: Ideally, we'd provide a user-facing error here # about the fact that they aren't allowed to have a # password in the Zulip server and should change it in LDAP. logging.info("Password reset not allowed for user in LDAP domain") return if realm.deactivated: logging.info("Realm is deactivated") return if settings.RATE_LIMITING: try: rate_limit_password_reset_form_by_email(email) rate_limit_request_by_ip(request, domain="sends_email_by_ip") except RateLimited: logging.info( "Too many password reset attempts for email %s from %s", email, request.META["REMOTE_ADDR"], ) # The view will handle the RateLimit exception and render an appropriate page raise user: Optional[UserProfile] = None try: user = get_user_by_delivery_email(email, realm) except UserProfile.DoesNotExist: pass context = { "email": email, "realm_uri": realm.uri, "realm_name": realm.name, } if user is not None and not user.is_active: context["user_deactivated"] = True user = None if user is not None: context["active_account_in_realm"] = True context["reset_url"] = generate_password_reset_url( user, token_generator) queue_soft_reactivation(user.id) send_email( "zerver/emails/password_reset", to_user_ids=[user.id], from_name=FromAddress.security_email_from_name( user_profile=user), from_address=FromAddress.tokenized_no_reply_address(), context=context, realm=realm, request=request, ) else: context["active_account_in_realm"] = False active_accounts_in_other_realms = UserProfile.objects.filter( delivery_email__iexact=email, is_active=True) if active_accounts_in_other_realms: context[ "active_accounts_in_other_realms"] = active_accounts_in_other_realms language = get_language() send_email( "zerver/emails/password_reset", to_emails=[email], from_name=FromAddress.security_email_from_name( language=language), from_address=FromAddress.tokenized_no_reply_address(), language=language, context=context, realm=realm, request=request, )
def send_confirm_registration_email(email: str, activation_url: str) -> None: send_email('zerver/emails/confirm_registration', to_email=email, from_address=FromAddress.tokenized_no_reply_address(), context={'activate_url': activation_url})
def send_confirm_registration_email(email: str, activation_url: str) -> None: send_email('zerver/emails/confirm_registration', to_email=email, from_address=FromAddress.NOREPLY, context={'activate_url': activation_url})
def save( self, domain_override: Optional[bool] = None, subject_template_name: str = 'registration/password_reset_subject.txt', email_template_name: str = 'registration/password_reset_email.html', use_https: bool = False, token_generator: PasswordResetTokenGenerator = default_token_generator, from_email: Optional[str] = None, request: HttpRequest = None, html_email_template_name: Optional[str] = None, extra_email_context: Optional[Dict[str, Any]] = None) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info( "Password reset attempted for %s even though password auth is disabled." % (email, )) return if realm.deactivated: logging.info("Realm is deactivated") return user = None # type: Optional[UserProfile] try: user = get_active_user(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, } if user is not None: token = token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.id)).decode('ascii') endpoint = reverse( 'django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) context['no_account_in_realm'] = False context['reset_url'] = "{}{}".format(user.realm.uri, endpoint) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context) else: context['no_account_in_realm'] = True accounts = UserProfile.objects.filter(email__iexact=email) if accounts: context['accounts'] = accounts context['multiple_accounts'] = accounts.count() != 1 send_email('zerver/emails/password_reset', to_email=email, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)
def find_account( request: HttpRequest, raw_emails: Optional[str] = REQ("emails", default=None) ) -> HttpResponse: from zerver.context_processors import common_context url = reverse("find_account") emails: List[str] = [] if request.method == "POST": form = FindMyTeamForm(request.POST) if form.is_valid(): emails = form.cleaned_data["emails"] for i in range(len(emails)): try: rate_limit_request_by_ip(request, domain="find_account_by_ip") except RateLimited as e: assert e.secs_to_freedom is not None return render( request, "zerver/rate_limit_exceeded.html", context={"retry_after": int(e.secs_to_freedom)}, status=429, ) # Django doesn't support __iexact__in lookup with EmailField, so we have # to use Qs to get around that without needing to do multiple queries. emails_q = Q() for email in emails: emails_q |= Q(delivery_email__iexact=email) for user in UserProfile.objects.filter(emails_q, is_active=True, is_bot=False, realm__deactivated=False): context = common_context(user) context.update(email=user.delivery_email, ) send_email( "zerver/emails/find_team", to_user_ids=[user.id], context=context, from_address=FromAddress.SUPPORT, ) # Note: Show all the emails in the result otherwise this # feature can be used to ascertain which email addresses # are associated with Zulip. data = urllib.parse.urlencode({"emails": ",".join(emails)}) return redirect(add_query_to_redirect_url(url, data)) else: form = FindMyTeamForm() # The below validation is perhaps unnecessary, in that we # shouldn't get able to get here with an invalid email unless # the user hand-edits the URLs. if raw_emails: for email in raw_emails.split(","): try: validators.validate_email(email) emails.append(email) except ValidationError: pass return render( request, "zerver/find_account.html", context={ "form": form, "current_url": lambda: url, "emails": emails }, )
def send_confirm_registration_email(email: str, activation_url: str, language: str, realm: Optional[Realm]=None) -> None: send_email('zerver/emails/confirm_registration', to_emails=[email], from_address=FromAddress.tokenized_no_reply_address(), language=language, context={'activate_url': activation_url}, realm=realm)
def find_account( request: HttpRequest, raw_emails: Optional[str] = REQ("emails", default=None) ) -> HttpResponse: url = reverse("find_account") emails: List[str] = [] if request.method == "POST": form = FindMyTeamForm(request.POST) if form.is_valid(): emails = form.cleaned_data["emails"] for i in range(len(emails)): try: rate_limit_request_by_ip(request, domain="sends_email_by_ip") except RateLimited as e: assert e.secs_to_freedom is not None return render( request, "zerver/rate_limit_exceeded.html", context={"retry_after": int(e.secs_to_freedom)}, status=429, ) # Django doesn't support __iexact__in lookup with EmailField, so we have # to use Qs to get around that without needing to do multiple queries. emails_q = Q() for email in emails: emails_q |= Q(delivery_email__iexact=email) user_profiles = UserProfile.objects.filter( emails_q, is_active=True, is_bot=False, realm__deactivated=False ) # We organize the data in preparation for sending exactly # one outgoing email per provided email address, with each # email listing all of the accounts that email address has # with the current Zulip server. context: Dict[str, Dict[str, Any]] = {} for user in user_profiles: key = user.delivery_email.lower() context.setdefault(key, {}) context[key].setdefault("realms", []) context[key]["realms"].append(user.realm) context[key]["external_host"] = settings.EXTERNAL_HOST # This value will end up being the last user ID among # matching accounts; since it's only used for minor # details like language, that arbitrary choice is OK. context[key]["to_user_id"] = user.id for delivery_email, realm_context in context.items(): realm_context["email"] = delivery_email send_email( "zerver/emails/find_team", to_user_ids=[realm_context["to_user_id"]], context=realm_context, from_address=FromAddress.SUPPORT, request=request, ) # Note: Show all the emails in the result otherwise this # feature can be used to ascertain which email addresses # are associated with Zulip. data = urllib.parse.urlencode({"emails": ",".join(emails)}) return redirect(append_url_query_string(url, data)) else: form = FindMyTeamForm() # The below validation is perhaps unnecessary, in that we # shouldn't get able to get here with an invalid email unless # the user hand-edits the URLs. if raw_emails: for email in raw_emails.split(","): try: validators.validate_email(email) emails.append(email) except ValidationError: pass return render( request, "zerver/find_account.html", context={"form": form, "current_url": lambda: url, "emails": emails}, )
def save(self, domain_override: Optional[bool]=None, subject_template_name: str='registration/password_reset_subject.txt', email_template_name: str='registration/password_reset_email.html', use_https: bool=False, token_generator: PasswordResetTokenGenerator=default_token_generator, from_email: Optional[str]=None, request: HttpRequest=None, html_email_template_name: Optional[str]=None, extra_email_context: Optional[Dict[str, Any]]=None ) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info("Password reset attempted for %s even though password auth is disabled." % (email,)) return if email_belongs_to_ldap(realm, email): # TODO: Ideally, we'd provide a user-facing error here # about the fact that they aren't allowed to have a # password in the Zulip server and should change it in LDAP. logging.info("Password reset not allowed for user in LDAP domain") return if realm.deactivated: logging.info("Realm is deactivated") return user = None # type: Optional[UserProfile] try: user = get_user_by_delivery_email(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, 'realm_name': realm.name, } if user is not None and not user.is_active: context['user_deactivated'] = True user = None if user is not None: context['active_account_in_realm'] = True context['reset_url'] = generate_password_reset_url(user, token_generator) send_email('zerver/emails/password_reset', to_user_ids=[user.id], from_name="Zulip Account Security", from_address=FromAddress.tokenized_no_reply_address(), context=context) else: context['active_account_in_realm'] = False active_accounts_in_other_realms = UserProfile.objects.filter( delivery_email__iexact=email, is_active=True) if active_accounts_in_other_realms: context['active_accounts_in_other_realms'] = active_accounts_in_other_realms send_email('zerver/emails/password_reset', to_emails=[email], from_name="Zulip Account Security", from_address=FromAddress.tokenized_no_reply_address(), language=request.LANGUAGE_CODE, context=context)
def save( self, domain_override=None, # type: Optional[bool] subject_template_name='registration/password_reset_subject.txt', # type: Text email_template_name='registration/password_reset_email.html', # type: Text use_https=False, # type: bool token_generator=default_token_generator, # type: PasswordResetTokenGenerator from_email=None, # type: Optional[Text] request=None, # type: HttpRequest html_email_template_name=None, # type: Optional[Text] extra_email_context=None # type: Optional[Dict[str, Any]] ): # type: (...) -> None """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] subdomain = get_subdomain(request) realm = get_realm(subdomain) if not email_auth_enabled(realm): logging.info( "Password reset attempted for %s even though password auth is disabled." % (email, )) return try: user = get_user_profile_by_email(email) except UserProfile.DoesNotExist: user = None context = { 'email': email, 'realm_uri': realm.uri, 'user': user, } if user is not None and user_matches_subdomain(subdomain, user): token = token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.id)) endpoint = reverse( 'django.contrib.auth.views.password_reset_confirm', kwargs=dict(uidb64=uid, token=token)) context['no_account_in_realm'] = False context['reset_url'] = "{}{}".format(user.realm.uri, endpoint) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context) else: context['no_account_in_realm'] = True if user is not None: context['account_exists_another_realm'] = True else: context['account_exists_another_realm'] = False send_email('zerver/emails/password_reset', to_email=email, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)