Пример #1
0
    def clean_username(self):
        # type: () -> str
        email = self.cleaned_data['username']
        try:
            user_profile = get_user_profile_by_email(email)
        except UserProfile.DoesNotExist:
            return email

        if user_profile.realm.deactivated:
            error_msg = u"""Sorry for the trouble, but %s has been deactivated.

Please contact %s to reactivate this group.""" % (
                user_profile.realm.name,
                FromAddress.SUPPORT)
            raise ValidationError(mark_safe(error_msg))

        if not user_profile.is_active and not user_profile.is_mirror_dummy:
            error_msg = (
                u"Sorry for the trouble, but your account has been deactivated. "
                u"Please contact your organization administrator to reactivate it. "
                u"If you're not sure who that is, try contacting %s.") % (FromAddress.SUPPORT,)
            raise ValidationError(mark_safe(error_msg))

        if not user_matches_subdomain(get_subdomain(self.request), user_profile):
            logging.warning("User %s attempted to password login to wrong subdomain %s" %
                            (user_profile.email, get_subdomain(self.request)))
            raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))
        return email
Пример #2
0
 def test(expected: str, host: str, *, plusport: bool=True,
          external_host: str='example.org',
          realm_hosts: Dict[str, str]={},
          root_aliases: List[str]=[]) -> None:
     with self.settings(EXTERNAL_HOST=external_host,
                        REALM_HOSTS=realm_hosts,
                        ROOT_SUBDOMAIN_ALIASES=root_aliases):
         self.assertEqual(get_subdomain(request_mock(host)), expected)
         if plusport and ':' not in host:
             self.assertEqual(get_subdomain(request_mock(host + ':443')),
                              expected)
Пример #3
0
def get_auth_backends_data(request: HttpRequest) -> Dict[str, Any]:
    """Returns which authentication methods are enabled on the server"""
    subdomain = get_subdomain(request)
    try:
        realm = Realm.objects.get(string_id=subdomain)
    except Realm.DoesNotExist:
        # If not the root subdomain, this is an error
        if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            raise JsonableError(_("Invalid subdomain"))
        # With the root subdomain, it's an error or not depending
        # whether ROOT_DOMAIN_LANDING_PAGE (which indicates whether
        # there are some realms without subdomains on this server)
        # is set.
        if settings.ROOT_DOMAIN_LANDING_PAGE:
            raise JsonableError(_("Subdomain required"))
        else:
            realm = None
    return {
        "password": password_auth_enabled(realm),
        "dev": dev_auth_enabled(realm),
        "email": email_auth_enabled(realm),
        "github": github_auth_enabled(realm),
        "google": google_auth_enabled(realm),
        "ldap": ldap_auth_enabled(realm),
    }
Пример #4
0
def oauth_redirect_to_root(request: HttpRequest, url: str,
                           sso_type: str, is_signup: bool=False) -> HttpResponse:
    main_site_uri = settings.ROOT_DOMAIN_URI + url
    if settings.SOCIAL_AUTH_SUBDOMAIN is not None and sso_type == 'social':
        main_site_uri = (settings.EXTERNAL_URI_SCHEME +
                         settings.SOCIAL_AUTH_SUBDOMAIN +
                         "." +
                         settings.EXTERNAL_HOST) + url

    params = {
        'subdomain': get_subdomain(request),
        'is_signup': '1' if is_signup else '0',
    }

    # mobile_flow_otp is a one-time pad provided by the app that we
    # can use to encrypt the API key when passing back to the app.
    mobile_flow_otp = request.GET.get('mobile_flow_otp')
    if mobile_flow_otp is not None:
        if not is_valid_otp(mobile_flow_otp):
            raise JsonableError(_("Invalid OTP"))
        params['mobile_flow_otp'] = mobile_flow_otp

    next = request.GET.get('next')
    if next:
        params['next'] = next

    return redirect(main_site_uri + '?' + urllib.parse.urlencode(params))
Пример #5
0
def log_into_subdomain(request: HttpRequest, token: Text) -> HttpResponse:
    try:
        data = signing.loads(token, salt=_subdomain_token_salt, max_age=15)
    except signing.SignatureExpired as e:
        logging.warning('Subdomain cookie: {}'.format(e))
        return HttpResponse(status=400)
    except signing.BadSignature:
        logging.warning('Subdomain cookie: Bad signature.')
        return HttpResponse(status=400)

    subdomain = get_subdomain(request)
    if data['subdomain'] != subdomain:
        logging.warning('Login attempt on invalid subdomain')
        return HttpResponse(status=400)

    email_address = data['email']
    full_name = data['name']
    is_signup = data['is_signup']
    if is_signup:
        # If we are signing up, user_profile should be None. In case
        # email_address already exists, user will get an error message.
        user_profile = None
        return_data = {}  # type: Dict[str, Any]
    else:
        # We can be reasonably confident that this subdomain actually
        # has a corresponding realm, since it was referenced in a
        # signed cookie.  But we probably should add some error
        # handling for the case where the realm disappeared in the
        # meantime.
        realm = get_realm(subdomain)
        user_profile, return_data = authenticate_remote_user(realm, email_address)
    invalid_subdomain = bool(return_data.get('invalid_subdomain'))
    return login_or_register_remote_user(request, email_address, user_profile,
                                         full_name, invalid_subdomain=invalid_subdomain,
                                         is_signup=is_signup)
Пример #6
0
def add_api_uri_context(context: Dict[str, Any], request: HttpRequest) -> None:
    context.update(zulip_default_context(request))

    subdomain = get_subdomain(request)
    if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
            or not settings.ROOT_DOMAIN_LANDING_PAGE):
        display_subdomain = subdomain
        html_settings_links = True
    else:
        display_subdomain = 'yourZulipDomain'
        html_settings_links = False

    display_host = Realm.host_for_subdomain(display_subdomain)
    api_url_scheme_relative = display_host + "/api"
    api_url = settings.EXTERNAL_URI_SCHEME + api_url_scheme_relative

    context['external_uri_scheme'] = settings.EXTERNAL_URI_SCHEME
    context['api_url'] = api_url
    context['api_url_scheme_relative'] = api_url_scheme_relative

    context["html_settings_links"] = html_settings_links
    if html_settings_links:
        settings_html = '<a href="/#settings">Zulip settings page</a>'
        subscriptions_html = '<a target="_blank" href="/#streams">streams page</a>'
    else:
        settings_html = 'Zulip settings page'
        subscriptions_html = 'streams page'
    context['settings_html'] = settings_html
    context['subscriptions_html'] = subscriptions_html
Пример #7
0
def json_fetch_api_key(request, user_profile, password=REQ(default='')):
    # type: (HttpRequest, UserProfile, str) -> HttpResponse
    if password_auth_enabled(user_profile.realm):
        if not authenticate(username=user_profile.email, password=password,
                            realm_subdomain=get_subdomain(request)):
            return json_error(_("Your username or password is incorrect."))
    return json_success({"api_key": user_profile.api_key})
Пример #8
0
def validate_api_key(request: HttpRequest, role: Optional[str],
                     api_key: str, is_webhook: bool=False,
                     client_name: Optional[str]=None) -> Union[UserProfile, RemoteZulipServer]:
    # Remove whitespace to protect users from trivial errors.
    api_key = api_key.strip()
    if role is not None:
        role = role.strip()

    if settings.ZILENCER_ENABLED and role is not None and is_remote_server(role):
        try:
            remote_server = get_remote_server_by_uuid(role)
        except RemoteZulipServer.DoesNotExist:
            raise InvalidZulipServerError(role)
        if api_key != remote_server.api_key:
            raise InvalidZulipServerKeyError(role)

        if get_subdomain(request) != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            raise JsonableError(_("Invalid subdomain for push notifications bouncer"))
        request.user = remote_server
        request._email = "zulip-server:" + role
        remote_server.rate_limits = ""
        process_client(request, remote_server, remote_server_request=True)
        return remote_server

    user_profile = access_user_by_api_key(request, api_key, email=role)
    if user_profile.is_incoming_webhook and not is_webhook:
        raise JsonableError(_("This API is not available to incoming webhook bots."))

    request.user = user_profile
    request._email = user_profile.email
    process_client(request, user_profile, client_name=client_name)

    return user_profile
