Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
    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)
Esempio n. 6
0
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},)
Esempio n. 7
0
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]}
Esempio n. 8
0
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)
Esempio n. 10
0
    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)
Esempio n. 12
0
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},)
Esempio n. 13
0
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]}
Esempio n. 14
0
    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)
Esempio n. 15
0
    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)
Esempio n. 16
0
    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)
Esempio n. 17
0
    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)
Esempio n. 18
0
    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)
Esempio n. 19
0
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})
Esempio n. 20
0
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)
Esempio n. 21
0
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()
Esempio n. 22
0
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})
Esempio n. 23
0
    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,
            )
Esempio n. 24
0
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})
Esempio n. 25
0
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})
Esempio n. 26
0
    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)
Esempio n. 27
0
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
        },
    )
Esempio n. 28
0
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)
Esempio n. 29
0
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},
    )
Esempio n. 30
0
    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)
Esempio n. 31
0
    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)