remote_billing: Add some useful fields to Remote...User models.

These are useful for auditing and follow what we have for UserProfile.
And is_active will be used in the future when we add user deactivation.
This commit is contained in:
Mateusz Mandera 2023-12-11 14:30:07 +01:00 committed by Tim Abbott
parent 9c66b4a660
commit c800951966
4 changed files with 129 additions and 6 deletions

View File

@ -19,7 +19,13 @@ from zerver.lib.remote_server import send_realms_only_to_push_bouncer
from zerver.lib.test_classes import BouncerTestCase from zerver.lib.test_classes import BouncerTestCase
from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.timestamp import datetime_to_timestamp
from zerver.models import UserProfile from zerver.models import UserProfile
from zilencer.models import RemoteRealm, RemoteRealmBillingUser, RemoteServerBillingUser from zilencer.models import (
PreregistrationRemoteRealmBillingUser,
PreregistrationRemoteServerBillingUser,
RemoteRealm,
RemoteRealmBillingUser,
RemoteServerBillingUser,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
@ -94,6 +100,10 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
self.assertEqual(remote_billing_user.user_uuid, user.uuid) self.assertEqual(remote_billing_user.user_uuid, user.uuid)
self.assertEqual(remote_billing_user.email, user.delivery_email) self.assertEqual(remote_billing_user.email, user.delivery_email)
prereg_user = PreregistrationRemoteRealmBillingUser.objects.latest("id")
self.assertEqual(prereg_user.created_user, remote_billing_user)
self.assertEqual(remote_billing_user.date_joined, now)
# Now we should be redirected again to the /remote-billing-login/ endpoint # Now we should be redirected again to the /remote-billing-login/ endpoint
# with a new signed_access_token. Now that the email has been confirmed, # with a new signed_access_token. Now that the email has been confirmed,
# and we have a RemoteRealmBillingUser entry, we'll be in the same position # and we have a RemoteRealmBillingUser entry, we'll be in the same position
@ -114,7 +124,8 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
if confirm_tos: if confirm_tos:
params = {"tos_consent": "true"} params = {"tos_consent": "true"}
result = self.client_post(signed_auth_url, params, subdomain="selfhosting") with time_machine.travel(now, tick=False):
result = self.client_post(signed_auth_url, params, subdomain="selfhosting")
if result.status_code >= 400: if result.status_code >= 400:
# Failures should be returned early so the caller can assert about them. # Failures should be returned early so the caller can assert about them.
return result return result
@ -139,6 +150,8 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
identity_dict, identity_dict,
) )
self.assertEqual(remote_billing_user.last_login, now)
# It's up to the caller to verify further details, such as the exact redirect URL, # It's up to the caller to verify further details, such as the exact redirect URL,
# depending on the set up and intent of the test. # depending on the set up and intent of the test.
return result return result
@ -543,6 +556,8 @@ class LegacyServerLoginTest(BouncerTestCase):
identity_dict, identity_dict,
) )
self.assertEqual(remote_billing_user.last_login, now)
return result return result
def test_server_login_get(self) -> None: def test_server_login_get(self) -> None:
@ -586,9 +601,11 @@ class LegacyServerLoginTest(BouncerTestCase):
def test_server_login_success_with_no_plan(self) -> None: def test_server_login_success_with_no_plan(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
result = self.execute_remote_billing_authentication_flow( now = timezone_now()
hamlet.delivery_email, hamlet.full_name, expect_tos=True, confirm_tos=True with time_machine.travel(now, tick=False):
) result = self.execute_remote_billing_authentication_flow(
hamlet.delivery_email, hamlet.full_name, expect_tos=True, confirm_tos=True
)
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], f"/server/{self.uuid}/plans/") self.assertEqual(result["Location"], f"/server/{self.uuid}/plans/")
@ -604,6 +621,15 @@ class LegacyServerLoginTest(BouncerTestCase):
result = self.client_get(result["Location"], subdomain="selfhosting") result = self.client_get(result["Location"], subdomain="selfhosting")
self.assert_in_success_response([f"Upgrade {self.server.hostname}"], result) self.assert_in_success_response([f"Upgrade {self.server.hostname}"], result)
# Verify the RemoteServerBillingUser and PreRegistrationRemoteServerBillingUser
# objects created in the process.
remote_billing_user = RemoteServerBillingUser.objects.latest("id")
self.assertEqual(remote_billing_user.email, hamlet.delivery_email)
prereg_user = PreregistrationRemoteServerBillingUser.objects.latest("id")
self.assertEqual(prereg_user.created_user, remote_billing_user)
self.assertEqual(remote_billing_user.date_joined, now)
def test_server_login_success_consent_is_not_re_asked(self) -> None: def test_server_login_success_consent_is_not_re_asked(self) -> None:
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
result = self.execute_remote_billing_authentication_flow( result = self.execute_remote_billing_authentication_flow(

View File

@ -249,7 +249,8 @@ def remote_realm_billing_finalize_login(
if full_name is not None: if full_name is not None:
remote_user.full_name = full_name remote_user.full_name = full_name
remote_user.tos_version = settings.TERMS_OF_SERVICE_VERSION remote_user.tos_version = settings.TERMS_OF_SERVICE_VERSION
remote_user.save(update_fields=["full_name", "tos_version"]) remote_user.last_login = timezone_now()
remote_user.save(update_fields=["full_name", "tos_version", "last_login"])
identity_dict["remote_billing_user_id"] = remote_user.id identity_dict["remote_billing_user_id"] = remote_user.id
request.session["remote_billing_identities"] = {} request.session["remote_billing_identities"] = {}
@ -370,6 +371,8 @@ def remote_realm_billing_from_login_confirmation_link(
remote_realm=remote_realm, remote_realm=remote_realm,
user_uuid=prereg_object.user_uuid, user_uuid=prereg_object.user_uuid,
) )
prereg_object.created_user = remote_billing_user
prereg_object.save(update_fields=["created_user"])
identity_dict = RemoteBillingIdentityDict( identity_dict = RemoteBillingIdentityDict(
user=RemoteBillingUserDict( user=RemoteBillingUserDict(
@ -608,6 +611,12 @@ def remote_billing_legacy_server_from_login_confirmation_link(
email=prereg_object.email, email=prereg_object.email,
remote_server=remote_server, remote_server=remote_server,
) )
if created:
prereg_object.created_user = remote_billing_user
prereg_object.save(update_fields=["created_user"])
remote_billing_user.last_login = timezone_now()
remote_billing_user.save(update_fields=["last_login"])
# Refresh IdentityDict in the session. (Or create it # Refresh IdentityDict in the session. (Or create it
# if the user came here e.g. in a different browser than they # if the user came here e.g. in a different browser than they

View File

@ -0,0 +1,72 @@
# Generated by Django 4.2.8 on 2023-12-11 12:55
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zilencer", "0049_alter_remoterealmbillinguser_unique_together_and_more"),
]
operations = [
migrations.AddField(
model_name="preregistrationremoterealmbillinguser",
name="created_user",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="zilencer.remoterealmbillinguser",
),
),
migrations.AddField(
model_name="preregistrationremoterealmbillinguser",
name="date_joined",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="preregistrationremoteserverbillinguser",
name="created_user",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="zilencer.remoteserverbillinguser",
),
),
migrations.AddField(
model_name="preregistrationremoteserverbillinguser",
name="date_joined",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="remoterealmbillinguser",
name="date_joined",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="remoterealmbillinguser",
name="is_active",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="remoterealmbillinguser",
name="last_login",
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name="remoteserverbillinguser",
name="date_joined",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="remoteserverbillinguser",
name="is_active",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="remoteserverbillinguser",
name="last_login",
field=models.DateTimeField(null=True),
),
]

View File

@ -161,6 +161,8 @@ class AbstractRemoteRealmBillingUser(models.Model):
user_uuid = models.UUIDField() user_uuid = models.UUIDField()
email = models.EmailField() email = models.EmailField()
date_joined = models.DateTimeField(default=timezone_now)
class Meta: class Meta:
abstract = True abstract = True
@ -168,6 +170,10 @@ class AbstractRemoteRealmBillingUser(models.Model):
class RemoteRealmBillingUser(AbstractRemoteRealmBillingUser): class RemoteRealmBillingUser(AbstractRemoteRealmBillingUser):
full_name = models.TextField(default="") full_name = models.TextField(default="")
last_login = models.DateTimeField(null=True)
is_active = models.BooleanField(default=True)
TOS_VERSION_BEFORE_FIRST_LOGIN = UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN TOS_VERSION_BEFORE_FIRST_LOGIN = UserProfile.TOS_VERSION_BEFORE_FIRST_LOGIN
tos_version = models.TextField(default=TOS_VERSION_BEFORE_FIRST_LOGIN) tos_version = models.TextField(default=TOS_VERSION_BEFORE_FIRST_LOGIN)
@ -188,12 +194,16 @@ class PreregistrationRemoteRealmBillingUser(AbstractRemoteRealmBillingUser):
next_page = models.TextField(null=True) next_page = models.TextField(null=True)
uri_scheme = models.TextField() uri_scheme = models.TextField()
created_user = models.ForeignKey(RemoteRealmBillingUser, null=True, on_delete=models.SET_NULL)
class AbstractRemoteServerBillingUser(models.Model): class AbstractRemoteServerBillingUser(models.Model):
remote_server = models.ForeignKey(RemoteZulipServer, on_delete=models.CASCADE) remote_server = models.ForeignKey(RemoteZulipServer, on_delete=models.CASCADE)
email = models.EmailField() email = models.EmailField()
date_joined = models.DateTimeField(default=timezone_now)
class Meta: class Meta:
abstract = True abstract = True
@ -201,6 +211,10 @@ class AbstractRemoteServerBillingUser(models.Model):
class RemoteServerBillingUser(AbstractRemoteServerBillingUser): class RemoteServerBillingUser(AbstractRemoteServerBillingUser):
full_name = models.TextField(default="") full_name = models.TextField(default="")
last_login = models.DateTimeField(null=True)
is_active = models.BooleanField(default=True)
class Meta: class Meta:
unique_together = [ unique_together = [
("remote_server", "email"), ("remote_server", "email"),
@ -214,6 +228,8 @@ class PreregistrationRemoteServerBillingUser(AbstractRemoteServerBillingUser):
next_page = models.TextField(null=True) next_page = models.TextField(null=True)
created_user = models.ForeignKey(RemoteServerBillingUser, null=True, on_delete=models.SET_NULL)
class RemoteZulipServerAuditLog(AbstractRealmAuditLog): class RemoteZulipServerAuditLog(AbstractRealmAuditLog):
"""Audit data associated with a remote Zulip server (not specific to a """Audit data associated with a remote Zulip server (not specific to a