Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues/523 saml auth create email address objects #544

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions openwisp_radius/saml/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from urllib.parse import parse_qs, urlparse

import swapper
from allauth.account.models import EmailAddress
from django.conf import settings
from django.contrib.auth import logout
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
Expand Down Expand Up @@ -66,6 +67,22 @@ def post_login_hook(self, request, user, session_info):
try:
user.registered_user
except ObjectDoesNotExist:
email = None
uid_is_email = 'email' in getattr(
settings, 'SAML_ATTRIBUTE_MAPPING', {}
).get('uid', ())
if uid_is_email:
email = session_info['name_id'].text
if email is None:
email = session_info['ava'].get('email', [None])[0]
if email:
user.email = email
user.save()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saving this information without validating it is dangerous.

Here we need to run full_clean() and catch any possible exception in case the email we get from the identity provider is invalid for some reason. If the email is invalid wont save/create, log a warning and skip, eg:

try:
    user.full_clean()
except ValidationError:
    logger.exception(f'Failed email validation for {user} during SAML user creation')
else:
    user.save()
    email_address = EmailAddress.objects.create(
       user=user, email=email, verified=True, primary=True
    )

Please add a new test for this error case, you can use mock or any other way to pass an invalid email and catch the validation error, there's similar tests in this and other modules you can base your test on, look for ValidationError in the test suites of the codebase.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem.The new unified documentation looks great. I will make the suggested changes.

email_address = EmailAddress.objects.create(
user=user, email=email, verified=True, primary=True
)
email_address.save()
Copy link
Member

@nemesifier nemesifier Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove (it's redundant, create() already peforms the save operation):

Suggested change
email_address.save()


registered_user = RegisteredUser(
user=user, method='saml', is_verified=app_settings.SAML_IS_VERIFIED
)
Expand Down
3 changes: 3 additions & 0 deletions openwisp_radius/tests/test_saml/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from urllib.parse import parse_qs, urlparse

import swapper
from allauth.account.models import EmailAddress
from django.contrib.auth import SESSION_KEY, get_user_model
from django.test import TestCase, override_settings
from django.urls import reverse
Expand Down Expand Up @@ -69,6 +70,8 @@ def _post_successful_auth_assertions(self, query_params, org_slug):
self.assertEqual(User.objects.count(), 1)
user_id = self.client.session[SESSION_KEY]
user = User.objects.get(id=user_id)
email = EmailAddress.objects.filter(user=user)
self.assertEqual(email.count(), 1)
self.assertEqual(user.username, 'org_user@example.com')
self.assertEqual(OrganizationUser.objects.count(), 1)
org_user = OrganizationUser.objects.get(user_id=user_id)
Expand Down