Merge pull request #3175 from akatsoulas/group-propagation

Always propagate access groups.
This commit is contained in:
Tasos Katsoulas 2019-01-07 16:35:15 +02:00 коммит произвёл GitHub
Родитель 9459d5dc13 6f1e0ded1f
Коммит 28bc2bede6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 22 добавлений и 101 удалений

Просмотреть файл

@ -5,39 +5,17 @@ import re
from django.db import transaction
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User
from cities_light.models import Country
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from waffle import switch_is_active
from mozillians.common.templatetags.helpers import get_object_or_none
from mozillians.dino_park.utils import _dino_park_get_profile_by_userid
from mozillians.users.models import IdpProfile
from mozillians.users.tasks import send_userprofile_to_cis
# Only allow the following login flows
# Passwordless > Google > Github, FxA > LDAP
# There is no way to downgrade
ALLOWED_IDP_FLOWS = {
IdpProfile.PROVIDER_PASSWORDLESS: IdpProfile.MFA_ACCOUNTS + [
IdpProfile.PROVIDER_PASSWORDLESS,
IdpProfile.PROVIDER_GOOGLE,
],
IdpProfile.PROVIDER_GOOGLE: IdpProfile.MFA_ACCOUNTS + [
IdpProfile.PROVIDER_PASSWORDLESS,
IdpProfile.PROVIDER_GOOGLE,
],
IdpProfile.PROVIDER_GITHUB: IdpProfile.MFA_ACCOUNTS,
IdpProfile.PROVIDER_FIREFOX_ACCOUNTS: IdpProfile.MFA_ACCOUNTS,
IdpProfile.PROVIDER_LDAP: [
IdpProfile.PROVIDER_LDAP
]
}
def calculate_username(email):
"""Calculate username from email address."""
@ -174,9 +152,6 @@ class MozilliansAuthBackend(OIDCAuthenticationBackend):
if groups and 'hris_is_staff' in groups:
profile.auto_vouch()
# Get current_idp
current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
# Get or create new `user_id`
obj, _ = IdpProfile.objects.get_or_create(
profile=profile,
@ -188,20 +163,13 @@ class MozilliansAuthBackend(OIDCAuthenticationBackend):
obj.username = self.claims.get('nickname', '')
obj.save()
# Do not allow downgrades.
if current_idp and obj.type < current_idp.type:
msg = u'Please use {0} as the login method to authenticate'
messages.error(self.request, msg.format(current_idp.get_type_display()))
return None
# Mark other `user_id` as `primary=False`
idp_q = IdpProfile.objects.filter(profile=profile)
with transaction.atomic():
idp_q.exclude(auth0_user_id=auth0_user_id, email=email).update(primary=False)
# Mark current `user_id` as `primary=True`
idp_q.filter(auth0_user_id=auth0_user_id, email=email).update(primary=True)
# Update CIS
send_userprofile_to_cis.delay(profile.pk)
# This is happening only the first time a user logs into mozillians.org
if not idp_q.filter(primary=True).exists():
with transaction.atomic():
idp_q.filter(auth0_user_id=auth0_user_id, email=email).update(primary=True)
# Update CIS
send_userprofile_to_cis.delay(profile.pk)
return user
def authenticate(self, **kwargs):

Просмотреть файл

@ -1,7 +1,7 @@
from django.http import HttpRequest
from django.test import override_settings
from mock import Mock, patch
from mock import Mock
from nose.tools import eq_, ok_
from mozillians.common.tests import TestCase
@ -23,8 +23,7 @@ class MozilliansAuthBackendTests(TestCase):
from mozillians.common.authbackend import MozilliansAuthBackend
self.backend = MozilliansAuthBackend()
@patch('mozillians.common.authbackend.messages')
def test_add_a_new_email_identity(self, mocked_message):
def test_add_a_new_email_identity(self):
"""Test to add a new email in an authenticated user."""
user = UserFactory.create(email='foo@example.com')
IdpProfile.objects.create(
@ -51,10 +50,8 @@ class MozilliansAuthBackendTests(TestCase):
email='bar@example.com')
ok_(email_q.exists())
eq_(returned_user, user)
ok_(not mocked_message.called)
@patch('mozillians.common.authbackend.messages')
def test_identity_already_exists(self, mocked_message):
def test_identity_already_exists(self):
"""Test to add an email that already exists."""
user = UserFactory.create(email='foo@example.com')
@ -78,10 +75,8 @@ class MozilliansAuthBackendTests(TestCase):
email=user.email,
profile=user.userprofile)
eq_(idp_q.count(), 1)
ok_(not mocked_message.called)
@patch('mozillians.common.authbackend.messages')
def test_identity_single_auth0_id_multiple_emails(self, mocked_message):
def test_identity_single_auth0_id_multiple_emails(self):
"""Test to add an email that already exists."""
user = UserFactory.create(email='foo@example.com')
@ -105,39 +100,11 @@ class MozilliansAuthBackendTests(TestCase):
eq_(IdpProfile.objects.filter(
profile=user.userprofile, primary=True,
username='foo', email='foo@example.com').count(), 1)
username='foo', email='foo@example.com').count(), 0)
eq_(IdpProfile.objects.filter(
profile=user.userprofile, primary=False,
profile=user.userprofile, primary=True,
email='foo@bar.com').count(), 1)
@patch('mozillians.common.authbackend.messages')
def test_add_idp_wrong_flow(self, mocked_message):
"""Test logging in with a weaker provider compared to the current one"""
user = UserFactory.create(email='foo@example.com')
IdpProfile.objects.create(
profile=user.userprofile,
auth0_user_id='ad|foobar',
primary=True,
email='foobar@example.com'
)
claims = {
'email': 'bar@example.com',
'user_id': 'foobar'
}
request_mock = Mock(spec=HttpRequest)
request_mock.user = user
self.backend.claims = claims
self.backend.request = request_mock
returned_user = self.backend.check_authentication_method(user)
msg = 'Please use LDAP Provider as the login method to authenticate'
mocked_message.error.assert_called_once_with(request_mock, msg)
eq_(returned_user, None)
def test_filter_users_with_email_belonging_to_non_primary_identity(self):
"""Test filter users with a non primary identity."""

