Merge pull request #24453 from edx/revert-24298-bpant/ENT-3007-api-endpoints-for-samlprovider
Revert "ENT-3007 : Add auth/saml/v0/providerconfig|data CRUD endpoints for use in admin portal"
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
"""
|
||||
Serializer for SAMLProviderConfig
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from third_party_auth.models import SAMLProviderConfig
|
||||
|
||||
|
||||
class SAMLProviderConfigSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SAMLProviderConfig
|
||||
fields = '__all__'
|
||||
@@ -1,90 +0,0 @@
|
||||
import unittest
|
||||
import copy
|
||||
from uuid import uuid4
|
||||
from django.urls import reverse
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.http import urlencode
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from enterprise.models import EnterpriseCustomerIdentityProvider, EnterpriseCustomer
|
||||
from enterprise.constants import ENTERPRISE_ADMIN_ROLE
|
||||
from third_party_auth.tests.samlutils import set_jwt_cookie
|
||||
from third_party_auth.models import SAMLProviderConfig
|
||||
from third_party_auth.tests import testutil
|
||||
|
||||
SINGLE_PROVIDER_CONFIG = {
|
||||
'entity_id': 'id',
|
||||
'metadata_source': 'http://test.url',
|
||||
'name': 'name-of-config',
|
||||
'enabled': 'true',
|
||||
'slug': 'test-slug'
|
||||
}
|
||||
|
||||
SINGLE_PROVIDER_CONFIG_2 = copy.copy(SINGLE_PROVIDER_CONFIG)
|
||||
SINGLE_PROVIDER_CONFIG_2['name'] = 'name-of-config-2'
|
||||
SINGLE_PROVIDER_CONFIG_2['slug'] = 'test-slug-2'
|
||||
|
||||
ENTERPRISE_ID = str(uuid4())
|
||||
|
||||
|
||||
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, testutil.AUTH_FEATURES_KEY + ' not enabled')
|
||||
class SAMLProviderConfigTests(APITestCase):
|
||||
"""
|
||||
API Tests for SAMLProviderConfig REST endpoints
|
||||
The skip annotation above exists because we currently cannot run this test in
|
||||
the cms mode in CI builds, where the third_party_auth application is not loaded
|
||||
"""
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super(SAMLProviderConfigTests, cls).setUpTestData()
|
||||
cls.user = User.objects.create_user(username='testuser', password='testpwd')
|
||||
cls.site, _ = Site.objects.get_or_create(domain='example.com')
|
||||
cls.enterprise_customer = EnterpriseCustomer.objects.create(
|
||||
uuid=ENTERPRISE_ID,
|
||||
name='test-ep',
|
||||
slug='test-ep',
|
||||
site=cls.site)
|
||||
cls.samlproviderconfig, _ = SAMLProviderConfig.objects.get_or_create(
|
||||
entity_id=SINGLE_PROVIDER_CONFIG['entity_id'],
|
||||
metadata_source=SINGLE_PROVIDER_CONFIG['metadata_source']
|
||||
)
|
||||
cls.enterprisecustomeridp, _ = EnterpriseCustomerIdentityProvider.objects.get_or_create(
|
||||
provider_id=cls.samlproviderconfig.id,
|
||||
enterprise_customer_id=ENTERPRISE_ID
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
set_jwt_cookie(self.client, self.user, [(ENTERPRISE_ADMIN_ROLE, ENTERPRISE_ID)])
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_get_one_config_by_enterprise_uuid_found(self):
|
||||
"""
|
||||
GET auth/saml/v0/providerconfig/?enterprise_customer_uuid=id=id
|
||||
"""
|
||||
urlbase = reverse('samlproviderconfig-list')
|
||||
query_kwargs = {'enterprise_customer_uuid': ENTERPRISE_ID}
|
||||
url = '{}?{}'.format(urlbase, urlencode(query_kwargs))
|
||||
response = self.client.get(url, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
print(response.data)
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0]['entity_id'], SINGLE_PROVIDER_CONFIG['entity_id'])
|
||||
self.assertEqual(results[0]['metadata_source'], SINGLE_PROVIDER_CONFIG['metadata_source'])
|
||||
self.assertEqual(SAMLProviderConfig.objects.count(), 1)
|
||||
|
||||
def test_create_one_config(self):
|
||||
"""
|
||||
POST auth/saml/v0/providerconfig/?enterprise_customer_uuid=id -d data
|
||||
"""
|
||||
query_kwargs = {'enterprise_customer_uuid': ENTERPRISE_ID}
|
||||
url = '{}?{}'.format(reverse('samlproviderconfig-list'), urlencode(query_kwargs))
|
||||
data = SINGLE_PROVIDER_CONFIG_2
|
||||
orig_count = SAMLProviderConfig.objects.count()
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(SAMLProviderConfig.objects.count(), orig_count + 1)
|
||||
providerconfig = SAMLProviderConfig.objects.get(slug=SINGLE_PROVIDER_CONFIG_2['slug'])
|
||||
self.assertEqual(providerconfig.name, 'name-of-config-2')
|
||||
@@ -1,11 +0,0 @@
|
||||
"""
|
||||
Viewset for auth/saml/v0/providerconfig/
|
||||
"""
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import SAMLProviderConfigViewSet
|
||||
|
||||
samlproviderconfig_router = routers.DefaultRouter()
|
||||
samlproviderconfig_router.register(r'providerconfig', SAMLProviderConfigViewSet, basename="samlproviderconfig")
|
||||
urlpatterns = samlproviderconfig_router.urls
|
||||
@@ -1,57 +0,0 @@
|
||||
"""
|
||||
Viewset for auth/saml/v0/samlproviderconfig
|
||||
"""
|
||||
|
||||
from edx_rbac.mixins import PermissionRequiredMixin
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from rest_framework import permissions, viewsets
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
|
||||
from enterprise.models import EnterpriseCustomerIdentityProvider
|
||||
from openedx.features.enterprise_support.utils import fetch_enterprise_customer_by_id
|
||||
|
||||
from ..models import SAMLProviderConfig
|
||||
from .serializers import SAMLProviderConfigSerializer
|
||||
|
||||
|
||||
class SAMLProviderMixin(object):
|
||||
authentication_classes = [JwtAuthentication, SessionAuthentication]
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
serializer_class = SAMLProviderConfigSerializer
|
||||
|
||||
|
||||
class SAMLProviderConfigViewSet(PermissionRequiredMixin, SAMLProviderMixin, viewsets.ModelViewSet):
|
||||
"""
|
||||
A View to handle SAMLProviderConfig CRUD
|
||||
|
||||
Usage:
|
||||
[HttpVerb] /auth/saml/v0/providerconfig/?enterprise-id=uuid
|
||||
|
||||
permission_required refers to the Django permission name defined
|
||||
in enterprise.rules.
|
||||
The associated rule will allow edx-rbac to check if the EnterpriseCustomer
|
||||
returned by the get_permission_object method here, can be
|
||||
accessed by the user making this request (request.user)
|
||||
Access is only allowed if the user has the system role
|
||||
of 'ENTERPRISE_ADMIN' which is defined in enterprise.constants
|
||||
"""
|
||||
permission_required = 'enterprise.can_access_admin_dashboard'
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Find and return the matching providerid for the given enterprise uuid
|
||||
"""
|
||||
enterprise_customer_idp = EnterpriseCustomerIdentityProvider.objects.get(
|
||||
enterprise_customer__uuid=self.requested_enterprise_uuid
|
||||
)
|
||||
return SAMLProviderConfig.objects.filter(pk=enterprise_customer_idp.provider_id)
|
||||
|
||||
@property
|
||||
def requested_enterprise_uuid(self):
|
||||
return self.request.query_params.get('enterprise_customer_uuid')
|
||||
|
||||
def get_permission_object(self):
|
||||
"""
|
||||
Retrive an EnterpriseCustomer to do auth against
|
||||
"""
|
||||
return fetch_enterprise_customer_by_id(self.requested_enterprise_uuid)
|
||||
@@ -1,13 +0,0 @@
|
||||
"""
|
||||
Serializer for SAMLProviderData
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from third_party_auth.models import SAMLProviderData
|
||||
|
||||
|
||||
class SAMLProviderDataSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SAMLProviderData
|
||||
fields = '__all__'
|
||||
@@ -1,102 +0,0 @@
|
||||
import unittest
|
||||
import copy
|
||||
import pytz
|
||||
from uuid import uuid4
|
||||
from datetime import datetime
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from enterprise.models import EnterpriseCustomer, EnterpriseCustomerIdentityProvider
|
||||
from enterprise.constants import ENTERPRISE_ADMIN_ROLE
|
||||
|
||||
from third_party_auth.tests import testutil
|
||||
from third_party_auth.models import SAMLProviderData, SAMLProviderConfig
|
||||
from third_party_auth.tests.samlutils import set_jwt_cookie
|
||||
|
||||
SINGLE_PROVIDER_CONFIG = {
|
||||
'entity_id': 'http://entity-id-1',
|
||||
'metadata_source': 'http://test.url',
|
||||
'name': 'name-of-config',
|
||||
'enabled': 'true',
|
||||
'slug': 'test-slug'
|
||||
}
|
||||
|
||||
# entity_id here matches that of the providerconfig, intentionally
|
||||
# that allows this data entity to be found
|
||||
SINGLE_DATA_CONFIG = {
|
||||
'entity_id': 'http://entity-id-1',
|
||||
'sso_url': 'http://test.url',
|
||||
'public_key': 'a-key0Aid98',
|
||||
'fetched_at': datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
}
|
||||
|
||||
SINGLE_DATA_CONFIG_2 = copy.copy(SINGLE_DATA_CONFIG)
|
||||
SINGLE_DATA_CONFIG_2['entity_id'] = 'http://entity-id-2'
|
||||
SINGLE_DATA_CONFIG_2['sso_url'] = 'http://test2.url'
|
||||
|
||||
ENTERPRISE_ID = str(uuid4())
|
||||
|
||||
|
||||
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, testutil.AUTH_FEATURES_KEY + ' not enabled')
|
||||
class SAMLProviderDataTests(APITestCase):
|
||||
"""
|
||||
API Tests for SAMLProviderConfig REST endpoints
|
||||
"""
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super(SAMLProviderDataTests, cls).setUpTestData()
|
||||
cls.user = User.objects.create_user(username='testuser', password='testpwd')
|
||||
cls.site, _ = Site.objects.get_or_create(domain='example.com')
|
||||
cls.enterprise_customer = EnterpriseCustomer.objects.create(
|
||||
uuid=ENTERPRISE_ID,
|
||||
name='test-ep',
|
||||
slug='test-ep',
|
||||
site=cls.site)
|
||||
cls.samlproviderconfig, _ = SAMLProviderConfig.objects.get_or_create(
|
||||
entity_id=SINGLE_PROVIDER_CONFIG['entity_id'],
|
||||
metadata_source=SINGLE_PROVIDER_CONFIG['metadata_source']
|
||||
)
|
||||
# the entity_id here must match that of the samlproviderconfig
|
||||
cls.samlproviderdata, _ = SAMLProviderData.objects.get_or_create(
|
||||
entity_id=SINGLE_DATA_CONFIG['entity_id'],
|
||||
sso_url=SINGLE_DATA_CONFIG['sso_url'],
|
||||
fetched_at=SINGLE_DATA_CONFIG['fetched_at']
|
||||
)
|
||||
cls.enterprisecustomeridp, _ = EnterpriseCustomerIdentityProvider.objects.get_or_create(
|
||||
provider_id=cls.samlproviderconfig.id,
|
||||
enterprise_customer_id=ENTERPRISE_ID
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
# a cookie with roles: [{enterprise_admin_role: ent_id}] will be
|
||||
# needed to rbac to authorize access for this view
|
||||
set_jwt_cookie(self.client, self.user, [(ENTERPRISE_ADMIN_ROLE, ENTERPRISE_ID)])
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_get_one_providedata_success(self):
|
||||
# GET auth/saml/v0/providerdata/?enterprise_customer_uuid=id
|
||||
urlbase = reverse('samlproviderdata-list')
|
||||
query_kwargs = {'enterprise_customer_uuid': ENTERPRISE_ID}
|
||||
url = '{}?{}'.format(urlbase, urlencode(query_kwargs))
|
||||
response = self.client.get(url, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_create_one_providerdata_success(self):
|
||||
# POST auth/saml/v0/providerdata/?enterprise_customer_uuid -d data
|
||||
urlbase = reverse('samlproviderdata-list')
|
||||
query_kwargs = {'enterprise_customer_uuid': ENTERPRISE_ID}
|
||||
url = '{}?{}'.format(urlbase, urlencode(query_kwargs))
|
||||
fetched_at = '2009-01-10 00:12:12'
|
||||
data = SINGLE_DATA_CONFIG_2
|
||||
orig_count = SAMLProviderData.objects.count()
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(SAMLProviderData.objects.count(), orig_count + 1)
|
||||
self.assertEqual(
|
||||
SAMLProviderData.objects.get(entity_id=SINGLE_DATA_CONFIG_2['entity_id']).sso_url,
|
||||
SINGLE_DATA_CONFIG_2['sso_url']
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
"""
|
||||
url mappings for auth/saml/v0/providerdata/
|
||||
"""
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import SAMLProviderDataViewSet
|
||||
|
||||
samlproviderdata_router = routers.DefaultRouter()
|
||||
samlproviderdata_router.register(r'providerdata', SAMLProviderDataViewSet, basename="samlproviderdata")
|
||||
urlpatterns = samlproviderdata_router.urls
|
||||
@@ -1,54 +0,0 @@
|
||||
"""
|
||||
Viewset for auth/saml/v0/samlproviderdata
|
||||
"""
|
||||
|
||||
from edx_rbac.mixins import PermissionRequiredMixin
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from rest_framework import permissions, viewsets
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
|
||||
from enterprise.models import EnterpriseCustomerIdentityProvider
|
||||
from openedx.features.enterprise_support.utils import fetch_enterprise_customer_by_id
|
||||
|
||||
from ..models import SAMLProviderConfig, SAMLProviderData
|
||||
from .serializers import SAMLProviderDataSerializer
|
||||
|
||||
|
||||
class SAMLProviderDataMixin(object):
|
||||
authentication_classes = [JwtAuthentication, SessionAuthentication]
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
serializer_class = SAMLProviderDataSerializer
|
||||
|
||||
|
||||
class SAMLProviderDataViewSet(PermissionRequiredMixin, SAMLProviderDataMixin, viewsets.ModelViewSet):
|
||||
"""
|
||||
A View to handle SAMLProviderData CRUD.
|
||||
Uses the edx-rbac mixin PermissionRequiredMixin to apply enterprise authorization
|
||||
|
||||
Usage:
|
||||
[HttpVerb] /auth/saml/v0/providerdata/
|
||||
"""
|
||||
permission_required = 'enterprise.can_access_admin_dashboard'
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Find and return the matching providerid for the given enterprise uuid
|
||||
Note: There is no direct association between samlproviderdata and enterprisecustomer.
|
||||
So we make that association in code via samlproviderdata > samlproviderconfig ( via entity_id )
|
||||
then, we fetch enterprisecustomer via samlproviderconfig > enterprisecustomer ( via association table )
|
||||
"""
|
||||
enterprise_customer_idp = EnterpriseCustomerIdentityProvider.objects.get(
|
||||
enterprise_customer__uuid=self.requested_enterprise_uuid
|
||||
)
|
||||
saml_provider = SAMLProviderConfig.objects.get(pk=enterprise_customer_idp.provider_id)
|
||||
return SAMLProviderData.objects.filter(entity_id=saml_provider.entity_id)
|
||||
|
||||
@property
|
||||
def requested_enterprise_uuid(self):
|
||||
return self.request.query_params.get('enterprise_customer_uuid')
|
||||
|
||||
def get_permission_object(self):
|
||||
"""
|
||||
Retrive an EnterpriseCustomer to do auth against
|
||||
"""
|
||||
return fetch_enterprise_customer_by_id(self.requested_enterprise_uuid)
|
||||
@@ -1,33 +0,0 @@
|
||||
"""
|
||||
Utility functions for use in SAMLProviderConfig, SAMLProviderData tests
|
||||
"""
|
||||
|
||||
from edx_rest_framework_extensions.auth.jwt.cookies import jwt_cookie_name
|
||||
from edx_rest_framework_extensions.auth.jwt.tests.utils import (
|
||||
generate_jwt_token,
|
||||
generate_unversioned_payload,
|
||||
)
|
||||
|
||||
|
||||
def _jwt_token_from_role_context_pairs(user, role_context_pairs):
|
||||
"""
|
||||
Generates a new JWT token with roles assigned from pairs of (role name, context).
|
||||
"""
|
||||
roles = []
|
||||
for role, context in role_context_pairs:
|
||||
role_data = '{role}'.format(role=role)
|
||||
if context is not None:
|
||||
role_data += ':{context}'.format(context=context)
|
||||
roles.append(role_data)
|
||||
|
||||
payload = generate_unversioned_payload(user)
|
||||
payload.update({'roles': roles})
|
||||
return generate_jwt_token(payload)
|
||||
|
||||
|
||||
def set_jwt_cookie(client, user, role_context_pairs=None):
|
||||
"""
|
||||
Set jwt token in cookies
|
||||
"""
|
||||
jwt_token = _jwt_token_from_role_context_pairs(user, role_context_pairs or [])
|
||||
client.cookies[jwt_cookie_name()] = jwt_token
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Url configuration for the auth module."""
|
||||
|
||||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from .views import (
|
||||
@@ -17,6 +18,4 @@ urlpatterns = [
|
||||
url(r'^auth/login/(?P<backend>lti)/$', lti_login_and_complete_view),
|
||||
url(r'^auth/idp_redirect/(?P<provider_slug>[\w-]+)', IdPRedirectView.as_view(), name="idp_redirect"),
|
||||
url(r'^auth/', include('social_django.urls', namespace='social')),
|
||||
url(r'^auth/saml/v0/', include('third_party_auth.samlproviderconfig.urls')),
|
||||
url(r'^auth/saml/v0/', include('third_party_auth.samlproviderdata.urls'))
|
||||
]
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.conf import settings
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from edx_django_utils.cache import TieredCache, get_cache_key
|
||||
from enterprise.models import EnterpriseCustomerUser, EnterpriseCustomer
|
||||
from enterprise.models import EnterpriseCustomerUser
|
||||
from social_django.models import UserSocialAuth
|
||||
|
||||
import third_party_auth
|
||||
@@ -342,7 +342,3 @@ def get_provider_login_url(request, provider_id, redirect_url=None):
|
||||
redirect_url=redirect_url if redirect_url else get_next_url_for_login_page(request)
|
||||
)
|
||||
return provider_login_url
|
||||
|
||||
|
||||
def fetch_enterprise_customer_by_id(enterprise_uuid):
|
||||
return EnterpriseCustomer.objects.get(uuid=enterprise_uuid)
|
||||
|
||||
Reference in New Issue
Block a user