mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	billing: Offer release announcement subscriptions.
Also avoid prompting for full name time more than once. Adds TOS version field to Remote server user. Co-authored-by: Karl Stolley <karl@zulip.com> Co-authored-by: Aman Agrawal <amanagr@zulip.com>
This commit is contained in:
		@@ -133,6 +133,15 @@ def get_identity_dict_from_signed_access_token(
 | 
			
		||||
    return identity_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_tos_consent_needed_for_user(
 | 
			
		||||
    remote_user: Union[RemoteRealmBillingUser, RemoteServerBillingUser]
 | 
			
		||||
) -> bool:
 | 
			
		||||
    assert settings.TERMS_OF_SERVICE_VERSION is not None
 | 
			
		||||
    return int(settings.TERMS_OF_SERVICE_VERSION.split(".")[0]) > int(
 | 
			
		||||
        remote_user.tos_version.split(".")[0]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@self_hosting_management_endpoint
 | 
			
		||||
@typed_endpoint
 | 
			
		||||
def remote_realm_billing_finalize_login(
 | 
			
		||||
@@ -141,6 +150,8 @@ def remote_realm_billing_finalize_login(
 | 
			
		||||
    signed_billing_access_token: PathOnly[str],
 | 
			
		||||
    full_name: Optional[str] = None,
 | 
			
		||||
    tos_consent: Literal[None, "true"] = None,
 | 
			
		||||
    enable_major_release_emails: Literal[None, "true", "false"] = None,
 | 
			
		||||
    enable_maintenance_release_emails: Literal[None, "true", "false"] = None,
 | 
			
		||||
) -> HttpResponse:
 | 
			
		||||
    """
 | 
			
		||||
    This is the endpoint accessed via the billing_access_url, generated by
 | 
			
		||||
@@ -193,9 +204,7 @@ def remote_realm_billing_finalize_login(
 | 
			
		||||
            remote_realm=remote_realm,
 | 
			
		||||
            user_uuid=user_uuid,
 | 
			
		||||
        )
 | 
			
		||||
        tos_consent_needed = int(settings.TERMS_OF_SERVICE_VERSION.split(".")[0]) > int(
 | 
			
		||||
            remote_user.tos_version.split(".")[0]
 | 
			
		||||
        )
 | 
			
		||||
        tos_consent_needed = is_tos_consent_needed_for_user(remote_user)
 | 
			
		||||
    except RemoteRealmBillingUser.DoesNotExist:
 | 
			
		||||
        # This is the first time this user is logging in.
 | 
			
		||||
        remote_user = None
 | 
			
		||||
@@ -243,7 +252,7 @@ def remote_realm_billing_finalize_login(
 | 
			
		||||
        # Users logging in for the first time need to be created and follow
 | 
			
		||||
        # a different path - they should not be POSTing here. It should be impossible
 | 
			
		||||
        # to get here with a remote_user that is None without tampering with the form
 | 
			
		||||
        # or manualling crafting a POST request.
 | 
			
		||||
        # or manually crafting a POST request.
 | 
			
		||||
        raise JsonableError(_("User account doesn't exist yet."))
 | 
			
		||||
 | 
			
		||||
    if tos_consent_needed and not tos_consent_given:
 | 
			
		||||
@@ -251,14 +260,25 @@ def remote_realm_billing_finalize_login(
 | 
			
		||||
        # don't need a pretty error.
 | 
			
		||||
        raise JsonableError(_("You must accept the Terms of Service to proceed."))
 | 
			
		||||
 | 
			
		||||
    # The current approach is to update the full_name
 | 
			
		||||
    # based on what the user entered in the login confirmation form.
 | 
			
		||||
    # Usually they'll presumably just use the name already set for this object.
 | 
			
		||||
    # The current approach is to update the full_name and email preferences
 | 
			
		||||
    # only when the user first logs in.
 | 
			
		||||
    if full_name is not None:
 | 
			
		||||
        remote_user.full_name = full_name
 | 
			
		||||
        remote_user.enable_major_release_emails = enable_major_release_emails == "true"
 | 
			
		||||
        remote_user.enable_maintenance_release_emails = enable_maintenance_release_emails == "true"
 | 
			
		||||
 | 
			
		||||
    remote_user.tos_version = settings.TERMS_OF_SERVICE_VERSION
 | 
			
		||||
    remote_user.last_login = timezone_now()
 | 
			
		||||
    remote_user.save(update_fields=["full_name", "tos_version", "last_login"])
 | 
			
		||||
 | 
			
		||||
    remote_user.save(
 | 
			
		||||
        update_fields=[
 | 
			
		||||
            "full_name",
 | 
			
		||||
            "tos_version",
 | 
			
		||||
            "last_login",
 | 
			
		||||
            "enable_maintenance_release_emails",
 | 
			
		||||
            "enable_major_release_emails",
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    identity_dict["remote_billing_user_id"] = remote_user.id
 | 
			
		||||
    request.session["remote_billing_identities"] = {}
 | 
			
		||||
@@ -580,6 +600,8 @@ def remote_billing_legacy_server_from_login_confirmation_link(
 | 
			
		||||
    confirmation_key: PathOnly[str],
 | 
			
		||||
    full_name: Optional[str] = None,
 | 
			
		||||
    tos_consent: Literal[None, "true"] = None,
 | 
			
		||||
    enable_major_release_emails: Literal[None, "true", "false"] = None,
 | 
			
		||||
    enable_maintenance_release_emails: Literal[None, "true", "false"] = None,
 | 
			
		||||
) -> HttpResponse:
 | 
			
		||||
    """
 | 
			
		||||
    The user comes here via the confirmation link they received via email.
 | 
			
		||||
@@ -602,14 +624,18 @@ def remote_billing_legacy_server_from_login_confirmation_link(
 | 
			
		||||
 | 
			
		||||
    # If this user (identified by email) already did this flow, meaning the have a RemoteServerBillingUser,
 | 
			
		||||
    # then we don't re-do the ToS consent  again.
 | 
			
		||||
    tos_consent_needed = not RemoteServerBillingUser.objects.filter(
 | 
			
		||||
    remote_billing_user = RemoteServerBillingUser.objects.filter(
 | 
			
		||||
        remote_server=remote_server, email=prereg_object.email
 | 
			
		||||
    ).exists()
 | 
			
		||||
    ).first()
 | 
			
		||||
    tos_consent_needed = remote_billing_user is None or is_tos_consent_needed_for_user(
 | 
			
		||||
        remote_billing_user
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if request.method == "GET":
 | 
			
		||||
        context = {
 | 
			
		||||
            "remote_server_uuid": remote_server_uuid,
 | 
			
		||||
            "host": remote_server.hostname,
 | 
			
		||||
            "user_full_name": getattr(remote_billing_user, "full_name", None),
 | 
			
		||||
            "user_email": prereg_object.email,
 | 
			
		||||
            "tos_consent_needed": tos_consent_needed,
 | 
			
		||||
            "action_url": reverse(
 | 
			
		||||
@@ -632,12 +658,17 @@ def remote_billing_legacy_server_from_login_confirmation_link(
 | 
			
		||||
        # don't need a pretty error.
 | 
			
		||||
        raise JsonableError(_("You must accept the Terms of Service to proceed."))
 | 
			
		||||
 | 
			
		||||
    remote_billing_user, created = RemoteServerBillingUser.objects.update_or_create(
 | 
			
		||||
        defaults={"full_name": full_name},
 | 
			
		||||
        email=prereg_object.email,
 | 
			
		||||
        remote_server=remote_server,
 | 
			
		||||
    )
 | 
			
		||||
    if created:
 | 
			
		||||
    if remote_billing_user is None:
 | 
			
		||||
        assert full_name is not None
 | 
			
		||||
        assert settings.TERMS_OF_SERVICE_VERSION is not None
 | 
			
		||||
        remote_billing_user = RemoteServerBillingUser.objects.create(
 | 
			
		||||
            full_name=full_name,
 | 
			
		||||
            email=prereg_object.email,
 | 
			
		||||
            remote_server=remote_server,
 | 
			
		||||
            tos_version=settings.TERMS_OF_SERVICE_VERSION,
 | 
			
		||||
            enable_major_release_emails=enable_major_release_emails == "true",
 | 
			
		||||
            enable_maintenance_release_emails=enable_maintenance_release_emails == "true",
 | 
			
		||||
        )
 | 
			
		||||
        prereg_object.created_user = remote_billing_user
 | 
			
		||||
        prereg_object.save(update_fields=["created_user"])
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,14 @@
 | 
			
		||||
                {{ csrf_input }}
 | 
			
		||||
                <div class="input-box remote-billing-confirm-login-form-field">
 | 
			
		||||
                    <label for="full_name" class="inline-block label-title">Full name</label>
 | 
			
		||||
                    <input id="full_name" name="full_name" class="required" type="text" {% if user_full_name %}value="{{ user_full_name }}"{% endif %} />
 | 
			
		||||
                    {% if not user_full_name %}
 | 
			
		||||
                    <input id="full_name" name="full_name" class="required" type="text" />
 | 
			
		||||
                    <div id="remote-billing-confirm-login-form-full_name-error" class="alert alert-danger remote-billing-confirm-login-form-field-error full_name-error"></div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="not-editable-realm-field">
 | 
			
		||||
                        {{ user_full_name }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="input-box remote-billing-confirm-login-form-field no-validation">
 | 
			
		||||
                    <label for="user-email" class="inline-block label-title">
 | 
			
		||||
@@ -37,9 +43,24 @@
 | 
			
		||||
                        {{ user_email }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <!-- user_full_name is not present only when user first logs in which also perfect to set email preferences  -->
 | 
			
		||||
                {% if not user_full_name %}
 | 
			
		||||
                <div class="input-group remote-billing-confirm-email-subscription-form-field">
 | 
			
		||||
                    <label for="enable-major-release-emails" class="checkbox">
 | 
			
		||||
                        <input id="enable-major-release-emails" name="enable_major_release_emails" type="checkbox" value="true" checked="checked" />
 | 
			
		||||
                        <span></span>
 | 
			
		||||
                        Sign me up for emails about <strong>major Zulip releases</strong> and other big announcements (a few times a year)
 | 
			
		||||
                    </label>
 | 
			
		||||
                    <label for="enable-maintenance-release-emails" class="checkbox">
 | 
			
		||||
                        <input id="enable-maintenance-release-emails" name="enable_maintenance_release_emails" type="checkbox" value="true" checked="checked" />
 | 
			
		||||
                        <span></span>
 | 
			
		||||
                        Sign me up for emails about <strong>all Zulip releases</strong>, including security and maintenance releases (recommended for server administrators)
 | 
			
		||||
                    </label>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                {% if tos_consent_needed %}
 | 
			
		||||
                <div class="input-group terms-of-service remote-billing-confirm-login-form-field" id="remote-billing-confirm-login-tos-wrapper">
 | 
			
		||||
                    <label for="remote-billing-confirm-login-tos" class="inline-block checkbox">
 | 
			
		||||
                    <label for="remote-billing-confirm-login-tos" class="checkbox">
 | 
			
		||||
                        <input id="remote-billing-confirm-login-tos" name="tos_consent" class="required" type="checkbox" value="true" />
 | 
			
		||||
                        <span></span>
 | 
			
		||||
                        I agree to the <a href="{{ root_domain_url }}/policies/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a>.
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,20 @@ export function initialize(): void {
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $<HTMLInputElement>("#enable-major-release-emails").on("change", function () {
 | 
			
		||||
        if (this.checked) {
 | 
			
		||||
            $(this).val("true");
 | 
			
		||||
        }
 | 
			
		||||
        $(this).val("false");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $<HTMLInputElement>("#enable-maintenance-release-emails").on("change", function () {
 | 
			
		||||
        if (this.checked) {
 | 
			
		||||
            $(this).val("true");
 | 
			
		||||
        }
 | 
			
		||||
        $(this).val("false");
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -731,7 +731,7 @@ input[name="licenses"] {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#remote-billing-confirm-login-form #remote-billing-confirm-login-tos-wrapper {
 | 
			
		||||
    margin: 25px auto 10px;
 | 
			
		||||
    margin: 0 auto 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#account-deactivated-success-page-details
 | 
			
		||||
 
 | 
			
		||||
@@ -1147,6 +1147,23 @@ button#register_auth_button_gitlab {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .remote-billing-confirm-email-subscription-form-field {
 | 
			
		||||
        width: 450px;
 | 
			
		||||
        margin-top: 15px;
 | 
			
		||||
 | 
			
		||||
        .checkbox {
 | 
			
		||||
            display: block;
 | 
			
		||||
            /* Present a hanging indent on subscription copy
 | 
			
		||||
               and checkboxes. */
 | 
			
		||||
            text-indent: -26px;
 | 
			
		||||
            margin: 0 0 10px 26px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        strong {
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .org-url {
 | 
			
		||||
        margin-bottom: 5px !important;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
# Generated by Django 4.2.8 on 2023-12-14 17:16
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("zilencer", "0053_remoterealmauditlog_acting_remote_user_and_more"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="remoterealmbillinguser",
 | 
			
		||||
            name="enable_maintenance_release_emails",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="remoterealmbillinguser",
 | 
			
		||||
            name="enable_major_release_emails",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="remoteserverbillinguser",
 | 
			
		||||
            name="enable_maintenance_release_emails",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="remoteserverbillinguser",
 | 
			
		||||
            name="enable_major_release_emails",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
# Generated by Django 4.2.8 on 2023-12-14 17:27
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("zilencer", "0054_remoterealmbillinguser_enable_maintenance_release_emails_and_more"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="remoteserverbillinguser",
 | 
			
		||||
            name="tos_version",
 | 
			
		||||
            field=models.TextField(default="-1"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -195,6 +195,9 @@ class RemoteRealmBillingUser(AbstractRemoteRealmBillingUser):
 | 
			
		||||
    TOS_VERSION_BEFORE_FIRST_LOGIN = UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN
 | 
			
		||||
    tos_version = models.TextField(default=TOS_VERSION_BEFORE_FIRST_LOGIN)
 | 
			
		||||
 | 
			
		||||
    enable_major_release_emails = models.BooleanField(default=True)
 | 
			
		||||
    enable_maintenance_release_emails = models.BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        unique_together = [
 | 
			
		||||
            ("remote_realm", "user_uuid"),
 | 
			
		||||
@@ -233,6 +236,12 @@ class RemoteServerBillingUser(AbstractRemoteServerBillingUser):
 | 
			
		||||
 | 
			
		||||
    is_active = models.BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    TOS_VERSION_BEFORE_FIRST_LOGIN = UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN
 | 
			
		||||
    tos_version = models.TextField(default=TOS_VERSION_BEFORE_FIRST_LOGIN)
 | 
			
		||||
 | 
			
		||||
    enable_major_release_emails = models.BooleanField(default=True)
 | 
			
		||||
    enable_maintenance_release_emails = models.BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        unique_together = [
 | 
			
		||||
            ("remote_server", "email"),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user