Resolves reuse of invitation code
This commit is contained in:
parent
5248a252ec
commit
aa17f9679b
|
@ -98,12 +98,28 @@ class ManageUserSerializer(serializers.ModelSerializer):
|
||||||
class ManageInvitationSerializer(serializers.ModelSerializer):
|
class ManageInvitationSerializer(serializers.ModelSerializer):
|
||||||
users = ManageUserSimpleSerializer(many=True, required=False)
|
users = ManageUserSimpleSerializer(many=True, required=False)
|
||||||
owner = ManageUserSimpleSerializer(required=False)
|
owner = ManageUserSimpleSerializer(required=False)
|
||||||
|
invited_user = ManageUserSimpleSerializer(required=False)
|
||||||
code = serializers.CharField(required=False, allow_null=True)
|
code = serializers.CharField(required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users_models.Invitation
|
model = users_models.Invitation
|
||||||
fields = ("id", "owner", "code", "expiration_date", "creation_date", "users")
|
fields = (
|
||||||
read_only_fields = ["id", "expiration_date", "owner", "creation_date", "users"]
|
"id",
|
||||||
|
"owner",
|
||||||
|
"invited_user",
|
||||||
|
"code",
|
||||||
|
"expiration_date",
|
||||||
|
"creation_date",
|
||||||
|
"users",
|
||||||
|
)
|
||||||
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"expiration_date",
|
||||||
|
"owner",
|
||||||
|
"invited_user",
|
||||||
|
"creation_date",
|
||||||
|
"users",
|
||||||
|
]
|
||||||
|
|
||||||
def validate_code(self, value):
|
def validate_code(self, value):
|
||||||
if not value:
|
if not value:
|
||||||
|
|
|
@ -57,6 +57,9 @@ class InvitationFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
|
||||||
|
|
||||||
class Params:
|
class Params:
|
||||||
expired = factory.Trait(expiration_date=factory.LazyFunction(timezone.now))
|
expired = factory.Trait(expiration_date=factory.LazyFunction(timezone.now))
|
||||||
|
with_invited_user = factory.Trait(
|
||||||
|
invited_user=factory.SubFactory("funkwhale_api.users.factories.UserFactory")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PasswordSetter(factory.PostGenerationMethodCall):
|
class PasswordSetter(factory.PostGenerationMethodCall):
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 3.2.16 on 2022-11-19 18:19
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
def update_invitation_table(apps, schema_editor):
|
||||||
|
User = apps.get_model("users", "User")
|
||||||
|
Invitation = apps.get_model("users", "Invitation")
|
||||||
|
for user in User.objects.all():
|
||||||
|
if user.invitation:
|
||||||
|
Invitation.objects.filter(id=user.invitation.id).update(invited_user_id=user.id)
|
||||||
|
|
||||||
|
|
||||||
|
def rewind(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0021_auto_20210703_1810'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='invitation',
|
||||||
|
name='invited_user',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_invitations', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invitation',
|
||||||
|
name='owner',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.RunPython(update_invitation_table, rewind),
|
||||||
|
]
|
|
@ -338,6 +338,9 @@ class Invitation(models.Model):
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
User, related_name="invitations", on_delete=models.CASCADE
|
User, related_name="invitations", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
invited_user = models.ForeignKey(
|
||||||
|
User, related_name="user_invitations", null=True, on_delete=models.CASCADE
|
||||||
|
)
|
||||||
code = models.CharField(max_length=50, unique=True)
|
code = models.CharField(max_length=50, unique=True)
|
||||||
|
|
||||||
objects = InvitationQuerySet.as_manager()
|
objects = InvitationQuerySet.as_manager()
|
||||||
|
@ -352,6 +355,10 @@ class Invitation(models.Model):
|
||||||
|
|
||||||
return super().save(**kwargs)
|
return super().save(**kwargs)
|
||||||
|
|
||||||
|
def set_invited_user(self, user):
|
||||||
|
self.invited_user = user
|
||||||
|
super().save()
|
||||||
|
|
||||||
|
|
||||||
class Application(oauth2_models.AbstractApplication):
|
class Application(oauth2_models.AbstractApplication):
|
||||||
scope = models.TextField(blank=True)
|
scope = models.TextField(blank=True)
|
||||||
|
|
|
@ -40,6 +40,9 @@ class RegisterView(registration_views.RegisterView):
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
# manual approval, we need to send the confirmation e-mail by hand
|
# manual approval, we need to send the confirmation e-mail by hand
|
||||||
authentication.send_email_confirmation(self.request, user)
|
authentication.send_email_confirmation(self.request, user)
|
||||||
|
if user.invitation:
|
||||||
|
user.invitation.set_invited_user(user)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,13 @@ def test_invitation_generates_random_code_on_save(factories):
|
||||||
assert len(invitation.code) >= 6
|
assert len(invitation.code) >= 6
|
||||||
|
|
||||||
|
|
||||||
|
def test_invitation_get_deleted_when_user_is_deleted(factories):
|
||||||
|
invitation = factories["users.Invitation"](with_invited_user=True)
|
||||||
|
invitation.invited_user.delete()
|
||||||
|
|
||||||
|
assert models.Invitation.objects.count() == 0
|
||||||
|
|
||||||
|
|
||||||
def test_invitation_expires_after_delay(factories, settings):
|
def test_invitation_expires_after_delay(factories, settings):
|
||||||
delay = settings.USERS_INVITATION_EXPIRATION_DAYS
|
delay = settings.USERS_INVITATION_EXPIRATION_DAYS
|
||||||
invitation = factories["users.Invitation"]()
|
invitation = factories["users.Invitation"]()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed invitation reuse after the invited user has been deleted (#1952)
|
|
@ -75,6 +75,11 @@
|
||||||
Owner
|
Owner
|
||||||
</translate>
|
</translate>
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
<translate translate-context="Content/Admin/Table.Label/Noun">
|
||||||
|
User
|
||||||
|
</translate>
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<translate translate-context="*/*/*">
|
<translate translate-context="*/*/*">
|
||||||
Status
|
Status
|
||||||
|
@ -105,6 +110,11 @@
|
||||||
{{ scope.obj.owner.username }}
|
{{ scope.obj.owner.username }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="scope.obj.invited_user">
|
||||||
|
{{ scope.obj.invited_user.username }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
v-if="scope.obj.users.length > 0"
|
v-if="scope.obj.users.length > 0"
|
||||||
|
|
Loading…
Reference in New Issue