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
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)
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), }
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))
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)
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
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})
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
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})
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)
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)
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})
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
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)
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}, )
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}, )
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"))
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})
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)
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/')
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)
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)
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')
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))
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
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)
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
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)
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}, )
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)))
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
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)))
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)
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 })
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, )
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
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)
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
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})
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))
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
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
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)
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)
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}, )
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
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
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
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
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
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) }
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)
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)
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)
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)
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
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) })
def save(self, domain_override: Optional[bool]=None, subject_template_name: str='registration/password_reset_subject.txt', email_template_name: str='registration/password_reset_email.html', use_https: bool=False, token_generator: PasswordResetTokenGenerator=default_token_generator, from_email: Optional[str]=None, request: HttpRequest=None, html_email_template_name: Optional[str]=None, extra_email_context: Optional[Dict[str, Any]]=None ) -> None: """ If the email address has an account in the target realm, generates a one-use only link for resetting password and sends to the user. We send a different email if an associated account does not exist in the database, or an account does exist, but not in the realm. Note: We ignore protocol and the various email template arguments (those are an artifact of using Django's password reset framework). """ email = self.cleaned_data["email"] realm = get_realm(get_subdomain(request)) if not email_auth_enabled(realm): logging.info("Password reset attempted for %s even though password auth is disabled." % (email,)) return if email_belongs_to_ldap(realm, email): # TODO: Ideally, we'd provide a user-facing error here # about the fact that they aren't allowed to have a # password in the Zulip server and should change it in LDAP. logging.info("Password reset not allowed for user in LDAP domain") return if realm.deactivated: logging.info("Realm is deactivated") return user = None # type: Optional[UserProfile] try: user = get_user_by_delivery_email(email, realm) except UserProfile.DoesNotExist: pass context = { 'email': email, 'realm_uri': realm.uri, 'realm_name': realm.name, } if user is not None and not user.is_active: context['user_deactivated'] = True user = None if user is not None: context['active_account_in_realm'] = True context['reset_url'] = generate_password_reset_url(user, token_generator) send_email('zerver/emails/password_reset', to_user_ids=[user.id], from_name="Zulip Account Security", from_address=FromAddress.tokenized_no_reply_address(), context=context) else: context['active_account_in_realm'] = False active_accounts_in_other_realms = UserProfile.objects.filter( delivery_email__iexact=email, is_active=True) if active_accounts_in_other_realms: context['active_accounts_in_other_realms'] = active_accounts_in_other_realms send_email('zerver/emails/password_reset', to_emails=[email], from_name="Zulip Account Security", from_address=FromAddress.tokenized_no_reply_address(), language=request.LANGUAGE_CODE, context=context)
def 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)
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"] ), }, )