Просмотреть файл

@ -139,13 +139,11 @@ class HasMFAEnabled(SimpleListFilter):
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.filter(userprofile__idp_profiles__type__in=[
IdpProfile.PROVIDER_GITHUB, IdpProfile.PROVIDER_LDAP]
).distinct()
return queryset.filter(
userprofile__idp_profiles__type__in=IdpProfile.HIGH_AAL_ACCOUNTS).distinct()
elif self.value() == 'False':
return queryset.exclude(userprofile__idp_profiles__type__in=[
IdpProfile.PROVIDER_GITHUB, IdpProfile.PROVIDER_LDAP]
).distinct()
return queryset.exclude(
userprofile__idp_profiles__type__in=IdpProfile.HIGH_AAL_ACCOUNTS).distinct()
else:
return queryset

Просмотреть файл

@ -736,15 +736,6 @@ class UserProfile(UserProfilePrivacyModel):
def get_cis_groups(self, idp):
"""Prepares the entry for profile groups in the CIS format."""
# Update strategy: send groups for higher MFA idp
# Wipe groups from the rest
idps = list(self.idp_profiles.all().values_list('type', flat=True))
# if the current idp does not match
# the greatest number in the list, wipe the groups
if not idps or idp.type != max(idps) or not idp.is_mfa():
return []
memberships = GroupMembership.objects.filter(
userprofile=self,
status=GroupMembership.MEMBER,
@ -807,10 +798,12 @@ class IdpProfile(models.Model):
(PROVIDER_LDAP, 'LDAP Provider',),
)
# MFA_ACCOUNTS
MFA_ACCOUNTS = [PROVIDER_LDAP,
PROVIDER_FIREFOX_ACCOUNTS,
PROVIDER_GITHUB]
# High Security OPs
HIGH_AAL_ACCOUNTS = [PROVIDER_LDAP,
PROVIDER_FIREFOX_ACCOUNTS,
PROVIDER_GITHUB,
PROVIDER_GOOGLE]
profile = models.ForeignKey(UserProfile, related_name='idp_profiles')
type = models.IntegerField(choices=PROVIDER_TYPES,
default=None,
@ -845,11 +838,6 @@ class IdpProfile(models.Model):
return self.PROVIDER_UNKNOWN
def is_mfa(self):
"""Helper method to check if IdpProfile is MFA-ed"""
return self.type in self.MFA_ACCOUNTS
def save(self, *args, **kwargs):
"""Custom save method.