Skip to content

Commit

Permalink
update invite link reuse handling (#1352, #1354, #1361)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Feb 15, 2024
1 parent ce68638 commit 2e384e9
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Changed
- **Projectroles**
- Improve remote site deletion UI text labels (#1349)
- Store remote sync app setting foreign key UUIDs as strings (#1356)
- Do not create timeline event for re-accepting project invite (#1352)
- Improve user message for re-accepting project invite (#1354)
- Redirect to ``ProjectDetailView`` from re-accepting project invite (#1361)

Fixed
-----
Expand Down
1 change: 1 addition & 0 deletions docs/source/major_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Release Highlights
==================

- Add missing LDAP settings in siteinfo
- Improve project invite accept link reuse handling
- Fix remote sync crash with target sites using SODAR Core <0.13.3
- General bug fixes and minor updates

Expand Down
15 changes: 14 additions & 1 deletion projectroles/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4347,6 +4347,9 @@ def test_get_role_exists(self):
)
self.make_assignment(self.project, invited_user, self.role_guest)
self.assertTrue(invite.active)
self.assertIsNone(
ProjectEvent.objects.filter(event_name='invite_accept').first()
)

with self.login(invited_user):
response = self.client.get(
Expand All @@ -4356,9 +4359,19 @@ def test_get_role_exists(self):
),
follow=True,
)
self.assertRedirects(response, reverse('home'))
self.assertRedirects(
response,
reverse(
'projectroles:detail',
kwargs={'project': self.project.sodar_uuid},
),
)
invite.refresh_from_db()
self.assertFalse(invite.active)
# No timeline event should be created
self.assertIsNone(
ProjectEvent.objects.filter(event_name='invite_accept').first()
)


class TestProjectInviteListView(
Expand Down
48 changes: 24 additions & 24 deletions projectroles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2359,16 +2359,18 @@ def get_invite(self, secret):
except ProjectInvite.DoesNotExist:
messages.error(self.request, 'Invite does not exist.')

def user_role_exists(self, invite, user, timeline=None):
def user_role_exists(self, invite, user):
"""
Display message and revoke invite if user already has roles in project.
Display message if user already has roles in project. Also revoke the
invite if necessary.
"""
if invite.project.has_role(user):
messages.warning(
self.request,
mark_safe(
'You already have roles set in the {project}. You can '
'access the {project} <a href="{url}">here</a>.'.format(
'You are already a member of this {project}. '
'<a href="{url}">Please use this URL to access the '
'{project}</a>.'.format(
project=get_display_name(invite.project.type),
url=reverse(
'projectroles:detail',
Expand All @@ -2377,15 +2379,8 @@ def user_role_exists(self, invite, user, timeline=None):
)
),
)
self.revoke_invite(
invite,
user,
failed=True,
fail_desc='User already has roles in {}'.format(
get_display_name(invite.project.type)
),
timeline=timeline,
)
if invite.active: # Only revoke if active
self.revoke_invite(invite, user)
return True
return False

Expand Down Expand Up @@ -2482,16 +2477,20 @@ def get(self, *args, **kwargs):
invite = self.get_invite(secret=kwargs['secret'])
if not invite:
return redirect(reverse('home'))
timeline = get_backend_api('timeline_backend')
user = self.request.user

if (
not user.is_anonymous
and user.is_authenticated
and user.email == invite.email
and self.user_role_exists(invite, user, timeline)
and self.user_role_exists(invite, user)
):
return redirect(reverse('home'))
return redirect(
reverse(
'projectroles:detail',
kwargs={'project': invite.project.sodar_uuid},
)
)

invite_type = self.get_invite_type(invite)
if invite_type == 'ldap':
Expand Down Expand Up @@ -2525,24 +2524,25 @@ def get(self, *args, **kwargs):
if not invite:
return redirect(reverse('home'))
timeline = get_backend_api('timeline_backend')

# Check invite has correct type
# Check if invite has correct type
if self.get_invite_type(invite) == 'local':
messages.error(
self.request,
'Invite was issued for local user, but LDAP invite view was '
'requested.',
)
return redirect(reverse('home'))

# Check if user already accepted the invite
if self.user_role_exists(invite, self.request.user, timeline=timeline):
return redirect(reverse('home'))

if self.user_role_exists(invite, self.request.user):
return redirect(
reverse(
'projectroles:detail',
kwargs={'project': invite.project.sodar_uuid},
)
)
# Check if invite expired
if self.is_invite_expired(invite, self.request.user):
return redirect(reverse('home'))

# If we get this far, create RoleAssignment..
if not self.create_assignment(
invite, self.request.user, timeline=timeline
Expand Down Expand Up @@ -2683,7 +2683,7 @@ def form_valid(self, form):
first_name=form.cleaned_data['first_name'],
last_name=form.cleaned_data['last_name'],
)
if self.user_role_exists(invite, user, timeline=timeline):
if self.user_role_exists(invite, user):
return redirect(
reverse(
'projectroles:detail',
Expand Down

0 comments on commit 2e384e9

Please sign in to comment.