def check_prereg_key_and_redirect(request: HttpRequest, confirmation_key: str) -> HttpResponse: # If the key isn't valid, show the error message on the original URL confirmation = Confirmation.objects.filter( confirmation_key=confirmation_key).first() if confirmation is None or confirmation.type not in [ Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION ]: return render_confirmation_key_error( request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)) try: get_object_from_key(confirmation_key, confirmation.type) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) # confirm_preregistrationuser.html just extracts the confirmation_key # (and GET parameters) and redirects to /accounts/register, so that the # user can enter their information on a cleaner URL. return render(request, 'confirmation/confirm_preregistrationuser.html', context={ 'key': confirmation_key, 'full_name': request.GET.get("full_name", None) })
def check_prereg_key_and_redirect(request: HttpRequest, confirmation_key: str) -> HttpResponse: confirmation = Confirmation.objects.filter( confirmation_key=confirmation_key).first() if confirmation is None or confirmation.type not in [ Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION, ]: return render_confirmation_key_error( request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)) prereg_user = confirmation.content_object if prereg_user.status == confirmation_settings.STATUS_REVOKED: return render(request, "zerver/confirmation_link_expired_error.html") try: get_object_from_key(confirmation_key, confirmation.type, activate_object=False) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) # confirm_preregistrationuser.html just extracts the confirmation_key # (and GET parameters) and redirects to /accounts/register, so that the # user can enter their information on a cleaner URL. return render( request, "confirmation/confirm_preregistrationuser.html", context={ "key": confirmation_key, "full_name": request.GET.get("full_name", None) }, )
def get_prereg_key_and_redirect( request: HttpRequest, confirmation_key: str, full_name: Optional[str] = REQ(default=None) ) -> HttpResponse: """ The purpose of this little endpoint is primarily to take a GET request to a long URL containing a confirmation key, and render a page that will via JavaScript immediately do a POST request to /accounts/register, so that the user can create their account on a page with a cleaner URL (and with the browser security and UX benefits of an HTTP POST having generated the page). The only thing it does before rendering that page is to check the validity of the confirmation link. This is redundant with a similar check in accounts_register, but it provides a slightly nicer user-facing error handling experience if the URL you visited is displayed in the browser. (E.g. you can debug that you accidentally adding an extra character after pasting). """ try: check_prereg_key(request, confirmation_key) except ConfirmationKeyException as e: return render_confirmation_key_error(request, e) return render( request, "confirmation/confirm_preregistrationuser.html", context={"key": confirmation_key, "full_name": full_name}, )
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError(_("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {'realm_name': user_profile.realm.name, 'new_email': new_email} language = user_profile.default_language send_email('zerver/emails/notify_change_in_email', to_emails=[old_email], from_name=FromAddress.security_email_from_name(user_profile=user_profile), from_address=FromAddress.SUPPORT, language=language, context=context, realm=user_profile.realm) ctx = { 'new_email_html_tag': SafeString(f'<a href="mailto:{escape(new_email)}">{escape(new_email)}</a>'), 'old_email_html_tag': SafeString(f'<a href="mailto:{escape(old_email)}">{escape(old_email)}</a>'), } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError(_("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {'realm_name': user_profile.realm.name, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_emails=[old_email], from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, language=user_profile.default_language, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse user_profile = request.user if user_profile.realm.email_changes_disabled: raise JsonableError(_("Email address changes are disabled in this organization.")) confirmation_key = confirmation_key.lower() try: obj = get_object_from_key(confirmation_key) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) assert isinstance(obj, EmailChangeStatus) new_email = obj.new_email old_email = obj.old_email do_change_user_email(obj.user_profile, obj.new_email) context = {'realm': obj.realm, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_email=old_email, from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, Confirmation.EMAIL_CHANGE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError( _("Email address changes are disabled in this organization.")) do_change_user_email(user_profile, new_email) context = {'realm': user_profile.realm, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_email=old_email, from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def confirm_email_change(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse user_profile = request.user if user_profile.realm.email_changes_disabled: raise JsonableError(_("Email address changes are disabled in this organization.")) confirmation_key = confirmation_key.lower() try: obj = get_object_from_key(confirmation_key) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) assert isinstance(obj, EmailChangeStatus) new_email = obj.new_email old_email = obj.old_email do_change_user_email(obj.user_profile, obj.new_email) context = {'realm': obj.realm, 'new_email': new_email} send_email('zerver/emails/notify_change_in_email', to_email=old_email, from_name="Zulip Account Security", from_address=FromAddress.SUPPORT, context=context) ctx = { 'new_email': new_email, 'old_email': old_email, } return render(request, 'confirmation/confirm_email_change.html', context=ctx)
def check_prereg_key_and_redirect(request: HttpRequest, confirmation_key: str) -> HttpResponse: # If the key isn't valid, show the error message on the original URL confirmation = Confirmation.objects.filter(confirmation_key=confirmation_key).first() if confirmation is None or confirmation.type not in [ Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION]: return render_confirmation_key_error( request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)) try: get_object_from_key(confirmation_key, confirmation.type) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) # confirm_preregistrationuser.html just extracts the confirmation_key # (and GET parameters) and redirects to /accounts/register, so that the # user can enter their information on a cleaner URL. return render(request, 'confirmation/confirm_preregistrationuser.html', context={ 'key': confirmation_key, 'full_name': request.GET.get("full_name", None)})
def accounts_home_from_multiuse_invite(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse multiuse_object = None try: multiuse_object = get_object_from_key(confirmation_key) except ConfirmationKeyException as exception: realm = get_realm_from_request(request) if realm is None or realm.invite_required: return render_confirmation_key_error(request, exception) return accounts_home(request, multiuse_object=multiuse_object)
def confirm(request, confirmation_key): # type: (HttpRequest, str) -> HttpResponse try: get_object_from_key(confirmation_key) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) return render(request, 'confirmation/confirm_preregistrationuser.html', context={ 'key': confirmation_key, 'full_name': request.GET.get("full_name", None)})
def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: str) -> HttpResponse: multiuse_object = None try: multiuse_object = get_object_from_key(confirmation_key, Confirmation.MULTIUSE_INVITE) # Required for oAuth2 request.session["multiuse_object_key"] = confirmation_key except ConfirmationKeyException as exception: realm = get_realm_from_request(request) if realm is None or realm.invite_required: return render_confirmation_key_error(request, exception) return accounts_home(request, multiuse_object=multiuse_object)
def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: str) -> HttpResponse: multiuse_object = None try: multiuse_object = get_object_from_key(confirmation_key, Confirmation.MULTIUSE_INVITE) # Required for oAuth2 request.session["multiuse_object_key"] = confirmation_key except ConfirmationKeyException as exception: realm = get_realm_from_request(request) if realm is None or realm.invite_required: return render_confirmation_key_error(request, exception) return accounts_home(request, multiuse_object=multiuse_object)
def process_unsubscribe( request: HttpRequest, confirmation_key: str, subscription_type: str, unsubscribe_function: Callable[[UserProfile], None]) -> HttpResponse: try: user_profile = get_object_from_key(confirmation_key, Confirmation.UNSUBSCRIBE) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) unsubscribe_function(user_profile) context = common_context(user_profile) context.update({"subscription_type": subscription_type}) return render(request, 'zerver/unsubscribe_success.html', context=context)
def check_prereg_key( request: HttpRequest, confirmation_key: str) -> Union[Confirmation, HttpResponse]: """ Checks if the Confirmation key is valid, returning the Confirmation object in case of success and an appropriate error page otherwise. """ try: confirmation: Optional[Confirmation] = Confirmation.objects.get( confirmation_key=confirmation_key) except Confirmation.DoesNotExist: confirmation = None if confirmation is None or confirmation.type not in [ Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION, ]: return render_confirmation_key_error( request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)) prereg_user = confirmation.content_object assert prereg_user is not None if prereg_user.status == confirmation_settings.STATUS_REVOKED: return render(request, "zerver/confirmation_link_expired_error.html", status=404) try: get_object_from_key(confirmation_key, confirmation.type, activate_object=False) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) return confirmation
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: email_change_object = get_object_from_key(confirmation_key, [Confirmation.EMAIL_CHANGE]) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) new_email = email_change_object.new_email old_email = email_change_object.old_email user_profile = email_change_object.user_profile if user_profile.realm.deactivated: return redirect_to_deactivation_notice() if not user_profile.is_active: # TODO: Make this into a user-facing error, not JSON raise UserDeactivatedError() if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError( _("Email address changes are disabled in this organization.")) do_change_user_delivery_email(user_profile, new_email) context = {"realm_name": user_profile.realm.name, "new_email": new_email} language = user_profile.default_language send_email( "zerver/emails/notify_change_in_email", to_emails=[old_email], from_name=FromAddress.security_email_from_name( user_profile=user_profile), from_address=FromAddress.SUPPORT, language=language, context=context, realm=user_profile.realm, ) ctx = { "new_email_html_tag": SafeString( f'<a href="mailto:{escape(new_email)}">{escape(new_email)}</a>'), "old_email_html_tag": SafeString( f'<a href="mailto:{escape(old_email)}">{escape(old_email)}</a>'), } return render(request, "confirmation/confirm_email_change.html", context=ctx)
def confirm(request: HttpRequest, confirmation_key: str) -> HttpResponse: try: get_object_from_key(confirmation_key, Confirmation.USER_REGISTRATION) except ConfirmationKeyException: try: get_object_from_key(confirmation_key, Confirmation.INVITATION) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) return render(request, 'confirmation/confirm_preregistrationuser.html', context={ 'key': confirmation_key, 'full_name': request.GET.get("full_name", None) })
def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: str) -> HttpResponse: realm = get_realm_from_request(request) multiuse_object: Optional[MultiuseInvite] = None try: confirmation_obj = get_object_from_key(confirmation_key, [Confirmation.MULTIUSE_INVITE]) assert isinstance(confirmation_obj, MultiuseInvite) multiuse_object = confirmation_obj if realm != multiuse_object.realm: return render(request, "confirmation/link_does_not_exist.html", status=404) # Required for OAuth 2 except ConfirmationKeyException as exception: if realm is None or realm.invite_required: return render_confirmation_key_error(request, exception) return accounts_home( request, multiuse_object_key=confirmation_key, multiuse_object=multiuse_object )
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 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 web app 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) multiuse_obj: Optional[MultiuseInvite] = None realm: Optional[Realm] = None from_multiuse_invite = False if multiuse_object_key: from_multiuse_invite = True try: multiuse_obj = get_object_from_key(multiuse_object_key, [Confirmation.MULTIUSE_INVITE]) except ConfirmationKeyException as exception: return render_confirmation_key_error(request, exception) assert multiuse_obj is not None realm = multiuse_obj.realm invited_as = multiuse_obj.invited_as else: try: realm = get_realm(get_subdomain(request)) except Realm.DoesNotExist: pass 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 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"] ), }, )
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_admin or realm_creation 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: realm = get_realm(get_subdomain(request)) if realm is None or realm != prereg_user.realm: return render_confirmation_key_error( request, ConfirmationKeyException( ConfirmationKeyException.DOES_NOT_EXIST)) 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: # nocoverage # We need to add a test for this. return HttpResponseRedirect( reverse('django.contrib.auth.views.login') + '?email=' + urllib.parse.quote_plus(email)) name_validated = False full_name = None if request.POST.get('from_confirmation'): try: del request.session['authenticated_full_name'] except KeyError: pass if 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 settings.POPULATE_PROFILE_VIA_LDAP: for backend in get_backends(): if isinstance(backend, LDAPBackend): try: ldap_username = backend.django_to_ldap_username(email) except ZulipLDAPException: logging.warning( "New account email %s could not be found in LDAP" % (email, )) form = RegistrationForm(realm_creation=realm_creation) break ldap_attrs = _LDAPUser(backend, ldap_username).attrs try: ldap_full_name = ldap_attrs[ settings.AUTH_LDAP_USER_ATTR_MAP['full_name']][0] request.session[ 'authenticated_full_name'] = ldap_full_name name_validated = True # 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) # FIXME: This will result in the user getting # validation errors if they have to enter a password. # Not relevant for ONLY_SSO, though. break except TypeError: # Let the user fill out a name and/or try another backend form = RegistrationForm(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): password = form.cleaned_data['password'] else: # SSO users don't need no passwords 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_initial_streams(realm) 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 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. auth_result = authenticate(request, username=email, password=password, realm=realm, return_data=return_data) if auth_result is not None: # Since we'll have created a user, we now just log them in. return login_and_go_to_home(request, auth_result) if return_data.get("outside_ldap_domain") and email_auth_enabled( realm): # If both the LDAP and Email auth backends are # enabled, and the user's email is outside the LDAP # domain, 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. # # It's likely that we can extend this block to the # Google and GitHub auth backends with no code changes # other than here. 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)) 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. else: user_profile = do_create_user( email, password, realm, full_name, short_name, prereg_user=prereg_user, is_realm_admin=is_realm_admin, 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.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.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, '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 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_admin or realm_creation 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: realm = get_realm(get_subdomain(request)) if realm is None or realm != prereg_user.realm: return render_confirmation_key_error( request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)) 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: # nocoverage # We need to add a test for this. return HttpResponseRedirect(reverse('django.contrib.auth.views.login') + '?email=' + urllib.parse.quote_plus(email)) name_validated = False full_name = None if request.POST.get('from_confirmation'): try: del request.session['authenticated_full_name'] except KeyError: pass if 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 settings.POPULATE_PROFILE_VIA_LDAP: for backend in get_backends(): if isinstance(backend, LDAPBackend): try: ldap_username = backend.django_to_ldap_username(email) except ZulipLDAPException: logging.warning("New account email %s could not be found in LDAP" % (email,)) form = RegistrationForm(realm_creation=realm_creation) break ldap_attrs = _LDAPUser(backend, ldap_username).attrs try: ldap_full_name = ldap_attrs[settings.AUTH_LDAP_USER_ATTR_MAP['full_name']][0] request.session['authenticated_full_name'] = ldap_full_name name_validated = True # 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) # FIXME: This will result in the user getting # validation errors if they have to enter a password. # Not relevant for ONLY_SSO, though. break except TypeError: # Let the user fill out a name and/or try another backend form = RegistrationForm(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): password = form.cleaned_data['password'] else: # SSO users don't need no passwords 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_initial_streams(realm) 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(email, realm) # type: Optional[UserProfile] except UserProfile.DoesNotExist: existing_user_profile = None else: existing_user_profile = None 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. auth_result = authenticate(request, username=email, password=password, realm=realm, return_data=return_data) if auth_result is not None: # Since we'll have created a user, we now just log them in. return login_and_go_to_home(request, auth_result) if return_data.get("outside_ldap_domain") and email_auth_enabled(realm): # If both the LDAP and Email auth backends are # enabled, and the user's email is outside the LDAP # domain, 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. # # It's likely that we can extend this block to the # Google and GitHub auth backends with no code changes # other than here. 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)) 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. else: user_profile = do_create_user(email, password, realm, full_name, short_name, prereg_user=prereg_user, is_realm_admin=is_realm_admin, tos_version=settings.TOS_VERSION, timezone=timezone, newsletter_data={"IP": request.META['REMOTE_ADDR']}, default_stream_groups=default_stream_groups, source_profile=source_profile) 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.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.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, '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) } )