accounts: Allow user to change email visibility during first login.

We now allow users to change email address visibility setting
on the "Terms of service" page during first login. This page is
not shown for users creating account using normal registration
process, but is useful for imported users and users created
through API, LDAP, SCIM and management commands.
This commit is contained in:
Sahil Batra
2023-04-24 20:21:02 +05:30
committed by Tim Abbott
parent 7f01b3fb63
commit 007a51f277
7 changed files with 132 additions and 10 deletions

View File

@@ -23,9 +23,10 @@ the registration flow has its own (nearly identical) copy of the fields below in
{{ csrf_input }}
<div id="registration-email">
<label for="id_email">{{ _("Email") }}</label>
<div class="controls fakecontrol">
<p>{{ email }}</p>
</div>
<div class="controls fakecontrol">{{ email }}</div>
{% if first_time_login %}
{% include 'zerver/new_user_email_address_visibility.html' %}
{% endif %}
</div>
{% if first_time_terms_of_service_message_template %}
@@ -36,6 +37,7 @@ the registration flow has its own (nearly identical) copy of the fields below in
</div>
{% endif %}
{% if terms_of_service %}
<div class="input-group terms-of-service">
{#
This is somewhat subtle.
@@ -57,6 +59,7 @@ the registration flow has its own (nearly identical) copy of the fields below in
{% endfor %}
{% endif %}
</div>
{% endif %}
<div class="controls">
<button id="accept_tos_button" type="submit">{{ _('Continue') }}</button>
<input type="hidden" name="next" value="{{ next }}" />
@@ -65,4 +68,9 @@ the registration flow has its own (nearly identical) copy of the fields below in
</div>
</div>
</div>
{% if first_time_login %}
{% include 'zerver/change_email_address_visibility_modal.html' %}
{% endif %}
{% endblock %}

View File

@@ -934,13 +934,22 @@ input.new-organization-button {
}
#registration-email {
margin-bottom: 18px;
& label {
float: left;
padding-top: 5px;
width: 100%;
display: block;
margin: 0;
margin-left: 2px;
margin-bottom: 0;
text-align: left;
font-weight: 600;
font-size: 1rem;
}
.controls {
padding-top: 25px;
margin: 2px;
}
}

View File

@@ -330,6 +330,11 @@ html {
margin-bottom: 20px;
font-size: 16px;
font-weight: 400;
max-width: 400px;
}
#new-user-email-address-visibility {
max-width: 400px;
}
.center-block {

View File

@@ -250,7 +250,7 @@ def check_change_bot_full_name(
@transaction.atomic(durable=True)
def do_change_tos_version(user_profile: UserProfile, tos_version: str) -> None:
def do_change_tos_version(user_profile: UserProfile, tos_version: Optional[str]) -> None:
user_profile.tos_version = tos_version
user_profile.save(update_fields=["tos_version"])
event_time = timezone_now()

View File

@@ -204,7 +204,21 @@ class RegistrationForm(RealmDetailsForm):
class ToSForm(forms.Form):
terms = forms.BooleanField(required=True)
terms = forms.BooleanField(required=False)
email_address_visibility = forms.TypedChoiceField(
required=False,
coerce=int,
empty_value=None,
choices=[
(value, name)
for value, name in UserProfile.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP.items()
],
)
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
if settings.TERMS_OF_SERVICE_VERSION is not None:
self.fields["terms"] = forms.BooleanField(required=True)
class HomepageForm(forms.Form):

View File

@@ -558,6 +558,61 @@ class HomeTest(ZulipTestCase):
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/")
user = self.example_user("hamlet")
user.tos_version = "-1"
user.save()
result = self.client_post("/accounts/accept_terms/")
self.assertEqual(result.status_code, 200)
self.assert_in_response("I agree to the", result)
self.assert_in_response(
"Administrators of this Zulip organization will be able to see this email address.",
result,
)
result = self.client_post(
"/accounts/accept_terms/",
{
"terms": True,
"email_address_visibility": UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
},
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/")
user = self.example_user("hamlet")
self.assertEqual(
user.email_address_visibility, UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS
)
def test_set_email_address_visibility_without_terms_of_service(self) -> None:
self.login("hamlet")
user = self.example_user("hamlet")
user.tos_version = "-1"
user.save()
with self.settings(TERMS_OF_SERVICE_VERSION=None):
result = self.client_get("/", dict(stream="Denmark"))
self.assertEqual(result.status_code, 200)
self.assert_in_response(
"Administrators of this Zulip organization will be able to see this email address.",
result,
)
result = self.client_post(
"/accounts/accept_terms/",
{
"email_address_visibility": UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
},
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/")
user = self.example_user("hamlet")
self.assertEqual(
user.email_address_visibility, UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS
)
def test_bad_narrow(self) -> None:
self.login("hamlet")
with self.assertLogs(level="WARNING") as m:

View File

@@ -7,7 +7,7 @@ from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect, render
from django.utils.cache import patch_cache_control
from zerver.actions.user_settings import do_change_tos_version
from zerver.actions.user_settings import do_change_tos_version, do_change_user_setting
from zerver.context_processors import get_realm_from_request, get_valid_realm_from_request
from zerver.decorator import web_public_view, zulip_login_required
from zerver.forms import ToSForm
@@ -17,13 +17,16 @@ from zerver.lib.request import RequestNotes
from zerver.lib.streams import access_stream_by_name
from zerver.lib.subdomains import get_subdomain
from zerver.lib.user_counts import realm_user_count
from zerver.models import PreregistrationUser, Realm, Stream, UserProfile
from zerver.models import PreregistrationUser, Realm, RealmUserDefault, Stream, UserProfile
def need_accept_tos(user_profile: Optional[UserProfile]) -> bool:
if user_profile is None:
return False
if user_profile.tos_version == UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN:
return True
if settings.TERMS_OF_SERVICE_VERSION is None:
return False
@@ -37,20 +40,48 @@ def accounts_accept_terms(request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = ToSForm(request.POST)
if form.is_valid():
assert settings.TERMS_OF_SERVICE_VERSION is not None
assert (
settings.TERMS_OF_SERVICE_VERSION is not None
or request.user.tos_version == UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN
)
do_change_tos_version(request.user, settings.TERMS_OF_SERVICE_VERSION)
email_address_visibility = form.cleaned_data["email_address_visibility"]
if (
email_address_visibility is not None
and email_address_visibility != request.user.email_address_visibility
):
do_change_user_setting(
request.user,
"email_address_visibility",
email_address_visibility,
acting_user=request.user,
)
return redirect(home)
else:
form = ToSForm()
default_email_address_visibility = None
first_time_login = request.user.tos_version == UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN
if first_time_login:
realm_user_default = RealmUserDefault.objects.get(realm=request.user.realm)
default_email_address_visibility = realm_user_default.email_address_visibility
context = {
"form": form,
"email": request.user.delivery_email,
# Text displayed when updating TERMS_OF_SERVICE_VERSION.
"terms_of_service_message": settings.TERMS_OF_SERVICE_MESSAGE,
"terms_of_service_version": settings.TERMS_OF_SERVICE_VERSION,
# HTML template used when agreeing to terms of service the
# first time, e.g. after data import.
"first_time_terms_of_service_message_template": None,
"first_time_login": first_time_login,
"default_email_address_visibility": default_email_address_visibility,
"email_address_visibility_options_dict": UserProfile.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP,
"email_address_visibility_admins_only": UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
"email_address_visibility_moderators": UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
"email_address_visibility_nobody": UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY,
}
if (