user lookup util
This commit is contained in:
committed by
Alex Dusenbery
parent
03db25bd50
commit
afe3cdb3ec
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-04-18 20:33
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0006_auto_20171207_0259'),
|
||||
('third_party_auth', '0022_auto_20181012_0307'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ltiproviderconfig',
|
||||
name='organization',
|
||||
field=models.OneToOneField(blank=True, help_text="optional. If this provider is an Organization, this attribute can be used reference users in that Organization", null=True, on_delete=django.db.models.deletion.CASCADE, to='organizations.Organization'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauth2providerconfig',
|
||||
name='organization',
|
||||
field=models.OneToOneField(blank=True, help_text="optional. If this provider is an Organization, this attribute can be used reference users in that Organization", null=True, on_delete=django.db.models.deletion.CASCADE, to='organizations.Organization'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='samlproviderconfig',
|
||||
name='organization',
|
||||
field=models.OneToOneField(blank=True, help_text="optional. If this provider is an Organization, this attribute can be used reference users in that Organization", null=True, on_delete=django.db.models.deletion.CASCADE, to='organizations.Organization'),
|
||||
),
|
||||
]
|
||||
@@ -16,6 +16,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from organizations.models import Organization
|
||||
from provider.oauth2.models import Client
|
||||
from provider.utils import long_token
|
||||
from social_core.backends.base import BaseAuth
|
||||
@@ -125,6 +126,16 @@ class ProviderConfig(ConfigurationModel):
|
||||
'in a separate list of "Institution" login providers.'
|
||||
),
|
||||
)
|
||||
organization = models.OneToOneField(
|
||||
Organization,
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
help_text=_(
|
||||
'optional. If this provider is an Organization, this attribute '
|
||||
'can be used reference users in that Organization'
|
||||
)
|
||||
)
|
||||
site = models.ForeignKey(
|
||||
Site,
|
||||
default=settings.SITE_ID,
|
||||
|
||||
@@ -64,9 +64,10 @@ class Oauth2ProviderConfigAdminTest(testutil.TestCase):
|
||||
# Remove the icon_image from the POST data, to simulate unchanged icon_image
|
||||
post_data = models.model_to_dict(provider1)
|
||||
del post_data['icon_image']
|
||||
# Remove max_session_length; it has a default null value which must be POSTed
|
||||
# Remove max_session_length and organization. A default null value must be POSTed
|
||||
# back as an absent value, rather than as a "null-like" included value.
|
||||
del post_data['max_session_length']
|
||||
del post_data['organization']
|
||||
|
||||
# Change the name, to verify POST
|
||||
post_data['name'] = 'Another name'
|
||||
|
||||
123
lms/djangoapps/program_enrollments/tests/test_utils.py
Normal file
123
lms/djangoapps/program_enrollments/tests/test_utils.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Unit tests for program_enrollments utils.
|
||||
"""
|
||||
from uuid import uuid4
|
||||
import pytest
|
||||
from django.core.cache import cache
|
||||
|
||||
from openedx.core.djangoapps.catalog.cache import PROGRAM_CACHE_KEY_TPL
|
||||
from openedx.core.djangoapps.catalog.tests.factories import (
|
||||
OrganizationFactory as CatalogOrganizationFactory, ProgramFactory
|
||||
)
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from organizations.tests.factories import OrganizationFactory
|
||||
from program_enrollments.utils import (
|
||||
get_user_by_program_id, ProgramDoesNotExistException, OrganizationDoesNotExistException,
|
||||
ProviderDoesNotExistException
|
||||
)
|
||||
from social_django.models import UserSocialAuth
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tests.factories import SAMLProviderConfigFactory
|
||||
|
||||
|
||||
class GetPlatformUserTests(CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the get_platform_user function
|
||||
"""
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(GetPlatformUserTests, self).setUp()
|
||||
self.program_uuid = uuid4()
|
||||
self.organization_key = 'ufo'
|
||||
self.external_user_id = '1234'
|
||||
self.user = UserFactory.create()
|
||||
self.setup_catalog_cache(self.program_uuid, self.organization_key)
|
||||
|
||||
def setup_catalog_cache(self, program_uuid, organization_key):
|
||||
"""
|
||||
helper function to initialize a cached program with an single authoring_organization
|
||||
"""
|
||||
catalog_org = CatalogOrganizationFactory.create(key=organization_key)
|
||||
program = ProgramFactory.create(
|
||||
uuid=program_uuid,
|
||||
authoring_organizations=[catalog_org]
|
||||
)
|
||||
cache.set(PROGRAM_CACHE_KEY_TPL.format(uuid=program_uuid), program, None)
|
||||
|
||||
def create_social_auth_entry(self, user, provider, external_id):
|
||||
"""
|
||||
helper functio to create a user social auth entry
|
||||
"""
|
||||
UserSocialAuth.objects.create(
|
||||
user=user,
|
||||
uid='{0}:{1}'.format(provider.slug, external_id)
|
||||
)
|
||||
|
||||
def test_get_user_success(self):
|
||||
"""
|
||||
Test lms user is successfully found
|
||||
"""
|
||||
organization = OrganizationFactory.create(short_name=self.organization_key)
|
||||
provider = SAMLProviderConfigFactory.create(organization=organization)
|
||||
self.create_social_auth_entry(self.user, provider, self.external_user_id)
|
||||
|
||||
user = get_user_by_program_id(self.external_user_id, self.program_uuid)
|
||||
self.assertEquals(user, self.user)
|
||||
|
||||
def test_social_auth_user_not_created(self):
|
||||
"""
|
||||
None should be returned if no lms user exists for an external id
|
||||
"""
|
||||
organization = OrganizationFactory.create(short_name=self.organization_key)
|
||||
SAMLProviderConfigFactory.create(organization=organization)
|
||||
|
||||
user = get_user_by_program_id(self.external_user_id, self.program_uuid)
|
||||
self.assertIsNone(user)
|
||||
|
||||
def test_catalog_program_does_not_exist(self):
|
||||
"""
|
||||
Test ProgramDoesNotExistException is thrown if the program cache does
|
||||
not include the requested program uuid.
|
||||
"""
|
||||
with pytest.raises(ProgramDoesNotExistException):
|
||||
get_user_by_program_id('school-id-1234', uuid4())
|
||||
|
||||
def test_catalog_program_missing_org(self):
|
||||
"""
|
||||
Test OrganizationDoesNotExistException is thrown if the cached program does not
|
||||
have an authoring organization.
|
||||
"""
|
||||
program = ProgramFactory.create(
|
||||
uuid=self.program_uuid,
|
||||
authoring_organizations=[]
|
||||
)
|
||||
cache.set(PROGRAM_CACHE_KEY_TPL.format(uuid=self.program_uuid), program, None)
|
||||
|
||||
organization = OrganizationFactory.create(short_name=self.organization_key)
|
||||
provider = SAMLProviderConfigFactory.create(organization=organization)
|
||||
self.create_social_auth_entry(self.user, provider, self.external_user_id)
|
||||
|
||||
with pytest.raises(OrganizationDoesNotExistException):
|
||||
get_user_by_program_id(self.external_user_id, self.program_uuid)
|
||||
|
||||
def test_lms_organization_not_found(self):
|
||||
"""
|
||||
Test an OrganizationDoesNotExistException is thrown if the LMS has no organization
|
||||
matching the catalog program's authoring_organization
|
||||
"""
|
||||
organization = OrganizationFactory.create(short_name='some_other_org')
|
||||
provider = SAMLProviderConfigFactory.create(organization=organization)
|
||||
self.create_social_auth_entry(self.user, provider, self.external_user_id)
|
||||
|
||||
with pytest.raises(OrganizationDoesNotExistException):
|
||||
get_user_by_program_id(self.external_user_id, self.program_uuid)
|
||||
|
||||
def test_saml_provider_not_found(self):
|
||||
"""
|
||||
Test an sdf is thrown if no SAML provider exists for this program's organization
|
||||
"""
|
||||
OrganizationFactory.create(short_name=self.organization_key)
|
||||
|
||||
with pytest.raises(ProviderDoesNotExistException):
|
||||
get_user_by_program_id(self.external_user_id, self.program_uuid)
|
||||
89
lms/djangoapps/program_enrollments/utils.py
Normal file
89
lms/djangoapps/program_enrollments/utils.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
utility functions for program enrollments
|
||||
"""
|
||||
import logging
|
||||
from openedx.core.djangoapps.catalog.utils import get_programs
|
||||
from organizations.models import Organization
|
||||
from social_django.models import UserSocialAuth
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserLookupException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProgramDoesNotExistException(UserLookupException):
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationDoesNotExistException(UserLookupException):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderDoesNotExistException(UserLookupException):
|
||||
pass
|
||||
|
||||
|
||||
def get_user_by_program_id(external_user_id, program_uuid):
|
||||
"""
|
||||
Returns a User model for an external_user_id with a social auth entry.
|
||||
|
||||
Args:
|
||||
external_user_id: external user id used for social auth
|
||||
program_uuid: a program this user is/will be enrolled in
|
||||
|
||||
Returns:
|
||||
A User object or None, if no user with the given external id for the given organization exists.
|
||||
|
||||
Raises:
|
||||
ProgramDoesNotExistException if no such program exists.
|
||||
OrganizationDoesNotExistException if no organization exists.
|
||||
ProviderDoesNotExistException if there is no SAML provider configured for the related organization.
|
||||
"""
|
||||
program = get_programs(uuid=program_uuid)
|
||||
if program is None:
|
||||
log.error(u'Unable to find catalog program matching uuid [%s]', program_uuid)
|
||||
raise ProgramDoesNotExistException
|
||||
|
||||
try:
|
||||
org_key = program['authoring_organizations'][0]['key']
|
||||
organization = Organization.objects.get(short_name=org_key)
|
||||
except (KeyError, IndexError):
|
||||
log.error(u'Cannot determine authoring organization key for catalog program [%s]', program_uuid)
|
||||
raise OrganizationDoesNotExistException
|
||||
except Organization.DoesNotExist:
|
||||
log.error(u'Unable to find organization for short_name [%s]', org_key)
|
||||
raise OrganizationDoesNotExistException
|
||||
|
||||
return get_user_by_organization(external_user_id, organization)
|
||||
|
||||
|
||||
def get_user_by_organization(external_user_id, organization):
|
||||
"""
|
||||
Returns a User model for an external_user_id with a social auth entry.
|
||||
|
||||
This function finds a matching SAML Provider for the given organization, and looks
|
||||
for a social auth entry with the provided exernal id.
|
||||
|
||||
Args:
|
||||
external_user_id: external user id used for social auth
|
||||
organization: organization providing saml authentication for this user
|
||||
|
||||
Returns:
|
||||
A User object or None, if no user with the given external id for the given organization exists.
|
||||
|
||||
Raises:
|
||||
ProviderDoesNotExistException if there is no SAML provider configured for the related organization.
|
||||
"""
|
||||
try:
|
||||
provider_slug = organization.samlproviderconfig.provider_id.strip('saml-')
|
||||
except Organization.samlproviderconfig.RelatedObjectDoesNotExist:
|
||||
log.error(u'No SAML provider found for organization id [%s]', organization.id)
|
||||
raise ProviderDoesNotExistException
|
||||
|
||||
try:
|
||||
social_auth_uid = '{0}:{1}'.format(provider_slug, external_user_id)
|
||||
return UserSocialAuth.objects.get(uid=social_auth_uid).user
|
||||
except UserSocialAuth.DoesNotExist:
|
||||
return None
|
||||
Reference in New Issue
Block a user