-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Only accept project invitations whose email match new user email (#2449)
* remove project invitation id and token verification remove invitation_id and invitation_token query params from invitation email link. remove support for allowing a user to register using a different email from the one the invite was sent to add a post_save signal to accept only invitations that match the new user email and remove implementation for accepting invitation from the UserProfileSerializer. This is because a user can also be created using OIDC * update project invitation documentation
- Loading branch information
1 parent
a85bffc
commit 784ee07
Showing
12 changed files
with
130 additions
and
526 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
from onadata.apps.main.tests.test_base import TestBase | ||
from onadata.apps.api.tasks import ( | ||
send_project_invitation_email_async, | ||
accept_project_invitation_async, | ||
) | ||
from onadata.apps.logger.models import ProjectInvitation | ||
from onadata.libs.utils.user_auth import get_user_default_project | ||
|
@@ -31,28 +30,3 @@ def test_sends_email(self, mock_send): | |
url = "https://example.com/register" | ||
send_project_invitation_email_async(self.invitation.id, url) | ||
mock_send.assert_called_once() | ||
|
||
|
||
@patch("onadata.apps.api.tasks.accept_project_invitation") | ||
class AcceptProjectInvitationTesCase(TestBase): | ||
"""Tests for accept_project_invitation_async""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
|
||
project = get_user_default_project(self.user) | ||
self.invitation = ProjectInvitation.objects.create( | ||
project=project, | ||
email="[email protected]", | ||
role="manager", | ||
) | ||
|
||
def test_accept_invitation(self, mock_accept_invitation): | ||
"""Test invitation is accepted""" | ||
accept_project_invitation_async(self.user.id, self.invitation.id) | ||
mock_accept_invitation.assert_called_once_with(self.user, self.invitation) | ||
|
||
def test_invitation_id_optional(self, mock_accept_invitation): | ||
"""invitation_id argument is optional""" | ||
accept_project_invitation_async(self.user.id) | ||
mock_accept_invitation.assert_called_once_with(self.user, None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ | |
import json | ||
import os | ||
from six.moves.urllib.parse import urlparse, parse_qs | ||
from mock import Mock | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.core.cache import cache | ||
|
@@ -32,7 +31,7 @@ | |
from onadata.apps.main.models.user_profile import set_kpi_formbuilder_permissions | ||
from onadata.libs.authentication import DigestAuthentication | ||
from onadata.libs.serializers.user_profile_serializer import _get_first_last_names | ||
from onadata.libs.utils.email import ProjectInvitationEmail | ||
from onadata.libs.permissions import EditorRole | ||
|
||
|
||
User = get_user_model() | ||
|
@@ -294,19 +293,13 @@ def test_profile_create(self, mock_send_verification_email): | |
|
||
@override_settings(CELERY_TASK_ALWAYS_EAGER=True) | ||
@override_settings(ENABLE_EMAIL_VERIFICATION=True) | ||
@patch( | ||
( | ||
"onadata.libs.serializers.user_profile_serializer." | ||
"accept_project_invitation_async.delay" | ||
) | ||
) | ||
@patch( | ||
( | ||
"onadata.libs.serializers.user_profile_serializer." | ||
"send_verification_email.delay" | ||
) | ||
) | ||
def test_accept_invitaton(self, mock_send_email, mock_accept_invitation): | ||
def test_accept_invitaton(self, mock_send_email): | ||
"""An invitation is accepted successfuly""" | ||
self._project_create() | ||
invitation = ProjectInvitation.objects.create( | ||
|
@@ -318,84 +311,19 @@ def test_accept_invitaton(self, mock_send_email, mock_accept_invitation): | |
data = _profile_data() | ||
del data["name"] | ||
data["email"] = invitation.email | ||
|
||
with patch.object( | ||
ProjectInvitationEmail, "check_invitation", Mock(return_value=invitation) | ||
) as mock_check_invitation: | ||
request = self.factory.post( | ||
"/api/v1/profiles?invitation_id=id&invitation_token=token", | ||
data=json.dumps(data), | ||
content_type="application/json", | ||
**self.extra, | ||
) | ||
response = self.view(request) | ||
self.assertEqual(response.status_code, 201) | ||
user = User.objects.get(username="deno") | ||
mock_check_invitation.assert_called_once_with("id", "token") | ||
mock_accept_invitation.assert_called_with(user.id, invitation.id) | ||
# user email matches invitation email so no need to send | ||
# verification email | ||
mock_send_email.assert_not_called() | ||
self.assertTrue(user.profile.metadata["is_email_verified"]) | ||
|
||
# user registers using a different email from invitation email | ||
data["email"] = "[email protected]" | ||
data["username"] = "nicki" | ||
|
||
with patch.object( | ||
ProjectInvitationEmail, "check_invitation", Mock(return_value=invitation) | ||
): | ||
request = self.factory.post( | ||
"/api/v1/profiles?invitation_id=some_valid_id&invitation_token=some_token", | ||
data=json.dumps(data), | ||
content_type="application/json", | ||
**self.extra, | ||
) | ||
response = self.view(request) | ||
self.assertEqual(response.status_code, 201) | ||
user = User.objects.get(username=data["username"]) | ||
mock_accept_invitation.assert_called_with(user.id, invitation.id) | ||
# user email does not match invitation email so we send email verification | ||
mock_send_email.assert_called_once() | ||
self.assertFalse(user.profile.metadata.get("is_email_verified", False)) | ||
|
||
# invitation_id and invitation_token missing | ||
data["email"] = "[email protected]" | ||
data["username"] = "jack" | ||
|
||
with patch.object( | ||
ProjectInvitationEmail, "check_invitation", Mock(return_value=invitation) | ||
): | ||
request = self.factory.post( | ||
"/api/v1/profiles", | ||
data=json.dumps(data), | ||
content_type="application/json", | ||
**self.extra, | ||
) | ||
response = self.view(request) | ||
self.assertEqual(response.status_code, 201) | ||
user = User.objects.get(username=data["username"]) | ||
mock_accept_invitation.assert_called_with(user.id, None) | ||
self.assertFalse(user.profile.metadata.get("is_email_verified", False)) | ||
|
||
# invalid invitation | ||
data["email"] = "[email protected]" | ||
data["username"] = "jude" | ||
|
||
with patch.object( | ||
ProjectInvitationEmail, "check_invitation", Mock(return_value=None) | ||
): | ||
request = self.factory.post( | ||
"/api/v1/profiles?invitation_id=some_valid_id&invitation_token=some_token", | ||
data=json.dumps(data), | ||
content_type="application/json", | ||
**self.extra, | ||
) | ||
response = self.view(request) | ||
self.assertEqual(response.status_code, 201) | ||
user = User.objects.get(username=data["username"]) | ||
mock_accept_invitation.assert_called_with(user.id, None) | ||
self.assertFalse(user.profile.metadata.get("is_email_verified", False)) | ||
request = self.factory.post( | ||
"/api/v1/profiles", | ||
data=json.dumps(data), | ||
content_type="application/json", | ||
**self.extra, | ||
) | ||
response = self.view(request) | ||
self.assertEqual(response.status_code, 201) | ||
user = User.objects.get(username="deno") | ||
mock_send_email.assert_called_once() | ||
invitation.refresh_from_db() | ||
self.assertEqual(invitation.status, ProjectInvitation.Status.ACCEPTED) | ||
self.assertTrue(EditorRole.user_has_role(user, self.project)) | ||
|
||
def _create_user_using_profiles_endpoint(self, data): | ||
request = self.factory.post( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.