def process_do_auth(self, user_profile: UserProfile, *args: Any, **kwargs: Any) -> Optional[HttpResponse]: # These functions need to be imported here to avoid cyclic # dependency. from zerver.views.auth import (login_or_register_remote_user, redirect_to_subdomain_login_url, redirect_and_log_into_subdomain) return_data = kwargs.get('return_data', {}) inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') if inactive_user or inactive_realm: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning("{} got invalid email argument.".format( self.auth_backend_name)) return None strategy = self.strategy # type: ignore # This comes from Python Social Auth. request = strategy.request email_address = self.get_email_address(*args, **kwargs) full_name = self.get_full_name(*args, **kwargs) is_signup = strategy.session_get('is_signup') == '1' mobile_flow_otp = strategy.session_get('mobile_flow_otp') subdomain = strategy.session_get('subdomain') if not subdomain: # At least in our tests, this can be None; and it's not # clear what the exact semantics of `session_get` are or # what values it might return. Historically we treated # any falsy value here as the root domain, so defensively # continue that behavior. subdomain = Realm.SUBDOMAIN_FOR_ROOT_DOMAIN if (subdomain == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN or mobile_flow_otp is not None): return login_or_register_remote_user( request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup) realm = get_realm(subdomain) if realm is None: return redirect_to_subdomain_login_url() return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup)
def process_do_auth(self, user_profile: UserProfile, *args: Any, **kwargs: Any) -> Optional[HttpResponse]: # These functions need to be imported here to avoid cyclic # dependency. from zerver.views.auth import (login_or_register_remote_user, redirect_to_subdomain_login_url, redirect_and_log_into_subdomain) return_data = kwargs.get('return_data', {}) inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') if inactive_user or inactive_realm: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning( "{} got invalid email argument.".format(self.auth_backend_name) ) return None strategy = self.strategy # type: ignore # This comes from Python Social Auth. request = strategy.request email_address = self.get_email_address(*args, **kwargs) full_name = self.get_full_name(*args, **kwargs) is_signup = strategy.session_get('is_signup') == '1' mobile_flow_otp = strategy.session_get('mobile_flow_otp') subdomain = strategy.session_get('subdomain') if not subdomain: # At least in our tests, this can be None; and it's not # clear what the exact semantics of `session_get` are or # what values it might return. Historically we treated # any falsy value here as the root domain, so defensively # continue that behavior. subdomain = Realm.SUBDOMAIN_FOR_ROOT_DOMAIN if (subdomain == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN or mobile_flow_otp is not None): return login_or_register_remote_user(request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup) realm = get_realm(subdomain) if realm is None: return redirect_to_subdomain_login_url() return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup)
def process_do_auth(self, user_profile: UserProfile, *args: Any, **kwargs: Any) -> Optional[HttpResponse]: # These functions need to be imported here to avoid cyclic # dependency. from zerver.views.auth import (login_or_register_remote_user, redirect_to_subdomain_login_url, redirect_and_log_into_subdomain) return_data = kwargs.get('return_data', {}) inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') if inactive_user or inactive_realm: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning("{} got invalid email argument.".format( self.auth_backend_name)) return None strategy = self.strategy # type: ignore # This comes from Python Social Auth. request = strategy.request email_address = self.get_email_address(*args, **kwargs) full_name = self.get_full_name(*args, **kwargs) is_signup = strategy.session_get('is_signup') == '1' redirect_to = strategy.session_get('next') mobile_flow_otp = strategy.session_get('mobile_flow_otp') subdomain = strategy.session_get('subdomain') assert subdomain is not None if mobile_flow_otp is not None: return login_or_register_remote_user( request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup, redirect_to=redirect_to) realm = get_realm(subdomain) if realm is None: return redirect_to_subdomain_login_url() return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup, redirect_to=redirect_to)
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 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 social_auth_finish(backend: Any, details: Dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any) -> Optional[UserProfile]: """Given the determination in social_auth_associate_user for whether the user should be authenticated, this takes care of actually logging in the user (if appropriate) and redirecting the browser to the appropriate next page depending on the situation. Read the comments below as well as login_or_register_remote_user in `zerver/views/auth.py` for the details on how that dispatch works. """ from zerver.views.auth import (login_or_register_remote_user, redirect_and_log_into_subdomain) user_profile = kwargs['user_profile'] return_data = kwargs['return_data'] no_verified_email = return_data.get("email_not_verified") auth_backend_disabled = return_data.get('auth_backend_disabled') inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_realm = return_data.get('invalid_realm') invalid_email = return_data.get('invalid_email') auth_failed_reason = return_data.get("social_auth_failed_reason") email_not_associated = return_data.get("email_not_associated") if invalid_realm: from zerver.views.auth import redirect_to_subdomain_login_url return redirect_to_subdomain_login_url() if inactive_user: return redirect_deactivated_user_to_login() if auth_backend_disabled or inactive_realm or no_verified_email or email_not_associated: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning( "{} got invalid email argument.".format(backend.auth_backend_name) ) return None if auth_failed_reason: logging.info(auth_failed_reason) return None # Structurally, all the cases where we don't have an authenticated # email for the user should be handled above; this assertion helps # prevent any violations of that contract from resulting in a user # being incorrectly authenticated. assert return_data.get('valid_attestation') is True strategy = backend.strategy email_address = return_data['validated_email'] full_name = return_data['full_name'] is_signup = strategy.session_get('is_signup') == '1' redirect_to = strategy.session_get('next') realm = Realm.objects.get(id=return_data["realm_id"]) multiuse_object_key = strategy.session_get('multiuse_object_key', '') mobile_flow_otp = strategy.session_get('mobile_flow_otp') # At this point, we have now confirmed that the user has # demonstrated control over the target email address. # # The next step is to call login_or_register_remote_user, but # there are two code paths here because of an optimization to save # a redirect on mobile. if mobile_flow_otp is not None: # For mobile app authentication, login_or_register_remote_user # will redirect to a special zulip:// URL that is handled by # the app after a successful authentication; so we can # redirect directly from here, saving a round trip over what # we need to do to create session cookies on the right domain # in the web login flow (below). return login_or_register_remote_user(strategy.request, email_address, user_profile, full_name, mobile_flow_otp=mobile_flow_otp, is_signup=is_signup, redirect_to=redirect_to) # If this authentication code were executing on # subdomain.zulip.example.com, we would just call # login_or_register_remote_user as in the mobile code path. # However, because third-party SSO providers generally don't allow # wildcard addresses in their redirect URLs, for multi-realm # servers, we will have just completed authentication on e.g. # auth.zulip.example.com (depending on # settings.SOCIAL_AUTH_SUBDOMAIN), which cannot store cookies on # the subdomain.zulip.example.com domain. So instead we serve a # redirect (encoding the authentication result data in a # cryptographically signed token) to a route on # subdomain.zulip.example.com that will verify the signature and # then call login_or_register_remote_user. return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup, redirect_to=redirect_to, multiuse_object_key=multiuse_object_key)
def social_auth_finish(backend: Any, details: Dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any) -> Optional[UserProfile]: from zerver.views.auth import (login_or_register_remote_user, redirect_and_log_into_subdomain) user_profile = kwargs['user_profile'] return_data = kwargs['return_data'] no_verified_email = return_data.get("email_not_verified") auth_backend_disabled = return_data.get('auth_backend_disabled') inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_realm = return_data.get('invalid_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') auth_failed_reason = return_data.get("social_auth_failed_reason") if invalid_realm: from zerver.views.auth import redirect_to_subdomain_login_url return redirect_to_subdomain_login_url() if auth_backend_disabled or inactive_user or inactive_realm or no_verified_email: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning("{} got invalid email argument.".format( backend.auth_backend_name)) return None if auth_failed_reason: logging.info(auth_failed_reason) return None # Structurally, all the cases where we don't have an authenticated # email for the user should be handled above; this assertion helps # prevent any violations of that contract from resulting in a user # being incorrectly authenticated. assert return_data.get('valid_attestation') is True strategy = backend.strategy # type: ignore # This comes from Python Social Auth. email_address = return_data['validated_email'] full_name = return_data['full_name'] is_signup = strategy.session_get('is_signup') == '1' redirect_to = strategy.session_get('next') realm = Realm.objects.get(id=return_data["realm_id"]) mobile_flow_otp = strategy.session_get('mobile_flow_otp') if mobile_flow_otp is not None: return login_or_register_remote_user( strategy.request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup, redirect_to=redirect_to) return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup, redirect_to=redirect_to)
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 social_auth_finish(backend: Any, details: Dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any) -> Optional[UserProfile]: """Given the determination in social_auth_associate_user for whether the user should be authenticated, this takes care of actually logging in the user (if appropriate) and redirecting the browser to the appropriate next page depending on the situation. Read the comments below as well as login_or_register_remote_user in `zerver/views/auth.py` for the details on how that dispatch works. """ from zerver.views.auth import (login_or_register_remote_user, redirect_and_log_into_subdomain) user_profile = kwargs['user_profile'] return_data = kwargs['return_data'] no_verified_email = return_data.get("email_not_verified") auth_backend_disabled = return_data.get('auth_backend_disabled') inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_realm = return_data.get('invalid_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') auth_failed_reason = return_data.get("social_auth_failed_reason") if invalid_realm: from zerver.views.auth import redirect_to_subdomain_login_url return redirect_to_subdomain_login_url() if auth_backend_disabled or inactive_user or inactive_realm or no_verified_email: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning( "{} got invalid email argument.".format(backend.auth_backend_name) ) return None if auth_failed_reason: logging.info(auth_failed_reason) return None # Structurally, all the cases where we don't have an authenticated # email for the user should be handled above; this assertion helps # prevent any violations of that contract from resulting in a user # being incorrectly authenticated. assert return_data.get('valid_attestation') is True strategy = backend.strategy # type: ignore # This comes from Python Social Auth. email_address = return_data['validated_email'] full_name = return_data['full_name'] is_signup = strategy.session_get('is_signup') == '1' redirect_to = strategy.session_get('next') realm = Realm.objects.get(id=return_data["realm_id"]) multiuse_object_key = strategy.session_get('multiuse_object_key', '') mobile_flow_otp = strategy.session_get('mobile_flow_otp') # At this point, we have now confirmed that the user has # demonstrated control over the target email address. # # The next step is to call login_or_register_remote_user, but # there are two code paths here because of an optimization to save # a redirect on mobile. if mobile_flow_otp is not None: # For mobile app authentication, login_or_register_remote_user # will redirect to a special zulip:// URL that is handled by # the app after a successful authentication; so we can # redirect directly from here, saving a round trip over what # we need to do to create session cookies on the right domain # in the web login flow (below). return login_or_register_remote_user(strategy.request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup, redirect_to=redirect_to) # If this authentication code were executing on # subdomain.zulip.example.com, we would just call # login_or_register_remote_user as in the mobile code path. # However, because third-party SSO providers generally don't allow # wildcard addresses in their redirect URLs, for multi-realm # servers, we will have just completed authentication on e.g. # auth.zulip.example.com (depending on # settings.SOCIAL_AUTH_SUBDOMAIN), which cannot store cookies on # the subdomain.zulip.example.com domain. So instead we serve a # redirect (encoding the authentication result data in a # cryptographically signed token) to a route on # subdomain.zulip.example.com that will verify the signature and # then call login_or_register_remote_user. return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup, redirect_to=redirect_to, multiuse_object_key=multiuse_object_key)
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) } )
def social_auth_finish(backend: Any, details: Dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any) -> Optional[UserProfile]: from zerver.views.auth import (login_or_register_remote_user, redirect_and_log_into_subdomain) user_profile = kwargs['user_profile'] return_data = kwargs['return_data'] auth_backend_disabled = return_data.get('auth_backend_disabled') inactive_user = return_data.get('inactive_user') inactive_realm = return_data.get('inactive_realm') invalid_realm = return_data.get('invalid_realm') invalid_subdomain = return_data.get('invalid_subdomain') invalid_email = return_data.get('invalid_email') auth_failed_reason = return_data.get("social_auth_failed_reason") if invalid_realm: from zerver.views.auth import redirect_to_subdomain_login_url return redirect_to_subdomain_login_url() if auth_backend_disabled or inactive_user or inactive_realm: # Redirect to login page. We can't send to registration # workflow with these errors. We will redirect to login page. return None if invalid_email: # In case of invalid email, we will end up on registration page. # This seems better than redirecting to login page. logging.warning( "{} got invalid email argument.".format(backend.auth_backend_name) ) return None if auth_failed_reason: logging.info(auth_failed_reason) return None # Structurally, all the cases where we don't have an authenticated # email for the user should be handled above; this assertion helps # prevent any violations of that contract from resulting in a user # being incorrectly authenticated. assert return_data.get('valid_attestation') is True strategy = backend.strategy # type: ignore # This comes from Python Social Auth. email_address = return_data['validated_email'] full_name = return_data['full_name'] is_signup = strategy.session_get('is_signup') == '1' redirect_to = strategy.session_get('next') realm = return_data["realm"] mobile_flow_otp = strategy.session_get('mobile_flow_otp') if mobile_flow_otp is not None: return login_or_register_remote_user(strategy.request, email_address, user_profile, full_name, invalid_subdomain=bool(invalid_subdomain), mobile_flow_otp=mobile_flow_otp, is_signup=is_signup, redirect_to=redirect_to) return redirect_and_log_into_subdomain(realm, full_name, email_address, is_signup=is_signup, redirect_to=redirect_to)