diff --git a/userausfall/emails.py b/userausfall/emails.py index 45efdd2..bb257c6 100644 --- a/userausfall/emails.py +++ b/userausfall/emails.py @@ -1,8 +1,8 @@ from djeveric.emails import ConfirmationEmail -class ConfidantConfirmationEmail(ConfirmationEmail): +class TrustBridgeConfirmationEmail(ConfirmationEmail): subject = "TODO" - def get_message(self, context): - return '"token": "{token}", "uid": "{uid}", "rtid": "{rtid}", "rid": "{rid}"'.format(**context) + def get_body(self, context: dict[str]) -> str: + return "{token}".format(**context) diff --git a/userausfall/models.py b/userausfall/models.py index dbeaca5..003e675 100644 --- a/userausfall/models.py +++ b/userausfall/models.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.validators import UnicodeUsernameValidator @@ -5,8 +6,11 @@ from django.core.mail import send_mail from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from djeveric.fields import ConfirmationField +from djeveric.models import ConfirmableModelMixin from userausfall import ldap +from userausfall.emails import TrustBridgeConfirmationEmail class MissingUserAttribute(Exception): @@ -98,13 +102,24 @@ class User(PermissionsMixin, AbstractBaseUser): return ldap.create_account(self.username, raw_password) def get_or_create_trust_bridge(self): + """Returns a trust bridge instance. If it doesn't exist it is created.""" try: return self.trust_bridge except TrustBridge.DoesNotExist: return TrustBridge.objects.create(trust_taker=self) + def get_primary_email(self): + """Returns the primary email address for this user.""" + return f"{self.username}@{settings.USERAUSFALL['PRIMARY_EMAIL_DOMAIN']}" -class TrustBridge(models.Model): - is_trusted = models.BooleanField(default=False) + +class TrustBridge(ConfirmableModelMixin, models.Model): + is_trusted = ConfirmationField(email_class=TrustBridgeConfirmationEmail) trust_giver = models.ForeignKey("User", on_delete=models.SET_NULL, null=True) trust_taker = models.OneToOneField("User", on_delete=models.CASCADE, related_name="trust_bridge") + + def get_confirmation_email_recipient(self) -> str: + return self.trust_giver.get_primary_email() + + def _has_confirmation_recipient(self): + return self.trust_giver is not None diff --git a/userausfall/rest_api/serializers.py b/userausfall/rest_api/serializers.py index 3643390..3680a0f 100644 --- a/userausfall/rest_api/serializers.py +++ b/userausfall/rest_api/serializers.py @@ -1,10 +1,25 @@ from rest_framework import serializers -from userausfall.models import TrustBridge +from userausfall.models import TrustBridge, User -class TrustBridgeSerializer(serializers.ModelSerializer): +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + fields = ["username"] + # the UniqueValidator for username prevents successful validation for existing users + extra_kwargs = {"username": {"validators": []}} + + +class TrustBridgeSerializer(serializers.HyperlinkedModelSerializer): + trust_giver = UserSerializer() + class Meta: model = TrustBridge fields = ["is_trusted", "trust_giver"] read_only_fields = ["is_trusted"] + + def update(self, instance: TrustBridge, validated_data): + instance.trust_giver, _ = User.objects.get_or_create(username=validated_data["trust_giver"]["username"]) + instance.save() + return instance diff --git a/userausfall/rest_api/tests/trust_bridges.py b/userausfall/rest_api/tests/trust_bridges.py index b2b9a43..c6c8582 100644 --- a/userausfall/rest_api/tests/trust_bridges.py +++ b/userausfall/rest_api/tests/trust_bridges.py @@ -1,3 +1,4 @@ +from django.core import mail from rest_framework import status from userausfall.rest_api.tests import UserausfallAPITestCase @@ -6,9 +7,7 @@ from userausfall.tests import UserMixin class TrustBridgeTestCase(UserMixin, UserausfallAPITestCase): def test_retrieve_trust_bridge(self): - """ - Retrieve the trust bridge information of a user without an ldap account. - """ + """Retrieve the trust bridge information of a user without an ldap account.""" url = "/trust-bridge/" self.authenticate_user() response = self.client.get(self.get_api_url(url)) @@ -22,13 +21,29 @@ class TrustBridgeTestCase(UserMixin, UserausfallAPITestCase): ) def test_update_trust_bridge(self): - """ - Update the trust giver of the user's trust bridge. - """ + """Update the trust giver of the user's trust bridge.""" url = "/trust-bridge/" - other_user = self.create_user() + trust_giver = self.create_user() self.create_user() self.authenticate_user() - response = self.client.put(self.get_api_url(url), {"trust_giver": other_user.pk}) + response = self.client.put( + self.get_api_url(url), + { + "trust_giver": { + "username": trust_giver.username, + }, + }, + ) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(self.user.trust_bridge.trust_giver, other_user) + self.assertEqual(self.user.trust_bridge.trust_giver, trust_giver) + + def test_set_trust_giver(self): + """When setting a trust giver a confirmation email is sent.""" + self.trust_giver = self.create_user() + self.create_user() + trust_bridge = self.user.get_or_create_trust_bridge() + trust_bridge.trust_giver = self.trust_giver + trust_bridge.save() + + self.assertEqual(len(mail.outbox), 1) + self.assertIn(self.user.trust_bridge.get_confirmation_token(), mail.outbox[0].body) diff --git a/userausfall/settings.py b/userausfall/settings.py index 5fb82af..d9c1624 100644 --- a/userausfall/settings.py +++ b/userausfall/settings.py @@ -200,3 +200,7 @@ USERAUSFALL_LDAP = { "ADMIN_USER_DN": "cn=admin,dc=local", "ADMIN_USER_PASSWORD": os.environ.get("USERAUSFALL_LDAP_PASSWORD"), } + +USERAUSFALL = { + "PRIMARY_EMAIL_DOMAIN": "systemausfall.org", +}