Пример #9
0
def api_dev_fetch_api_key(request: HttpRequest, username: str=REQ()) -> HttpResponse:
    """This function allows logging in without a password on the Zulip
    mobile apps when connecting to a Zulip development environment.  It
    requires DevAuthBackend to be included in settings.AUTHENTICATION_BACKENDS.
    """
    if not dev_auth_enabled() or settings.PRODUCTION:
        return json_error(_("Dev environment not enabled."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(username)

    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)

    return_data = {}  # type: Dict[str, bool]
    user_profile = authenticate(dev_auth_username=username,
                                realm=realm,
                                return_data=return_data)
    if return_data.get("inactive_realm"):
        return json_error(_("Your realm has been deactivated."),
                          data={"reason": "realm deactivated"}, status=403)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"}, status=403)
    if user_profile is None:
        return json_error(_("This user is not registered."),
                          data={"reason": "unregistered"}, status=403)
    do_login(request, user_profile)
    return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
Пример #10
0
def remote_user_sso(request: HttpRequest,
                    mobile_flow_otp: Optional[str]=REQ(default=None)) -> HttpResponse:
    try:
        remote_user = request.META["REMOTE_USER"]
    except KeyError:
        # TODO: Arguably the JsonableError values here should be
        # full-page HTML configuration errors instead.
        raise JsonableError(_("No REMOTE_USER set."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(remote_user_to_email(remote_user))

    # Here we support the mobile flow for REMOTE_USER_BACKEND; we
    # validate the data format and then pass it through to
    # login_or_register_remote_user if appropriate.
    if mobile_flow_otp is not None:
        if not is_valid_otp(mobile_flow_otp):
            raise JsonableError(_("Invalid OTP"))

    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    # Since RemoteUserBackend will return None if Realm is None, we
    # don't need to check whether `get_realm` returned None.
    user_profile = authenticate(remote_user=remote_user, realm=realm)
    return login_or_register_remote_user(request, remote_user, user_profile,
                                         mobile_flow_otp=mobile_flow_otp)
Пример #11
0
def logged_in_and_active(request: HttpRequest) -> bool:
    if not request.user.is_authenticated:
        return False
    if not request.user.is_active:
        return False
    if request.user.realm.deactivated:
        return False
    return user_matches_subdomain(get_subdomain(request), request.user)
Пример #12
0
def api_fetch_api_key(request, username=REQ(), password=REQ()):
    # type: (HttpRequest, str, str) -> HttpResponse
    return_data = {}  # type: Dict[str, bool]
    if username == "google-oauth2-token":
        user_profile = authenticate(google_oauth2_token=password,
                                    realm_subdomain=get_subdomain(request),
                                    return_data=return_data)
    else:
        if not ldap_auth_enabled(realm=get_realm_from_request(request)):
            # In case we don't authenticate against LDAP, check for a valid
            # email. LDAP backend can authenticate against a non-email.
            validate_login_email(username)

        user_profile = authenticate(username=username,
                                    password=password,
                                    realm_subdomain=get_subdomain(request),
                                    return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"}, status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("Your realm has been deactivated."),
                          data={"reason": "realm deactivated"}, status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"}, status=403)
    if user_profile is None:
        if return_data.get("valid_attestation"):
            # We can leak that the user is unregistered iff they present a valid authentication string for the user.
            return json_error(_("This user is not registered; do so from a browser."),
                              data={"reason": "unregistered"}, status=403)
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"}, status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.email

    return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
Пример #13
0
    def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
        try:
            request.get_host()
        except DisallowedHost:
            # If we get a DisallowedHost exception trying to access
            # the host, (1) the request is failed anyway and so the
            # below code will do nothing, and (2) the below will
            # trigger a recursive exception, breaking things, so we
            # just return here.
            return response

        if (not request.path.startswith("/static/") and not request.path.startswith("/api/") and
                not request.path.startswith("/json/")):
            subdomain = get_subdomain(request)
            if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
                realm = get_realm(subdomain)
                if (realm is None):
                    return render(request, "zerver/invalid_realm.html")
        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie.
        """
        try:
            accessed = request.session.accessed
            modified = request.session.modified
        except AttributeError:
            pass
        else:
            if accessed:
                patch_vary_headers(response, ('Cookie',))
            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
                if request.session.get_expire_at_browser_close():
                    max_age = None
                    expires = None
                else:
                    max_age = request.session.get_expiry_age()
                    expires_time = time.time() + max_age
                    expires = cookie_date(expires_time)
                # Save the session data and refresh the client cookie.
                # Skip session save for 500 responses, refs #3881.
                if response.status_code != 500:
                    request.session.save()
                    host = request.get_host().split(':')[0]

                    # The subdomains feature overrides the
                    # SESSION_COOKIE_DOMAIN setting, since the setting
                    # is a fixed value and with subdomains enabled,
                    # the session cookie domain has to vary with the
                    # subdomain.
                    session_cookie_domain = host
                    response.set_cookie(settings.SESSION_COOKIE_NAME,
                                        request.session.session_key, max_age=max_age,
                                        expires=expires, domain=session_cookie_domain,
                                        path=settings.SESSION_COOKIE_PATH,
                                        secure=settings.SESSION_COOKIE_SECURE or None,
                                        httponly=settings.SESSION_COOKIE_HTTPONLY or None)
        return response
Пример #14
0
def create_preregistration_user(email: Text, request: HttpRequest, realm_creation: bool=False,
                                password_required: bool=True) -> HttpResponse:
    realm = None
    if not realm_creation:
        realm = get_realm(get_subdomain(request))
    return PreregistrationUser.objects.create(email=email,
                                              realm_creation=realm_creation,
                                              password_required=password_required,
                                              realm=realm)
Пример #15
0
def maybe_send_to_registration(request: HttpRequest, email: str, full_name: str='',
                               is_signup: bool=False, password_required: bool=True,
                               multiuse_object_key: str='') -> HttpResponse:
    realm = get_realm(get_subdomain(request))
    from_multiuse_invite = False
    multiuse_obj = None
    streams_to_subscribe = None
    invited_as = PreregistrationUser.INVITE_AS['MEMBER']
    if multiuse_object_key:
        from_multiuse_invite = True
        multiuse_obj = Confirmation.objects.get(confirmation_key=multiuse_object_key).content_object
        realm = multiuse_obj.realm
        streams_to_subscribe = multiuse_obj.streams.all()
        invited_as = multiuse_obj.invited_as

    form = HomepageForm({'email': email}, realm=realm, from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # Construct a PreregistrationUser object and send the user over to
        # the confirmation view.
        prereg_user = None
        if settings.ONLY_SSO:
            try:
                prereg_user = PreregistrationUser.objects.filter(
                    email__iexact=email, realm=realm).latest("invited_at")
            except PreregistrationUser.DoesNotExist:
                prereg_user = create_preregistration_user(email, request,
                                                          password_required=password_required)
        else:
            prereg_user = create_preregistration_user(email, request,
                                                      password_required=password_required)

        if multiuse_object_key:
            request.session.modified = True
            if streams_to_subscribe is not None:
                prereg_user.streams.set(streams_to_subscribe)
            prereg_user.invited_as = invited_as
            prereg_user.save()

        confirmation_link = create_confirmation_link(prereg_user, request.get_host(),
                                                     Confirmation.USER_REGISTRATION)
        if is_signup:
            return redirect(confirmation_link)

        context = {'email': email,
                   'continue_link': confirmation_link,
                   'full_name': full_name}
        return render(request,
                      'zerver/confirm_continue_registration.html',
                      context=context)
    else:
        url = reverse('register')
        return render(request,
                      'zerver/accounts_home.html',
                      context={'form': form, 'current_url': lambda: url,
                               'from_multiuse_invite': from_multiuse_invite,
                               'multiuse_object_key': multiuse_object_key},
                      )
Пример #16
0
def maybe_send_to_registration(request: HttpRequest, email: str, full_name: str='',
                               is_signup: bool=False, password_required: bool=True) -> HttpResponse:
    realm = get_realm(get_subdomain(request))
    from_multiuse_invite = False
    multiuse_obj = None
    streams_to_subscribe = None
    multiuse_object_key = request.session.get("multiuse_object_key", None)
    if multiuse_object_key is not None:
        from_multiuse_invite = True
        multiuse_obj = Confirmation.objects.get(confirmation_key=multiuse_object_key).content_object
        realm = multiuse_obj.realm
        streams_to_subscribe = multiuse_obj.streams.all()

    form = HomepageForm({'email': email}, realm=realm, from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # Construct a PreregistrationUser object and send the user over to
        # the confirmation view.
        prereg_user = None
        if settings.ONLY_SSO:
            try:
                prereg_user = PreregistrationUser.objects.filter(
                    email__iexact=email, realm=realm).latest("invited_at")
            except PreregistrationUser.DoesNotExist:
                prereg_user = create_preregistration_user(email, request,
                                                          password_required=password_required)
        else:
            prereg_user = create_preregistration_user(email, request,
                                                      password_required=password_required)

        if multiuse_object_key is not None:
            del request.session["multiuse_object_key"]
            request.session.modified = True
            if streams_to_subscribe is not None:
                prereg_user.streams.set(streams_to_subscribe)

        confirmation_link = "".join((
            create_confirmation_link(prereg_user, request.get_host(), Confirmation.USER_REGISTRATION),
            '?full_name=',
            # urllib does not handle Unicode, so coerece to encoded byte string
            # Explanation: http://stackoverflow.com/a/5605354/90777
            urllib.parse.quote_plus(full_name.encode('utf8'))))
        if is_signup:
            return redirect(confirmation_link)

        context = {'email': email,
                   'continue_link': confirmation_link}
        return render(request,
                      'zerver/confirm_continue_registration.html',
                      context=context)
    else:
        url = reverse('register')
        return render(request,
                      'zerver/accounts_home.html',
                      context={'form': form, 'current_url': lambda: url,
                               'from_multiuse_invite': from_multiuse_invite},
                      )
Пример #17
0
def validate_account_and_subdomain(request: HttpRequest, user_profile: UserProfile) -> None:
    if user_profile.realm.deactivated:
        raise JsonableError(_("This organization has been deactivated"))
    if not user_profile.is_active:
        raise JsonableError(_("Account is deactivated"))

    # Either the subdomain matches, or processing a websockets message
    # in the message_sender worker (which will have already had the
    # subdomain validated), or we're accessing Tornado from and to
    # localhost (aka spoofing a request as the user).
    if (not user_matches_subdomain(get_subdomain(request), user_profile) and
        not (request.method == "SOCKET" and
             request.META['SERVER_NAME'] == "127.0.0.1") and
        not (settings.RUNNING_INSIDE_TORNADO and
             request.META["SERVER_NAME"] == "127.0.0.1" and
             request.META["REMOTE_ADDR"] == "127.0.0.1")):
        logging.warning("User %s (%s) attempted to access API on wrong subdomain (%s)" % (
            user_profile.email, user_profile.realm.subdomain, get_subdomain(request)))
        raise JsonableError(_("Account is not associated with this subdomain"))
Пример #18
0
def json_fetch_api_key(request: HttpRequest, user_profile: UserProfile,
                       password: str=REQ(default='')) -> HttpResponse:
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if password_auth_enabled(user_profile.realm):
        if not authenticate(username=user_profile.email, password=password,
                            realm=realm):
            return json_error(_("Your username or password is incorrect."))

    api_key = get_api_key(user_profile)
    return json_success({"api_key": api_key})
Пример #19
0
def dev_direct_login(request, **kwargs):
    # type: (HttpRequest, **Any) -> HttpResponse
    # This function allows logging in without a password and should only be called in development environments.
    # It may be called if the DevAuthBackend is included in settings.AUTHENTICATION_BACKENDS
    if (not dev_auth_enabled()) or settings.PRODUCTION:
        # This check is probably not required, since authenticate would fail without an enabled DevAuthBackend.
        raise Exception('Direct login not supported.')
    email = request.POST['direct_email']
    user_profile = authenticate(username=email, realm_subdomain=get_subdomain(request))
    if user_profile is None:
        raise Exception("User cannot login")
    do_login(request, user_profile)
    return HttpResponseRedirect(user_profile.realm.uri)
Пример #20
0
def password_reset(request: HttpRequest, **kwargs: Any) -> HttpResponse:
    realm = get_realm(get_subdomain(request))

    if realm is None:
        # If trying to get to password reset on a subdomain that
        # doesn't exist, just go to find_account.
        redirect_url = reverse('zerver.views.registration.find_account')
        return HttpResponseRedirect(redirect_url)

    return django_password_reset(request,
                                 template_name='zerver/reset.html',
                                 password_reset_form=ZulipPasswordResetForm,
                                 post_reset_redirect='/accounts/password/reset/done/')
Пример #21
0
def remote_user_sso(request):
    # type: (HttpRequest) -> HttpResponse
    try:
        remote_user = request.META["REMOTE_USER"]
    except KeyError:
        raise JsonableError(_("No REMOTE_USER set."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(remote_user_to_email(remote_user))

    user_profile = authenticate(remote_user=remote_user, realm_subdomain=get_subdomain(request))
    return login_or_register_remote_user(request, remote_user, user_profile)
Пример #22
0
def dev_direct_login(request: HttpRequest, **kwargs: Any) -> HttpResponse:
    # This function allows logging in without a password and should only be called
    # in development environments.  It may be called if the DevAuthBackend is included
    # in settings.AUTHENTICATION_BACKENDS
    if (not dev_auth_enabled()) or settings.PRODUCTION:
        # This check is probably not required, since authenticate would fail without
        # an enabled DevAuthBackend.
        return HttpResponseRedirect(reverse('dev_not_supported'))
    email = request.POST['direct_email']
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    user_profile = authenticate(dev_auth_username=email, realm=realm)
    if user_profile is None:
        return HttpResponseRedirect(reverse('dev_not_supported'))
    do_login(request, user_profile)
    return HttpResponseRedirect(user_profile.realm.uri)
Пример #23
0
def home(request: HttpRequest) -> HttpResponse:
    if settings.DEVELOPMENT and os.path.exists('var/handlebars-templates/compile.error'):
        response = render(request, 'zerver/handlebars_compilation_failed.html')
        response.status_code = 500
        return response
    if not settings.ROOT_DOMAIN_LANDING_PAGE:
        return home_real(request)

    # If settings.ROOT_DOMAIN_LANDING_PAGE, sends the user the landing
    # page, not the login form, on the root domain

    subdomain = get_subdomain(request)
    if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
        return home_real(request)

    return render(request, 'zerver/hello.html')
Пример #24
0
def oauth_redirect_to_root(request: HttpRequest, url: Text, is_signup: bool=False) -> HttpResponse:
    main_site_uri = settings.ROOT_DOMAIN_URI + url
    params = {
        'subdomain': get_subdomain(request),
        'is_signup': '1' if is_signup else '0',
    }

    # mobile_flow_otp is a one-time pad provided by the app that we
    # can use to encrypt the API key when passing back to the app.
    mobile_flow_otp = request.GET.get('mobile_flow_otp')
    if mobile_flow_otp is not None:
        if not is_valid_otp(mobile_flow_otp):
            raise JsonableError(_("Invalid OTP"))
        params['mobile_flow_otp'] = mobile_flow_otp

    return redirect(main_site_uri + '?' + urllib.parse.urlencode(params))
Пример #25
0
def add_api_uri_context(context: Dict[str, Any], request: HttpRequest) -> None:
    subdomain = get_subdomain(request)
    if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
            or not settings.ROOT_DOMAIN_LANDING_PAGE):
        display_subdomain = subdomain
        html_settings_links = True
    else:
        display_subdomain = 'yourZulipDomain'
        html_settings_links = False

    display_host = Realm.host_for_subdomain(display_subdomain)
    api_url_scheme_relative = display_host + "/api"
    api_url = settings.EXTERNAL_URI_SCHEME + api_url_scheme_relative

    context['api_url'] = api_url
    context['api_url_scheme_relative'] = api_url_scheme_relative
    context["html_settings_links"] = html_settings_links
Пример #26
0
def remote_user_sso(request: HttpRequest) -> HttpResponse:
    try:
        remote_user = request.META["REMOTE_USER"]
    except KeyError:
        raise JsonableError(_("No REMOTE_USER set."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(remote_user_to_email(remote_user))

    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    # Since RemoteUserBackend will return None if Realm is None, we
    # don't need to check whether `get_realm` returned None.
    user_profile = authenticate(remote_user=remote_user, realm=realm)
    return login_or_register_remote_user(request, remote_user, user_profile)
Пример #27
0
    def clean(self):
        # type: () -> Dict[str, Any]
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username is not None and password:
            subdomain = get_subdomain(self.request)
            self.user_cache = authenticate(self.request, username=username, password=password,
                                           realm_subdomain=subdomain)
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
            else:
                self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data
Пример #28
0
def remote_user_jwt(request: HttpRequest) -> HttpResponse:
    subdomain = get_subdomain(request)
    try:
        auth_key = settings.JWT_AUTH_KEYS[subdomain]
    except KeyError:
        raise JsonableError(_("Auth key for this subdomain not found."))

    try:
        json_web_token = request.POST["json_web_token"]
        options = {'verify_signature': True}
        payload = jwt.decode(json_web_token, auth_key, options=options)
    except KeyError:
        raise JsonableError(_("No JSON web token passed in request"))
    except jwt.InvalidTokenError:
        raise JsonableError(_("Bad JSON web token"))

    remote_user = payload.get("user", None)
    if remote_user is None:
        raise JsonableError(_("No user specified in JSON web token claims"))
    email_domain = payload.get('realm', None)
    if email_domain is None:
        raise JsonableError(_("No realm specified in JSON web token claims"))

    email = "%s@%s" % (remote_user, email_domain)

    realm = get_realm(subdomain)
    if realm is None:
        raise JsonableError(_("Wrong subdomain"))

    try:
        # We do all the authentication we need here (otherwise we'd have to
        # duplicate work), but we need to call authenticate with some backend so
        # that the request.backend attribute gets set.
        return_data = {}  # type: Dict[str, bool]
        user_profile = authenticate(username=email,
                                    realm=realm,
                                    return_data=return_data,
                                    use_dummy_backend=True)
    except UserProfile.DoesNotExist:
        user_profile = None

    return login_or_register_remote_user(request, email, user_profile, remote_user)
Пример #29
0
def accounts_home(request: HttpRequest, multiuse_object: Optional[MultiuseInvite]=None) -> HttpResponse:
    realm = get_realm(get_subdomain(request))

    if realm is None:
        return HttpResponseRedirect(reverse('zerver.views.registration.find_account'))
    if realm.deactivated:
        return redirect_to_deactivation_notice()

    from_multiuse_invite = False
    streams_to_subscribe = None

    if multiuse_object:
        realm = multiuse_object.realm
        streams_to_subscribe = multiuse_object.streams.all()
        from_multiuse_invite = True

    if request.method == 'POST':
        form = HomepageForm(request.POST, realm=realm, from_multiuse_invite=from_multiuse_invite)
        if form.is_valid():
            email = form.cleaned_data['email']
            activation_url = prepare_activation_url(email, request, streams=streams_to_subscribe)
            try:
                send_confirm_registration_email(email, activation_url)
            except smtplib.SMTPException as e:
                logging.error('Error in accounts_home: %s' % (str(e),))
                return HttpResponseRedirect("/config-error/smtp")

            return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))

        email = request.POST['email']
        try:
            validate_email_for_realm(realm, email)
        except ValidationError:
            return redirect_to_email_login_url(email)
    else:
        form = HomepageForm(realm=realm)
    return render(request,
                  'zerver/accounts_home.html',
                  context={'form': form, 'current_url': request.get_full_path,
                           'from_multiuse_invite': from_multiuse_invite},
                  )
Пример #30
0
def oauth_redirect_to_root(
        request: HttpRequest,
        url: str,
        sso_type: str,
        is_signup: bool = False,
        extra_url_params: Dict[str, str] = {},
        next: Optional[str] = REQ(default=None),
        multiuse_object_key: str = REQ(default=""),
        mobile_flow_otp: Optional[str] = REQ(default=None),
        desktop_flow_otp: Optional[str] = REQ(default=None),
) -> HttpResponse:
    main_site_uri = settings.ROOT_DOMAIN_URI + url
    if settings.SOCIAL_AUTH_SUBDOMAIN is not None and sso_type == "social":
        main_site_uri = (settings.EXTERNAL_URI_SCHEME +
                         settings.SOCIAL_AUTH_SUBDOMAIN + "." +
                         settings.EXTERNAL_HOST) + url

    params = {
        "subdomain": get_subdomain(request),
        "is_signup": "1" if is_signup else "0",
    }

    params["multiuse_object_key"] = multiuse_object_key

    # mobile_flow_otp is a one-time pad provided by the app that we
    # can use to encrypt the API key when passing back to the app.
    validate_otp_params(mobile_flow_otp, desktop_flow_otp)
    if mobile_flow_otp is not None:
        params["mobile_flow_otp"] = mobile_flow_otp
    if desktop_flow_otp is not None:
        params["desktop_flow_otp"] = desktop_flow_otp

    if next:
        params["next"] = next

    params = {**params, **extra_url_params}

    return redirect(
        append_url_query_string(main_site_uri, urllib.parse.urlencode(params)))
Пример #31
0
    def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
        # Match against ALLOWED_HOSTS, which is rather permissive;
        # failure will raise DisallowedHost, which is a 400.
        request.get_host()

        # This check is important to avoid doing the extra work of
        # `get_realm` (which does a database query that could be
        # problematic for Tornado).  Also the error page below is only
        # appropriate for a page visited in a browser, not the API.
        #
        # API authentication will end up checking for an invalid
        # realm, and throw a JSON-format error if appropriate.
        if request.path.startswith(("/static/", "/api/", "/json/")):
            return None

        subdomain = get_subdomain(request)
        if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            try:
                request.realm = get_realm(subdomain)
            except Realm.DoesNotExist:
                return render(request, "zerver/invalid_realm.html", status=404)
        return None
Пример #32
0
def oauth_redirect_to_root(
        request: HttpRequest,
        url: str,
        sso_type: str,
        is_signup: bool = False,
        extra_url_params: Dict[str, str] = {}) -> HttpResponse:
    main_site_uri = settings.ROOT_DOMAIN_URI + url
    if settings.SOCIAL_AUTH_SUBDOMAIN is not None and sso_type == 'social':
        main_site_uri = (settings.EXTERNAL_URI_SCHEME +
                         settings.SOCIAL_AUTH_SUBDOMAIN + "." +
                         settings.EXTERNAL_HOST) + url

    params = {
        'subdomain': get_subdomain(request),
        'is_signup': '1' if is_signup else '0',
    }

    params['multiuse_object_key'] = request.GET.get('multiuse_object_key', '')

    # mobile_flow_otp is a one-time pad provided by the app that we
    # can use to encrypt the API key when passing back to the app.
    mobile_flow_otp = request.GET.get('mobile_flow_otp')
    desktop_flow_otp = request.GET.get('desktop_flow_otp')

    validate_otp_params(mobile_flow_otp, desktop_flow_otp)
    if mobile_flow_otp is not None:
        params['mobile_flow_otp'] = mobile_flow_otp
    if desktop_flow_otp is not None:
        params['desktop_flow_otp'] = desktop_flow_otp

    next = request.GET.get('next')
    if next:
        params['next'] = next

    params = {**params, **extra_url_params}

    return redirect(
        add_query_to_redirect_url(main_site_uri,
                                  urllib.parse.urlencode(params)))
Пример #33
0
def log_into_subdomain(request: HttpRequest, token: Text) -> HttpResponse:
    try:
        data = signing.loads(token, salt=_subdomain_token_salt, max_age=15)
    except signing.SignatureExpired as e:
        logging.warning('Subdomain cookie: {}'.format(e))
        return HttpResponse(status=400)
    except signing.BadSignature:
        logging.warning('Subdomain cookie: Bad signature.')
        return HttpResponse(status=400)

    subdomain = get_subdomain(request)
    if data['subdomain'] != subdomain:
        logging.warning('Login attempt on invalid subdomain')
        return HttpResponse(status=400)

    email_address = data['email']
    full_name = data['name']
    is_signup = data['is_signup']
    if is_signup:
        # If we are signing up, user_profile should be None. In case
        # email_address already exists, user will get an error message.
        user_profile = None
        return_data = {}  # type: Dict[str, Any]
    else:
        # We can be reasonably confident that this subdomain actually
        # has a corresponding realm, since it was referenced in a
        # signed cookie.  But we probably should add some error
        # handling for the case where the realm disappeared in the
        # meantime.
        realm = get_realm(subdomain)
        user_profile, return_data = authenticate_remote_user(
            realm, email_address)
    invalid_subdomain = bool(return_data.get('invalid_subdomain'))
    return login_or_register_remote_user(request,
                                         email_address,
                                         user_profile,
                                         full_name,
                                         invalid_subdomain=invalid_subdomain,
                                         is_signup=is_signup)
Пример #34
0
def api_dev_fetch_api_key(
    request: HttpRequest, username: str = REQ()) -> HttpResponse:
    """This function allows logging in without a password on the Zulip
    mobile apps when connecting to a Zulip development environment.  It
    requires DevAuthBackend to be included in settings.AUTHENTICATION_BACKENDS.
    """
    if not dev_auth_enabled() or settings.PRODUCTION:
        return json_error(_("Dev environment not enabled."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(username)

    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)

    return_data = {}  # type: Dict[str, bool]
    user_profile = authenticate(dev_auth_username=username,
                                realm=realm,
                                return_data=return_data)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"},
                          status=403)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"},
                          status=403)
    if user_profile is None:
        return json_error(_("This user is not registered."),
                          data={"reason": "unregistered"},
                          status=403)
    do_login(request, user_profile)
    return json_success({
        "api_key": user_profile.api_key,
        "email": user_profile.email
    })
Пример #35
0
def create_preregistration_user(
    email: str,
    request: HttpRequest,
    realm_creation: bool = False,
    password_required: bool = True,
    full_name: Optional[str] = None,
    full_name_validated: bool = False,
) -> HttpResponse:
    realm = None
    if not realm_creation:
        try:
            realm = get_realm(get_subdomain(request))
        except Realm.DoesNotExist:
            pass
    return PreregistrationUser.objects.create(
        email=email,
        realm_creation=realm_creation,
        password_required=password_required,
        realm=realm,
        full_name=full_name,
        full_name_validated=full_name_validated,
    )
Пример #36
0
def validate_api_key(
    request: HttpRequest,
    role: Optional[str],
    api_key: str,
    is_webhook: bool = False,
    client_name: Optional[str] = None
) -> Union[UserProfile, RemoteZulipServer]:
    # Remove whitespace to protect users from trivial errors.
    api_key = api_key.strip()
    if role is not None:
        role = role.strip()

    # If `role` doesn't look like an email, it might be a uuid.
    if settings.ZILENCER_ENABLED and role is not None and '@' not in role:
        try:
            remote_server = get_remote_server_by_uuid(role)
        except RemoteZulipServer.DoesNotExist:
            raise InvalidZulipServerError(role)
        if api_key != remote_server.api_key:
            raise InvalidZulipServerKeyError(role)

        if get_subdomain(request) != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            raise JsonableError(
                _("Invalid subdomain for push notifications bouncer"))
        request.user = remote_server
        remote_server.rate_limits = ""
        # Skip updating UserActivity, since remote_server isn't actually a UserProfile object.
        process_client(request, remote_server, skip_update_user_activity=True)
        return remote_server

    user_profile = access_user_by_api_key(request, api_key, email=role)
    if user_profile.is_incoming_webhook and not is_webhook:
        raise JsonableError(
            _("This API is not available to incoming webhook bots."))

    request.user = user_profile
    process_client(request, user_profile, client_name=client_name)

    return user_profile
Пример #37
0
def remote_user_sso(request: HttpRequest,
                    mobile_flow_otp: Optional[str]=REQ(default=None)) -> HttpResponse:
    try:
        remote_user = request.META["REMOTE_USER"]
    except KeyError:
        # TODO: Arguably the JsonableError values here should be
        # full-page HTML configuration errors instead.
        raise JsonableError(_("No REMOTE_USER set."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(remote_user_to_email(remote_user))

    # Here we support the mobile flow for REMOTE_USER_BACKEND; we
    # validate the data format and then pass it through to
    # login_or_register_remote_user if appropriate.
    if mobile_flow_otp is not None:
        if not is_valid_otp(mobile_flow_otp):
            raise JsonableError(_("Invalid OTP"))

    subdomain = get_subdomain(request)
    try:
        realm = get_realm(subdomain)  # type: Optional[Realm]
    except Realm.DoesNotExist:
        realm = None
    # Since RemoteUserBackend will return None if Realm is None, we
    # don't need to check whether `realm` is None.
    return_data = {}  # type: Dict[str, Any]
    user_profile = authenticate(remote_user=remote_user, realm=realm,
                                return_data=return_data)

    redirect_to = request.GET.get('next', '')

    return login_or_register_remote_user(request, remote_user, user_profile,
                                         invalid_subdomain = bool(return_data.get("invalid_subdomain")),
                                         mobile_flow_otp=mobile_flow_otp,
                                         redirect_to=redirect_to)
Пример #38
0
def add_api_uri_context(context, request):
    # type: (Dict[str, Any], HttpRequest) -> None
    subdomain = get_subdomain(request)
    if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
            or not settings.ROOT_DOMAIN_LANDING_PAGE):
        display_subdomain = subdomain
        html_settings_links = True
    else:
        display_subdomain = 'yourZulipDomain'
        html_settings_links = False
    if display_subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
        external_api_path_subdomain = '%s.%s' % (display_subdomain,
                                                 settings.EXTERNAL_API_PATH)
    else:
        external_api_path_subdomain = settings.EXTERNAL_API_PATH

    external_api_uri_subdomain = '%s%s' % (settings.EXTERNAL_URI_SCHEME,
                                           external_api_path_subdomain)

    context['external_api_path_subdomain'] = external_api_path_subdomain
    context['external_api_uri_subdomain'] = external_api_uri_subdomain
    context["html_settings_links"] = html_settings_links
Пример #39
0
def api_fetch_api_key(request: HttpRequest, username: str=REQ(), password: str=REQ()) -> HttpResponse:
    return_data = {}  # type: Dict[str, bool]
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if not ldap_auth_enabled(realm=get_realm_from_request(request)):
        # In case we don't authenticate against LDAP, check for a valid
        # email. LDAP backend can authenticate against a non-email.
        validate_login_email(username)
    user_profile = authenticate(request=request,
                                username=username,
                                password=password,
                                realm=realm,
                                return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"}, status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"}, status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"}, status=403)
    if user_profile is None:
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"}, status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.delivery_email

    api_key = get_api_key(user_profile)
    return json_success({"api_key": api_key, "email": user_profile.delivery_email})
Пример #40
0
def oauth_redirect_to_root(request: HttpRequest,
                           url: str,
                           is_signup: bool = False) -> HttpResponse:
    main_site_uri = settings.ROOT_DOMAIN_URI + url
    params = {
        'subdomain': get_subdomain(request),
        'is_signup': '1' if is_signup else '0',
    }

    # mobile_flow_otp is a one-time pad provided by the app that we
    # can use to encrypt the API key when passing back to the app.
    mobile_flow_otp = request.GET.get('mobile_flow_otp')
    if mobile_flow_otp is not None:
        if not is_valid_otp(mobile_flow_otp):
            raise JsonableError(_("Invalid OTP"))
        params['mobile_flow_otp'] = mobile_flow_otp

    next = request.GET.get('next')
    if next:
        params['next'] = next

    return redirect(main_site_uri + '?' + urllib.parse.urlencode(params))
Пример #41
0
    def clean(self):
        # type: () -> Dict[str, Any]
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username is not None and password:
            subdomain = get_subdomain(self.request)
            realm = get_realm(subdomain)
            return_data = {}  # type: Dict[str, Any]
            self.user_cache = authenticate(self.request, username=username, password=password,
                                           realm=realm, return_data=return_data)

            if return_data.get("inactive_realm"):
                raise AssertionError("Programming error: inactive realm in authentication form")

            if return_data.get("inactive_user") and not return_data.get("is_mirror_dummy"):
                # We exclude mirror dummy accounts here. They should be treated as the
                # user never having had an account, so we let them fall through to the
                # normal invalid_login case below.
                error_msg = (
                    u"Your account is no longer active. "
                    u"Please contact your organization administrator to reactivate it.")
                raise ValidationError(mark_safe(error_msg))

            if return_data.get("invalid_subdomain"):
                logging.warning("User %s attempted to password login to wrong subdomain %s" %
                                (username, subdomain))
                raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))

            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )

            self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data
Пример #42
0
def validate_api_key(request,
                     role,
                     api_key,
                     is_webhook=False,
                     client_name=None):
    # type: (HttpRequest, Optional[Text], Text, bool, Optional[Text]) -> Union[UserProfile, RemoteZulipServer]
    # Remove whitespace to protect users from trivial errors.
    api_key = api_key.strip()
    if role is not None:
        role = role.strip()

    if settings.ZILENCER_ENABLED and role is not None and is_remote_server(
            role):
        try:
            remote_server = get_remote_server_by_uuid(role)
        except RemoteZulipServer.DoesNotExist:
            raise InvalidZulipServerError(role)
        if api_key != remote_server.api_key:
            raise InvalidZulipServerKeyError(role)

        if get_subdomain(request) != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            raise JsonableError(
                _("Invalid subdomain for push notifications bouncer"))
        request.user = remote_server
        request._email = "zulip-server:" + role
        remote_server.rate_limits = ""
        process_client(request, remote_server, remote_server_request=True)
        return remote_server

    user_profile = access_user_by_api_key(request, api_key, email=role)
    if user_profile.is_incoming_webhook and not is_webhook:
        raise JsonableError(
            _("This API is not available to incoming webhook bots."))

    request.user = user_profile
    request._email = user_profile.email
    process_client(request, user_profile, client_name=client_name)

    return user_profile
Пример #43
0
def remote_user_sso(request: HttpRequest,
                    mobile_flow_otp: Optional[str]=REQ(default=None),
                    desktop_flow_otp: Optional[str]=REQ(default=None)) -> HttpResponse:
    subdomain = get_subdomain(request)
    try:
        realm = get_realm(subdomain)  # type: Optional[Realm]
    except Realm.DoesNotExist:
        realm = None

    if not auth_enabled_helper([ZulipRemoteUserBackend.auth_backend_name], realm):
        return redirect_to_config_error("remoteuser/backend_disabled")

    try:
        remote_user = request.META["REMOTE_USER"]
    except KeyError:
        return redirect_to_config_error("remoteuser/remote_user_header_missing")

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(remote_user_to_email(remote_user))

    # Here we support the mobile and desktop flow for REMOTE_USER_BACKEND; we
    # validate the data format and then pass it through to
    # login_or_register_remote_user if appropriate.
    validate_otp_params(mobile_flow_otp, desktop_flow_otp)

    if realm is None:
        user_profile = None
    else:
        user_profile = authenticate(remote_user=remote_user, realm=realm)

    redirect_to = request.GET.get('next', '')
    email = remote_user_to_email(remote_user)
    return login_or_register_remote_user(request, email, user_profile,
                                         mobile_flow_otp=mobile_flow_otp,
                                         desktop_flow_otp=desktop_flow_otp,
                                         redirect_to=redirect_to)
Пример #44
0
def log_into_subdomain(request: HttpRequest, token: str) -> HttpResponse:
    """Given a valid authentication token (generated by
    redirect_and_log_into_subdomain called on auth.zulip.example.com),
    call login_or_register_remote_user, passing all the authentication
    result data that has been stored in Redis, associated with this token.
    """
    # The tokens are intended to have the same format as API keys.
    if not has_api_key_format(token):
        logging.warning("log_into_subdomain: Malformed token given: %s", token)
        return HttpResponse(status=400)

    try:
        result = ExternalAuthResult(login_token=token)
    except ExternalAuthResult.InvalidTokenError:
        logging.warning("log_into_subdomain: Invalid token given: %s", token)
        return render(request, "zerver/log_into_subdomain_token_invalid.html", status=400)

    subdomain = get_subdomain(request)
    if result.data_dict["subdomain"] != subdomain:
        raise JsonableError(_("Invalid subdomain"))

    return login_or_register_remote_user(request, result)
Пример #45
0
def accounts_home(request, multiuse_object=None):
    # type: (HttpRequest, Optional[MultiuseInvite]) -> HttpResponse
    realm = get_realm(get_subdomain(request))
    if realm and realm.deactivated:
        return redirect_to_deactivation_notice()

    from_multiuse_invite = False
    streams_to_subscribe = None

    if multiuse_object:
        realm = multiuse_object.realm
        streams_to_subscribe = multiuse_object.streams.all()
        from_multiuse_invite = True

    if request.method == 'POST':
        form = HomepageForm(request.POST, realm=realm, from_multiuse_invite=from_multiuse_invite)
        if form.is_valid():
            email = form.cleaned_data['email']
            try:
                send_registration_completion_email(email, request, streams=streams_to_subscribe)
            except smtplib.SMTPException as e:
                logging.error('Error in accounts_home: %s' % (str(e),))
                return HttpResponseRedirect("/config-error/smtp")

            return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))

        email = request.POST['email']
        try:
            validate_email_for_realm(realm, email)
        except ValidationError:
            return redirect_to_email_login_url(email)
    else:
        form = HomepageForm(realm=realm)
    return render(request,
                  'zerver/accounts_home.html',
                  context={'form': form, 'current_url': request.get_full_path,
                           'from_multiuse_invite': from_multiuse_invite},
                  )
Пример #46
0
    def clean(self) -> Dict[str, Any]:
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username is not None and password:
            subdomain = get_subdomain(self.request)
            try:
                realm = get_realm(subdomain)  # type: Optional[Realm]
            except Realm.DoesNotExist:
                realm = None
            return_data = {}  # type: Dict[str, Any]
            self.user_cache = authenticate(self.request, username=username, password=password,
                                           realm=realm, return_data=return_data)

            if return_data.get("inactive_realm"):
                raise AssertionError("Programming error: inactive realm in authentication form")

            if return_data.get("inactive_user") and not return_data.get("is_mirror_dummy"):
                # We exclude mirror dummy accounts here. They should be treated as the
                # user never having had an account, so we let them fall through to the
                # normal invalid_login case below.
                raise ValidationError(mark_safe(DEACTIVATED_ACCOUNT_ERROR))

            if return_data.get("invalid_subdomain"):
                logging.warning("User %s attempted to password login to wrong subdomain %s" %
                                (username, subdomain))
                raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))

            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )

            self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data
Пример #47
0
    def clean(self) -> Dict[str, Any]:
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username is not None and password:
            subdomain = get_subdomain(self.request)
            realm = get_realm(subdomain)
            return_data = {}  # type: Dict[str, Any]
            self.user_cache = authenticate(self.request, username=username, password=password,
                                           realm=realm, return_data=return_data)

            if return_data.get("inactive_realm"):
                raise AssertionError("Programming error: inactive realm in authentication form")

            if return_data.get("inactive_user") and not return_data.get("is_mirror_dummy"):
                # We exclude mirror dummy accounts here. They should be treated as the
                # user never having had an account, so we let them fall through to the
                # normal invalid_login case below.
                error_msg = (
                    u"Your account is no longer active. "
                    u"Please contact your organization administrator to reactivate it.")
                raise ValidationError(mark_safe(error_msg))

            if return_data.get("invalid_subdomain"):
                logging.warning("User %s attempted to password login to wrong subdomain %s" %
                                (username, subdomain))
                raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))

            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )

            self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data
Пример #48
0
    def process_response(self, request: HttpRequest,
                         response: StreamingHttpResponse) -> StreamingHttpResponse:
        if getattr(response, "asynchronous", False):
            # This special Tornado "asynchronous" response is
            # discarded after going through this code path as Tornado
            # intends to block, so we stop here to avoid unnecessary work.
            return response

        # The reverse proxy might have sent us the real external IP
        remote_ip = request.META.get('HTTP_X_REAL_IP')
        if remote_ip is None:
            remote_ip = request.META['REMOTE_ADDR']

        # Get the requestor's identifier and client, if available.
        try:
            requestor_for_logs = request._requestor_for_logs
        except Exception:
            if hasattr(request, 'user') and hasattr(request.user, 'format_requestor_for_logs'):
                requestor_for_logs = request.user.format_requestor_for_logs()
            else:
                requestor_for_logs = "unauth@%s" % (get_subdomain(request) or 'root',)
        try:
            client = request.client.name
        except Exception:
            client = "?"

        if response.streaming:
            content_iter = response.streaming_content
            content = None
        else:
            content = response.content
            content_iter = None

        write_log_line(request._log_data, request.path, request.method,
                       remote_ip, requestor_for_logs, client, status_code=response.status_code,
                       error_content=content, error_content_iter=content_iter)
        return response
Пример #49
0
def get_auth_backends_data(request: HttpRequest) -> Dict[str, Any]:
    """Returns which authentication methods are enabled on the server"""
    subdomain = get_subdomain(request)
    try:
        realm = Realm.objects.get(string_id=subdomain)
    except Realm.DoesNotExist:
        # If not the root subdomain, this is an error
        if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
            raise JsonableError(_("Invalid subdomain"))
        # With the root subdomain, it's an error or not depending
        # whether ROOT_DOMAIN_LANDING_PAGE (which indicates whether
        # there are some realms without subdomains on this server)
        # is set.
        if settings.ROOT_DOMAIN_LANDING_PAGE:
            raise JsonableError(_("Subdomain required"))
        else:
            realm = None
    result = {
        "password": password_auth_enabled(realm),
    }
    for auth_backend_name in AUTH_BACKEND_NAME_MAP:
        key = auth_backend_name.lower()
        result[key] = auth_enabled_helper([auth_backend_name], realm)
    return result
Пример #50
0
    def __init__(self, obj: UserProfile, request: Optional[HttpRequest] = None) -> None:
        # We keep the function signature from the superclass, but this actually
        # shouldn't be called with request being None.
        assert request is not None

        # self.obj is populated appropriately by django-scim2 views with
        # an instance of UserProfile - either fetched from the database
        # or constructed via UserProfile() if the request currently being
        # handled is a User creation request (POST).
        self.obj: UserProfile

        super().__init__(obj, request)
        self.subdomain = get_subdomain(request)
        self.config = settings.SCIM_CONFIG[self.subdomain]

        # These attributes are custom to this class and will be
        # populated with values in handle_replace and similar methods
        # in response to a request for the the corresponding
        # UserProfile fields to change. The .save() method inspects
        # these fields an executes the requested changes.
        self._email_new_value: Optional[str] = None
        self._is_active_new_value: Optional[bool] = None
        self._full_name_new_value: Optional[str] = None
        self._password_set_to: Optional[str] = None
Пример #51
0
def get_auth_backends_data(request):
    # type: (HttpRequest) -> Dict[str, Any]
    """Returns which authentication methods are enabled on the server"""
    subdomain = get_subdomain(request)
    try:
        realm = Realm.objects.get(string_id=subdomain)
    except Realm.DoesNotExist:
        # If not the root subdomain, this is an error
        if subdomain != "":
            raise JsonableError(_("Invalid subdomain"))
        # With the root subdomain, it's an error or not depending
        # whether ROOT_DOMAIN_LANDING_PAGE (which indicates whether
        # there are some realms without subdomains on this server)
        # is set.
        if settings.ROOT_DOMAIN_LANDING_PAGE:
            raise JsonableError(_("Subdomain required"))
        else:
            realm = None
    return {
        "password": password_auth_enabled(realm),
        "dev": dev_auth_enabled(realm),
        "github": github_auth_enabled(realm),
        "google": google_auth_enabled(realm)
    }
Пример #52
0
def maybe_send_to_registration(
    request: HttpRequest,
    email: str,
    full_name: str = "",
    mobile_flow_otp: Optional[str] = None,
    desktop_flow_otp: Optional[str] = None,
    is_signup: bool = False,
    password_required: bool = True,
    multiuse_object_key: str = "",
    full_name_validated: bool = False,
) -> HttpResponse:
    """Given a successful authentication for an email address (i.e. we've
    confirmed the user controls the email address) that does not
    currently have a Zulip account in the target realm, send them to
    the registration flow or the "continue to registration" flow,
    depending on is_signup, whether the email address can join the
    organization (checked in HomepageForm), and similar details.
    """

    # In the desktop and mobile registration flows, the sign up
    # happens in the browser so the user can use their
    # already-logged-in social accounts.  Then at the end, with the
    # user account created, we pass the appropriate data to the app
    # via e.g. a `zulip://` redirect.  We store the OTP keys for the
    # mobile/desktop flow in the session with 1-hour expiry, because
    # we want this configuration of having a successful authentication
    # result in being logged into the app to persist if the user makes
    # mistakes while trying to authenticate (E.g. clicks the wrong
    # Google account, hits back, etc.) during a given browser session,
    # rather than just logging into the webapp in the target browser.
    #
    # We can't use our usual pre-account-creation state storage
    # approach of putting something in PreregistrationUser, because
    # that would apply to future registration attempts on other
    # devices, e.g. just creating an account on the web on their laptop.
    assert not (mobile_flow_otp and desktop_flow_otp)
    if mobile_flow_otp:
        set_expirable_session_var(
            request.session, "registration_mobile_flow_otp", mobile_flow_otp, expiry_seconds=3600
        )
    elif desktop_flow_otp:
        set_expirable_session_var(
            request.session, "registration_desktop_flow_otp", desktop_flow_otp, expiry_seconds=3600
        )

    if multiuse_object_key:
        from_multiuse_invite = True
        multiuse_obj = Confirmation.objects.get(confirmation_key=multiuse_object_key).content_object
        realm = multiuse_obj.realm
        invited_as = multiuse_obj.invited_as
    else:
        from_multiuse_invite = False
        multiuse_obj = None
        try:
            realm = get_realm(get_subdomain(request))
        except Realm.DoesNotExist:
            realm = None
        invited_as = PreregistrationUser.INVITE_AS["MEMBER"]

    form = HomepageForm({"email": email}, realm=realm, from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # If the email address is allowed to sign up for an account in
        # this organization, construct a PreregistrationUser and
        # Confirmation objects, and then send the user to account
        # creation or confirm-continue-registration depending on
        # is_signup.
        try:
            prereg_user = filter_to_valid_prereg_users(
                PreregistrationUser.objects.filter(email__iexact=email, realm=realm)
            ).latest("invited_at")

            # password_required and full_name data passed here as argument should take precedence
            # over the defaults with which the existing PreregistrationUser that we've just fetched
            # was created.
            prereg_user.password_required = password_required
            update_fields = ["password_required"]
            if full_name:
                prereg_user.full_name = full_name
                prereg_user.full_name_validated = full_name_validated
                update_fields.extend(["full_name", "full_name_validated"])
            prereg_user.save(update_fields=update_fields)
        except PreregistrationUser.DoesNotExist:
            prereg_user = create_preregistration_user(
                email,
                request,
                password_required=password_required,
                full_name=full_name,
                full_name_validated=full_name_validated,
            )

        if multiuse_obj is not None:
            request.session.modified = True
            streams_to_subscribe = list(multiuse_obj.streams.all())
            prereg_user.streams.set(streams_to_subscribe)
            prereg_user.invited_as = invited_as
            prereg_user.save()

        confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION)
        if is_signup:
            return redirect(confirmation_link)

        context = {"email": email, "continue_link": confirmation_link, "full_name": full_name}
        return render(request, "zerver/confirm_continue_registration.html", context=context)

    # This email address it not allowed to join this organization, so
    # just send the user back to the registration page.
    url = reverse("register")
    context = login_context(request)
    extra_context: Mapping[str, Any] = {
        "form": form,
        "current_url": lambda: url,
        "from_multiuse_invite": from_multiuse_invite,
        "multiuse_object_key": multiuse_object_key,
        "mobile_flow_otp": mobile_flow_otp,
        "desktop_flow_otp": desktop_flow_otp,
    }
    context.update(extra_context)
    return render(request, "zerver/accounts_home.html", context=context)
Пример #53
0
def maybe_send_to_registration(
        request: HttpRequest,
        email: str,
        full_name: str = '',
        is_signup: bool = False,
        password_required: bool = True,
        multiuse_object_key: str = '',
        full_name_validated: bool = False) -> HttpResponse:
    """Given a successful authentication for an email address (i.e. we've
    confirmed the user controls the email address) that does not
    currently have a Zulip account in the target realm, send them to
    the registration flow or the "continue to registration" flow,
    depending on is_signup, whether the email address can join the
    organization (checked in HomepageForm), and similar details.
    """
    if multiuse_object_key:
        from_multiuse_invite = True
        multiuse_obj = Confirmation.objects.get(
            confirmation_key=multiuse_object_key).content_object
        realm = multiuse_obj.realm
        invited_as = multiuse_obj.invited_as
    else:
        from_multiuse_invite = False
        multiuse_obj = None
        try:
            realm = get_realm(get_subdomain(request))
        except Realm.DoesNotExist:
            realm = None
        invited_as = PreregistrationUser.INVITE_AS['MEMBER']

    form = HomepageForm({'email': email},
                        realm=realm,
                        from_multiuse_invite=from_multiuse_invite)
    if form.is_valid():
        # If the email address is allowed to sign up for an account in
        # this organization, construct a PreregistrationUser and
        # Confirmation objects, and then send the user to account
        # creation or confirm-continue-registration depending on
        # is_signup.
        try:
            prereg_user = PreregistrationUser.objects.filter(
                email__iexact=email, realm=realm).latest("invited_at")

            # password_required and full_name data passed here as argument should take precedence
            # over the defaults with which the existing PreregistrationUser that we've just fetched
            # was created.
            prereg_user.password_required = password_required
            update_fields = ["password_required"]
            if full_name:
                prereg_user.full_name = full_name
                prereg_user.full_name_validated = full_name_validated
                update_fields.extend(["full_name", "full_name_validated"])
            prereg_user.save(update_fields=update_fields)
        except PreregistrationUser.DoesNotExist:
            prereg_user = create_preregistration_user(
                email,
                request,
                password_required=password_required,
                full_name=full_name,
                full_name_validated=full_name_validated)

        if multiuse_obj is not None:
            request.session.modified = True
            streams_to_subscribe = list(multiuse_obj.streams.all())
            prereg_user.streams.set(streams_to_subscribe)
            prereg_user.invited_as = invited_as
            prereg_user.save()

        # We want to create a confirmation link to create an account
        # in the current realm, i.e. one with a hostname of
        # realm.host.  For the Apache REMOTE_USER_SSO auth code path,
        # this is preferable over realm.get_host() because the latter
        # contains the port number of the Apache instance and we want
        # to send the user back to nginx.  But if we're in the realm
        # creation code path, there might not be a realm yet, so we
        # have to use request.get_host().
        if realm is not None:
            host = realm.host
        else:
            host = request.get_host()
        confirmation_link = create_confirmation_link(
            prereg_user, host, Confirmation.USER_REGISTRATION)
        if is_signup:
            return redirect(confirmation_link)

        context = {
            'email': email,
            'continue_link': confirmation_link,
            'full_name': full_name
        }
        return render(request,
                      'zerver/confirm_continue_registration.html',
                      context=context)

    # This email address it not allowed to join this organization, so
    # just send the user back to the registration page.
    url = reverse('register')
    context = login_context(request)
    extra_context = {
        'form': form,
        'current_url': lambda: url,
        'from_multiuse_invite': from_multiuse_invite,
        'multiuse_object_key': multiuse_object_key
    }  # type: Mapping[str, Any]
    context.update(extra_context)
    return render(request, 'zerver/accounts_home.html', context=context)
Пример #54
0
def log_into_subdomain(request: HttpRequest, token: str) -> HttpResponse:
    """Given a valid authentication token (generated by
    redirect_and_log_into_subdomain called on auth.zulip.example.com),
    call login_or_register_remote_user, passing all the authentication
    result data that has been stored in redis, associated with this token.
    Obligatory fields for the data are 'subdomain' and 'email', because this endpoint
    needs to know which user and realm to log into. Others are optional and only used
    if the user account still needs to be made and they're passed as argument to the
    register_remote_user function.
    """
    if not has_api_key_format(
            token
    ):  # The tokens are intended to have the same format as API keys.
        logging.warning("log_into_subdomain: Malformed token given: %s" %
                        (token, ))
        return HttpResponse(status=400)

    data = get_login_data(token)
    if data is None:
        logging.warning("log_into_subdomain: Invalid token given: %s" %
                        (token, ))
        return HttpResponse(status=400)

    # We extract fields provided by the caller via the data object.
    # The only fields that are required are email and subdomain (if we
    # are simply doing login); more fields are expected if this is a
    # new account registration flow or we're going to a specific
    # narrow after login.
    subdomain = get_subdomain(request)
    if data['subdomain'] != subdomain:
        raise JsonableError(_("Invalid subdomain"))
    email_address = data['email']

    full_name = data.get('name', '')
    is_signup = data.get('is_signup', False)
    redirect_to = data.get('next', '')
    full_name_validated = data.get('full_name_validated', False)
    multiuse_object_key = data.get('multiuse_object_key', '')

    # We cannot pass the actual authenticated user_profile object that
    # was fetched by the original authentication backend and passed
    # into redirect_and_log_into_subdomain through a signed URL token,
    # so we need to re-fetch it from the database.
    if is_signup:
        # If we are creating a new user account, user_profile will
        # always have been None, so we set that here.  In the event
        # that a user account with this email was somehow created in a
        # race, the eventual registration code will catch that and
        # throw an error, so we don't need to check for that here.
        user_profile = None
    else:
        # We're just trying to login.  We can be reasonably confident
        # that this subdomain actually has a corresponding active
        # realm, since the signed cookie proves there was one very
        # recently.  But as part of fetching the UserProfile object
        # for the target user, we use DummyAuthBackend, which
        # conveniently re-validates that the realm and user account
        # were not deactivated in the meantime.

        # Note: Ideally, we'd have a nice user-facing error message
        # for the case where this auth fails (because e.g. the realm
        # or user was deactivated since the signed cookie was
        # generated < 15 seconds ago), but the authentication result
        # is correct in those cases and such a race would be very
        # rare, so a nice error message is low priority.
        realm = get_realm(subdomain)
        user_profile = authenticate_remote_user(realm, email_address)

    return login_or_register_remote_user(
        request,
        email_address,
        user_profile,
        full_name,
        is_signup=is_signup,
        redirect_to=redirect_to,
        multiuse_object_key=multiuse_object_key,
        full_name_validated=full_name_validated)
Пример #55
0
    def save(self,
             domain_override: Optional[bool] = None,
             subject_template_name:
             Text = 'registration/password_reset_subject.txt',
             email_template_name:
             Text = 'registration/password_reset_email.html',
             use_https: bool = False,
             token_generator:
             PasswordResetTokenGenerator = default_token_generator,
             from_email: Optional[Text] = None,
             request: HttpRequest = None,
             html_email_template_name: Optional[Text] = 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

        user = None  # type: Optional[UserProfile]
        try:
            user = get_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)
Пример #56
0
    def process_response(self, request: HttpRequest,
                         response: HttpResponse) -> HttpResponse:
        try:
            request.get_host()
        except DisallowedHost:
            # If we get a DisallowedHost exception trying to access
            # the host, (1) the request is failed anyway and so the
            # below code will do nothing, and (2) the below will
            # trigger a recursive exception, breaking things, so we
            # just return here.
            return response

        if (not request.path.startswith("/static/")
                and not request.path.startswith("/api/")
                and not request.path.startswith("/json/")):
            subdomain = get_subdomain(request)
            if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
                realm = get_realm(subdomain)
                if (realm is None):
                    return render(request, "zerver/invalid_realm.html")
        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie or delete
        the session cookie if the session has been emptied.
        """
        try:
            accessed = request.session.accessed
            modified = request.session.modified
            empty = request.session.is_empty()
        except AttributeError:
            pass
        else:
            # First check if we need to delete this cookie.
            # The session should be deleted only if the session is entirely empty
            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
                response.delete_cookie(
                    settings.SESSION_COOKIE_NAME,
                    path=settings.SESSION_COOKIE_PATH,
                    domain=settings.SESSION_COOKIE_DOMAIN,
                )
            else:
                if accessed:
                    patch_vary_headers(response, ('Cookie', ))
                if (modified
                        or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
                    if request.session.get_expire_at_browser_close():
                        max_age = None
                        expires = None
                    else:
                        max_age = request.session.get_expiry_age()
                        expires_time = time.time() + max_age
                        expires = cookie_date(expires_time)
                    # Save the session data and refresh the client cookie.
                    # Skip session save for 500 responses, refs #3881.
                    if response.status_code != 500:
                        try:
                            request.session.save()
                        except UpdateError:
                            raise SuspiciousOperation(
                                "The request's session was deleted before the "
                                "request completed. The user may have logged "
                                "out in a concurrent request, for example.")
                        host = request.get_host().split(':')[0]

                        # The subdomains feature overrides the
                        # SESSION_COOKIE_DOMAIN setting, since the setting
                        # is a fixed value and with subdomains enabled,
                        # the session cookie domain has to vary with the
                        # subdomain.
                        session_cookie_domain = host
                        response.set_cookie(
                            settings.SESSION_COOKIE_NAME,
                            request.session.session_key,
                            max_age=max_age,
                            expires=expires,
                            domain=session_cookie_domain,
                            path=settings.SESSION_COOKIE_PATH,
                            secure=settings.SESSION_COOKIE_SECURE or None,
                            httponly=settings.SESSION_COOKIE_HTTPONLY or None,
                        )
        return response
Пример #57
0
def accounts_register(request: HttpRequest) -> HttpResponse:
    key = request.POST['key']
    confirmation = Confirmation.objects.get(confirmation_key=key)
    prereg_user = confirmation.content_object
    email = prereg_user.email
    realm_creation = prereg_user.realm_creation
    password_required = prereg_user.password_required
    is_realm_admin = prereg_user.invited_as == PreregistrationUser.INVITE_AS[
        'REALM_ADMIN'] or realm_creation
    is_guest = prereg_user.invited_as == PreregistrationUser.INVITE_AS[
        'GUEST_USER']

    try:
        validators.validate_email(email)
    except ValidationError:
        return render(request,
                      "zerver/invalid_email.html",
                      context={"invalid_email": True})

    if realm_creation:
        # For creating a new realm, there is no existing realm or domain
        realm = None
    else:
        if get_subdomain(request) != prereg_user.realm.string_id:
            return render_confirmation_key_error(
                request,
                ConfirmationKeyException(
                    ConfirmationKeyException.DOES_NOT_EXIST))
        realm = prereg_user.realm

        try:
            email_allowed_for_realm(email, realm)
        except DomainNotAllowedForRealmError:
            return render(request,
                          "zerver/invalid_email.html",
                          context={
                              "realm_name": realm.name,
                              "closed_domain": True
                          })
        except DisposableEmailError:
            return render(request,
                          "zerver/invalid_email.html",
                          context={
                              "realm_name": realm.name,
                              "disposable_emails_not_allowed": True
                          })
        except EmailContainsPlusError:
            return render(request,
                          "zerver/invalid_email.html",
                          context={
                              "realm_name": realm.name,
                              "email_contains_plus": True
                          })

        if realm.deactivated:
            # The user is trying to register for a deactivated realm. Advise them to
            # contact support.
            return redirect_to_deactivation_notice()

        try:
            validate_email_for_realm(realm, email)
        except ValidationError:
            return HttpResponseRedirect(
                reverse('django.contrib.auth.views.login') + '?email=' +
                urllib.parse.quote_plus(email))

    name_validated = False
    full_name = None
    require_ldap_password = False

    if request.POST.get('from_confirmation'):
        try:
            del request.session['authenticated_full_name']
        except KeyError:
            pass

        ldap_full_name = None
        if settings.POPULATE_PROFILE_VIA_LDAP:
            # If the user can be found in LDAP, we'll take the full name from the directory,
            # and further down create a form pre-filled with it.
            for backend in get_backends():
                if isinstance(backend, LDAPBackend):
                    try:
                        ldap_username = backend.django_to_ldap_username(email)
                    except ZulipLDAPExceptionNoMatchingLDAPUser:
                        logging.warning(
                            "New account email %s could not be found in LDAP" %
                            (email, ))
                        break

                    # Note that this `ldap_user` object is not a
                    # `ZulipLDAPUser` with a `Realm` attached, so
                    # calling `.populate_user()` on it will crash.
                    # This is OK, since we're just accessing this user
                    # to extract its name.
                    #
                    # TODO: We should potentially be accessing this
                    # user to sync its initial avatar and custom
                    # profile fields as well, if we indeed end up
                    # creating a user account through this flow,
                    # rather than waiting until `manage.py
                    # sync_ldap_user_data` runs to populate it.
                    ldap_user = _LDAPUser(backend, ldap_username)

                    try:
                        ldap_full_name, _ = backend.get_mapped_name(ldap_user)
                    except TypeError:
                        break

                    # Check whether this is ZulipLDAPAuthBackend,
                    # which is responsible for authentication and
                    # requires that LDAP accounts enter their LDAP
                    # password to register, or ZulipLDAPUserPopulator,
                    # which just populates UserProfile fields (no auth).
                    require_ldap_password = isinstance(backend,
                                                       ZulipLDAPAuthBackend)
                    break

        if ldap_full_name:
            # We don't use initial= here, because if the form is
            # complete (that is, no additional fields need to be
            # filled out by the user) we want the form to validate,
            # so they can be directly registered without having to
            # go through this interstitial.
            form = RegistrationForm({'full_name': ldap_full_name},
                                    realm_creation=realm_creation)
            request.session['authenticated_full_name'] = ldap_full_name
            name_validated = True
        elif realm is not None and realm.is_zephyr_mirror_realm:
            # For MIT users, we can get an authoritative name from Hesiod.
            # Technically we should check that this is actually an MIT
            # realm, but we can cross that bridge if we ever get a non-MIT
            # zephyr mirroring realm.
            hesiod_name = compute_mit_user_fullname(email)
            form = RegistrationForm(initial={
                'full_name':
                hesiod_name if "@" not in hesiod_name else ""
            },
                                    realm_creation=realm_creation)
            name_validated = True
        elif prereg_user.full_name:
            if prereg_user.full_name_validated:
                request.session[
                    'authenticated_full_name'] = prereg_user.full_name
                name_validated = True
                form = RegistrationForm({'full_name': prereg_user.full_name},
                                        realm_creation=realm_creation)
            else:
                form = RegistrationForm(
                    initial={'full_name': prereg_user.full_name},
                    realm_creation=realm_creation)
        elif 'full_name' in request.POST:
            form = RegistrationForm(
                initial={'full_name': request.POST.get('full_name')},
                realm_creation=realm_creation)
        else:
            form = RegistrationForm(realm_creation=realm_creation)
    else:
        postdata = request.POST.copy()
        if name_changes_disabled(realm):
            # If we populate profile information via LDAP and we have a
            # verified name from you on file, use that. Otherwise, fall
            # back to the full name in the request.
            try:
                postdata.update(
                    {'full_name': request.session['authenticated_full_name']})
                name_validated = True
            except KeyError:
                pass
        form = RegistrationForm(postdata, realm_creation=realm_creation)

    if not (password_auth_enabled(realm) and password_required):
        form['password'].field.required = False

    if form.is_valid():
        if password_auth_enabled(realm) and form['password'].field.required:
            password = form.cleaned_data['password']
        else:
            # If the user wasn't prompted for a password when
            # completing the authentication form (because they're
            # signing up with SSO and no password is required), set
            # the password field to `None` (Which causes Django to
            # create an unusable password).
            password = None

        if realm_creation:
            string_id = form.cleaned_data['realm_subdomain']
            realm_name = form.cleaned_data['realm_name']
            realm = do_create_realm(string_id, realm_name)
            setup_realm_internal_bots(realm)
        assert (realm is not None)

        full_name = form.cleaned_data['full_name']
        short_name = email_to_username(email)
        default_stream_group_names = request.POST.getlist(
            'default_stream_group')
        default_stream_groups = lookup_default_stream_groups(
            default_stream_group_names, realm)

        timezone = ""
        if 'timezone' in request.POST and request.POST[
                'timezone'] in get_all_timezones():
            timezone = request.POST['timezone']

        if 'source_realm' in request.POST and request.POST[
                "source_realm"] != "on":
            source_profile = get_source_profile(email,
                                                request.POST["source_realm"])
        else:
            source_profile = None

        if not realm_creation:
            try:
                existing_user_profile = get_user_by_delivery_email(
                    email, realm)  # type: Optional[UserProfile]
            except UserProfile.DoesNotExist:
                existing_user_profile = None
        else:
            existing_user_profile = None

        user_profile = None  # type: Optional[UserProfile]
        return_data = {}  # type: Dict[str, bool]
        if ldap_auth_enabled(realm):
            # If the user was authenticated using an external SSO
            # mechanism like Google or GitHub auth, then authentication
            # will have already been done before creating the
            # PreregistrationUser object with password_required=False, and
            # so we don't need to worry about passwords.
            #
            # If instead the realm is using EmailAuthBackend, we will
            # set their password above.
            #
            # But if the realm is using LDAPAuthBackend, we need to verify
            # their LDAP password (which will, as a side effect, create
            # the user account) here using authenticate.
            # pregeg_user.realm_creation carries the information about whether
            # we're in realm creation mode, and the ldap flow will handle
            # that and create the user with the appropriate parameters.
            user_profile = authenticate(request,
                                        username=email,
                                        password=password,
                                        realm=realm,
                                        prereg_user=prereg_user,
                                        return_data=return_data)
            if user_profile is None:
                can_use_different_backend = email_auth_enabled(
                    realm) or any_social_backend_enabled(realm)
                if settings.LDAP_APPEND_DOMAIN:
                    # In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-ldap account
                    # if the email matches the ldap domain.
                    can_use_different_backend = can_use_different_backend and (
                        not email_belongs_to_ldap(realm, email))
                if return_data.get(
                        "no_matching_ldap_user") and can_use_different_backend:
                    # If both the LDAP and Email or Social auth backends are
                    # enabled, and there's no matching user in the LDAP
                    # directory then the intent is to create a user in the
                    # realm with their email outside the LDAP organization
                    # (with e.g. a password stored in the Zulip database,
                    # not LDAP).  So we fall through and create the new
                    # account.
                    pass
                else:
                    # TODO: This probably isn't going to give a
                    # user-friendly error message, but it doesn't
                    # particularly matter, because the registration form
                    # is hidden for most users.
                    return HttpResponseRedirect(
                        reverse('django.contrib.auth.views.login') +
                        '?email=' + urllib.parse.quote_plus(email))
            elif not realm_creation:
                # Since we'll have created a user, we now just log them in.
                return login_and_go_to_home(request, user_profile)
            else:
                # With realm_creation=True, we're going to return further down,
                # after finishing up the creation process.
                pass

        if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
            user_profile = existing_user_profile
            do_activate_user(user_profile)
            do_change_password(user_profile, password)
            do_change_full_name(user_profile, full_name, user_profile)
            do_set_user_display_setting(user_profile, 'timezone', timezone)
            # TODO: When we clean up the `do_activate_user` code path,
            # make it respect invited_as_admin / is_realm_admin.

        if user_profile is None:
            user_profile = do_create_user(
                email,
                password,
                realm,
                full_name,
                short_name,
                prereg_user=prereg_user,
                is_realm_admin=is_realm_admin,
                is_guest=is_guest,
                tos_version=settings.TOS_VERSION,
                timezone=timezone,
                newsletter_data={"IP": request.META['REMOTE_ADDR']},
                default_stream_groups=default_stream_groups,
                source_profile=source_profile,
                realm_creation=realm_creation)

        if realm_creation:
            bulk_add_subscriptions([realm.signup_notifications_stream],
                                   [user_profile])
            send_initial_realm_messages(realm)

            # Because for realm creation, registration happens on the
            # root domain, we need to log them into the subdomain for
            # their new realm.
            return redirect_and_log_into_subdomain(realm, full_name, email)

        # This dummy_backend check below confirms the user is
        # authenticating to the correct subdomain.
        auth_result = authenticate(username=user_profile.delivery_email,
                                   realm=realm,
                                   return_data=return_data,
                                   use_dummy_backend=True)
        if return_data.get('invalid_subdomain'):
            # By construction, this should never happen.
            logging.error("Subdomain mismatch in registration %s: %s" % (
                realm.subdomain,
                user_profile.delivery_email,
            ))
            return redirect('/')

        return login_and_go_to_home(request, auth_result)

    return render(
        request,
        'zerver/register.html',
        context={
            'form': form,
            'email': email,
            'key': key,
            'full_name': request.session.get('authenticated_full_name', None),
            'lock_name': name_validated and name_changes_disabled(realm),
            # password_auth_enabled is normally set via our context processor,
            # but for the registration form, there is no logged in user yet, so
            # we have to set it here.
            'creating_new_team': realm_creation,
            'password_required': password_auth_enabled(realm)
            and password_required,
            'require_ldap_password': require_ldap_password,
            'password_auth_enabled': password_auth_enabled(realm),
            'root_domain_available': is_root_domain_available(),
            'default_stream_groups': get_default_stream_groups(realm),
            'accounts': get_accounts_for_email(email),
            'MAX_REALM_NAME_LENGTH': str(Realm.MAX_REALM_NAME_LENGTH),
            'MAX_NAME_LENGTH': str(UserProfile.MAX_NAME_LENGTH),
            'MAX_PASSWORD_LENGTH': str(form.MAX_PASSWORD_LENGTH),
            'MAX_REALM_SUBDOMAIN_LENGTH': str(Realm.MAX_REALM_SUBDOMAIN_LENGTH)
        })
Пример #58
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)
Пример #59
0
def accounts_home(
    request: HttpRequest,
    multiuse_object_key: str = "",
    multiuse_object: Optional[MultiuseInvite] = None,
) -> HttpResponse:
    try:
        realm = get_realm(get_subdomain(request))
    except Realm.DoesNotExist:
        return HttpResponseRedirect(reverse(find_account))
    if realm.deactivated:
        return redirect_to_deactivation_notice()

    from_multiuse_invite = False
    streams_to_subscribe = None
    invited_as = None

    if multiuse_object:
        realm = multiuse_object.realm
        streams_to_subscribe = multiuse_object.streams.all()
        from_multiuse_invite = True
        invited_as = multiuse_object.invited_as

    if request.method == "POST":
        form = HomepageForm(request.POST, realm=realm, from_multiuse_invite=from_multiuse_invite)
        if form.is_valid():
            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,
                )

            email = form.cleaned_data["email"]

            try:
                validate_email_not_already_in_realm(realm, email)
            except ValidationError:
                return redirect_to_email_login_url(email)

            activation_url = prepare_activation_url(
                email, request, streams=streams_to_subscribe, invited_as=invited_as
            )
            try:
                send_confirm_registration_email(email, activation_url, request=request, realm=realm)
            except EmailNotDeliveredException:
                logging.error("Error in accounts_home")
                return HttpResponseRedirect("/config-error/smtp")

            return HttpResponseRedirect(reverse("signup_send_confirm", kwargs={"email": email}))

    else:
        form = HomepageForm(realm=realm)
    context = login_context(request)
    context.update(
        form=form,
        current_url=request.get_full_path,
        multiuse_object_key=multiuse_object_key,
        from_multiuse_invite=from_multiuse_invite,
    )
    return render(request, "zerver/accounts_home.html", context=context)
Пример #60
0
def accounts_register(
    request: HttpRequest,
    key: str = REQ(default=""),
    timezone: str = REQ(default="", converter=to_timezone_or_empty),
    from_confirmation: Optional[str] = REQ(default=None),
    form_full_name: Optional[str] = REQ("full_name", default=None),
    source_realm_id: Optional[int] = REQ(
        default=None, converter=to_converted_or_fallback(to_non_negative_int, None)
    ),
) -> HttpResponse:
    try:
        prereg_user = check_prereg_key(request, key)
    except ConfirmationKeyException as e:
        return render_confirmation_key_error(request, e)

    email = prereg_user.email
    realm_creation = prereg_user.realm_creation
    password_required = prereg_user.password_required

    role = prereg_user.invited_as
    if realm_creation:
        role = UserProfile.ROLE_REALM_OWNER

    try:
        validators.validate_email(email)
    except ValidationError:
        return render(request, "zerver/invalid_email.html", context={"invalid_email": True})

    if realm_creation:
        # For creating a new realm, there is no existing realm or domain
        realm = None
    else:
        assert prereg_user.realm is not None
        if get_subdomain(request) != prereg_user.realm.string_id:
            return render_confirmation_key_error(
                request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)
            )
        realm = prereg_user.realm
        try:
            email_allowed_for_realm(email, realm)
        except DomainNotAllowedForRealmError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "closed_domain": True},
            )
        except DisposableEmailError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "disposable_emails_not_allowed": True},
            )
        except EmailContainsPlusError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "email_contains_plus": True},
            )

        if realm.deactivated:
            # The user is trying to register for a deactivated realm. Advise them to
            # contact support.
            return redirect_to_deactivation_notice()

        try:
            validate_email_not_already_in_realm(realm, email)
        except ValidationError:
            return redirect_to_email_login_url(email)

        if settings.BILLING_ENABLED:
            try:
                check_spare_licenses_available_for_registering_new_user(realm, email)
            except LicenseLimitError:
                return render(request, "zerver/no_spare_licenses.html")

    name_validated = False
    require_ldap_password = False

    if from_confirmation:
        try:
            del request.session["authenticated_full_name"]
        except KeyError:
            pass

        ldap_full_name = None
        if settings.POPULATE_PROFILE_VIA_LDAP:
            # If the user can be found in LDAP, we'll take the full name from the directory,
            # and further down create a form pre-filled with it.
            for backend in get_backends():
                if isinstance(backend, LDAPBackend):
                    try:
                        ldap_username = backend.django_to_ldap_username(email)
                    except ZulipLDAPExceptionNoMatchingLDAPUser:
                        logging.warning("New account email %s could not be found in LDAP", email)
                        break

                    # Note that this `ldap_user` object is not a
                    # `ZulipLDAPUser` with a `Realm` attached, so
                    # calling `.populate_user()` on it will crash.
                    # This is OK, since we're just accessing this user
                    # to extract its name.
                    #
                    # TODO: We should potentially be accessing this
                    # user to sync its initial avatar and custom
                    # profile fields as well, if we indeed end up
                    # creating a user account through this flow,
                    # rather than waiting until `manage.py
                    # sync_ldap_user_data` runs to populate it.
                    ldap_user = _LDAPUser(backend, ldap_username)

                    try:
                        ldap_full_name = backend.get_mapped_name(ldap_user)
                    except TypeError:
                        break

                    # Check whether this is ZulipLDAPAuthBackend,
                    # which is responsible for authentication and
                    # requires that LDAP accounts enter their LDAP
                    # password to register, or ZulipLDAPUserPopulator,
                    # which just populates UserProfile fields (no auth).
                    require_ldap_password = isinstance(backend, ZulipLDAPAuthBackend)
                    break

        if ldap_full_name:
            # We don't use initial= here, because if the form is
            # complete (that is, no additional fields need to be
            # filled out by the user) we want the form to validate,
            # so they can be directly registered without having to
            # go through this interstitial.
            form = RegistrationForm({"full_name": ldap_full_name}, realm_creation=realm_creation)
            request.session["authenticated_full_name"] = ldap_full_name
            name_validated = True
        elif realm is not None and realm.is_zephyr_mirror_realm:
            # For MIT users, we can get an authoritative name from Hesiod.
            # Technically we should check that this is actually an MIT
            # realm, but we can cross that bridge if we ever get a non-MIT
            # zephyr mirroring realm.
            hesiod_name = compute_mit_user_fullname(email)
            form = RegistrationForm(
                initial={"full_name": hesiod_name if "@" not in hesiod_name else ""},
                realm_creation=realm_creation,
            )
            name_validated = True
        elif prereg_user.full_name:
            if prereg_user.full_name_validated:
                request.session["authenticated_full_name"] = prereg_user.full_name
                name_validated = True
                form = RegistrationForm(
                    {"full_name": prereg_user.full_name}, realm_creation=realm_creation
                )
            else:
                form = RegistrationForm(
                    initial={"full_name": prereg_user.full_name}, realm_creation=realm_creation
                )
        elif form_full_name is not None:
            form = RegistrationForm(
                initial={"full_name": form_full_name},
                realm_creation=realm_creation,
            )
        else:
            form = RegistrationForm(realm_creation=realm_creation)
    else:
        postdata = request.POST.copy()
        if name_changes_disabled(realm):
            # If we populate profile information via LDAP and we have a
            # verified name from you on file, use that. Otherwise, fall
            # back to the full name in the request.
            try:
                postdata.update(full_name=request.session["authenticated_full_name"])
                name_validated = True
            except KeyError:
                pass
        form = RegistrationForm(postdata, realm_creation=realm_creation)

    if not (password_auth_enabled(realm) and password_required):
        form["password"].field.required = False

    if form.is_valid():
        if password_auth_enabled(realm) and form["password"].field.required:
            password = form.cleaned_data["password"]
        else:
            # If the user wasn't prompted for a password when
            # completing the authentication form (because they're
            # signing up with SSO and no password is required), set
            # the password field to `None` (Which causes Django to
            # create an unusable password).
            password = None

        if realm_creation:
            string_id = form.cleaned_data["realm_subdomain"]
            realm_name = form.cleaned_data["realm_name"]
            realm_type = form.cleaned_data["realm_type"]
            is_demo_org = form.cleaned_data["is_demo_organization"]
            realm = do_create_realm(
                string_id, realm_name, org_type=realm_type, is_demo_organization=is_demo_org
            )
            setup_realm_internal_bots(realm)
        assert realm is not None

        full_name = form.cleaned_data["full_name"]
        enable_marketing_emails = form.cleaned_data["enable_marketing_emails"]
        default_stream_group_names = request.POST.getlist("default_stream_group")
        default_stream_groups = lookup_default_stream_groups(default_stream_group_names, realm)

        if source_realm_id is not None:
            # Non-integer realm_id values like "string" are treated
            # like the "Do not import" value of "".
            source_profile: Optional[UserProfile] = get_source_profile(email, source_realm_id)
        else:
            source_profile = None

        if not realm_creation:
            try:
                existing_user_profile: Optional[UserProfile] = get_user_by_delivery_email(
                    email, realm
                )
            except UserProfile.DoesNotExist:
                existing_user_profile = None
        else:
            existing_user_profile = None

        user_profile: Optional[UserProfile] = None
        return_data: Dict[str, bool] = {}
        if ldap_auth_enabled(realm):
            # If the user was authenticated using an external SSO
            # mechanism like Google or GitHub auth, then authentication
            # will have already been done before creating the
            # PreregistrationUser object with password_required=False, and
            # so we don't need to worry about passwords.
            #
            # If instead the realm is using EmailAuthBackend, we will
            # set their password above.
            #
            # But if the realm is using LDAPAuthBackend, we need to verify
            # their LDAP password (which will, as a side effect, create
            # the user account) here using authenticate.
            # pregeg_user.realm_creation carries the information about whether
            # we're in realm creation mode, and the ldap flow will handle
            # that and create the user with the appropriate parameters.
            user_profile = authenticate(
                request=request,
                username=email,
                password=password,
                realm=realm,
                prereg_user=prereg_user,
                return_data=return_data,
            )
            if user_profile is None:
                can_use_different_backend = email_auth_enabled(realm) or (
                    len(get_external_method_dicts(realm)) > 0
                )
                if settings.LDAP_APPEND_DOMAIN:
                    # In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account
                    # if the email matches the ldap domain.
                    can_use_different_backend = can_use_different_backend and (
                        not email_belongs_to_ldap(realm, email)
                    )
                if return_data.get("no_matching_ldap_user") and can_use_different_backend:
                    # If both the LDAP and Email or Social auth backends are
                    # enabled, and there's no matching user in the LDAP
                    # directory then the intent is to create a user in the
                    # realm with their email outside the LDAP organization
                    # (with e.g. a password stored in the Zulip database,
                    # not LDAP).  So we fall through and create the new
                    # account.
                    pass
                else:
                    # TODO: This probably isn't going to give a
                    # user-friendly error message, but it doesn't
                    # particularly matter, because the registration form
                    # is hidden for most users.
                    view_url = reverse("login")
                    query = urlencode({"email": email})
                    redirect_url = append_url_query_string(view_url, query)
                    return HttpResponseRedirect(redirect_url)
            elif not realm_creation:
                # Since we'll have created a user, we now just log them in.
                return login_and_go_to_home(request, user_profile)
            else:
                # With realm_creation=True, we're going to return further down,
                # after finishing up the creation process.
                pass

        if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
            user_profile = existing_user_profile
            do_activate_mirror_dummy_user(user_profile, acting_user=user_profile)
            do_change_password(user_profile, password)
            do_change_full_name(user_profile, full_name, user_profile)
            do_change_user_setting(user_profile, "timezone", timezone, acting_user=user_profile)
            # TODO: When we clean up the `do_activate_mirror_dummy_user` code path,
            # make it respect invited_as_admin / is_realm_admin.

        if user_profile is None:
            user_profile = do_create_user(
                email,
                password,
                realm,
                full_name,
                prereg_user=prereg_user,
                role=role,
                tos_version=settings.TOS_VERSION,
                timezone=timezone,
                default_stream_groups=default_stream_groups,
                source_profile=source_profile,
                realm_creation=realm_creation,
                acting_user=None,
                enable_marketing_emails=enable_marketing_emails,
            )

        if realm_creation:
            assert realm.signup_notifications_stream is not None
            bulk_add_subscriptions(
                realm, [realm.signup_notifications_stream], [user_profile], acting_user=None
            )
            send_initial_realm_messages(realm)

            # Because for realm creation, registration happens on the
            # root domain, we need to log them into the subdomain for
            # their new realm.
            return redirect_and_log_into_subdomain(
                ExternalAuthResult(user_profile=user_profile, data_dict={"is_realm_creation": True})
            )

        # This dummy_backend check below confirms the user is
        # authenticating to the correct subdomain.
        auth_result = authenticate(
            username=user_profile.delivery_email,
            realm=realm,
            return_data=return_data,
            use_dummy_backend=True,
        )
        if return_data.get("invalid_subdomain"):
            # By construction, this should never happen.
            logging.error(
                "Subdomain mismatch in registration %s: %s",
                realm.subdomain,
                user_profile.delivery_email,
            )
            return redirect("/")

        return login_and_go_to_home(request, auth_result)

    return render(
        request,
        "zerver/register.html",
        context={
            "form": form,
            "email": email,
            "key": key,
            "full_name": request.session.get("authenticated_full_name", None),
            "lock_name": name_validated and name_changes_disabled(realm),
            # password_auth_enabled is normally set via our context processor,
            # but for the registration form, there is no logged in user yet, so
            # we have to set it here.
            "creating_new_team": realm_creation,
            "password_required": password_auth_enabled(realm) and password_required,
            "require_ldap_password": require_ldap_password,
            "password_auth_enabled": password_auth_enabled(realm),
            "root_domain_available": is_root_domain_available(),
            "default_stream_groups": [] if realm is None else get_default_stream_groups(realm),
            "accounts": get_accounts_for_email(email),
            "MAX_REALM_NAME_LENGTH": str(Realm.MAX_REALM_NAME_LENGTH),
            "MAX_NAME_LENGTH": str(UserProfile.MAX_NAME_LENGTH),
            "MAX_PASSWORD_LENGTH": str(form.MAX_PASSWORD_LENGTH),
            "MAX_REALM_SUBDOMAIN_LENGTH": str(Realm.MAX_REALM_SUBDOMAIN_LENGTH),
            "sorted_realm_types": sorted(
                Realm.ORG_TYPES.values(), key=lambda d: d["display_order"]
            ),
        },
    )