Merge pull request #23199 from edx/robrap/BOM-1292-remove-application-organization
BOM-1292: remove uses of ApplicationOrganization
This commit is contained in:
@@ -6,7 +6,7 @@ Override admin configuration for django-oauth-toolkit
|
||||
from django.contrib.admin import ModelAdmin, site
|
||||
from oauth2_provider import models
|
||||
|
||||
from .models import ApplicationAccess, ApplicationOrganization, RestrictedApplication
|
||||
from .models import ApplicationAccess, RestrictedApplication
|
||||
|
||||
|
||||
def reregister(model_class):
|
||||
@@ -83,13 +83,6 @@ class ApplicationAccessAdmin(ModelAdmin):
|
||||
list_display = ['application', 'scopes', 'filters']
|
||||
|
||||
|
||||
class ApplicationOrganizationAdmin(ModelAdmin):
|
||||
"""
|
||||
ModelAdmin for ApplicationOrganization
|
||||
"""
|
||||
list_display = [u'application', u'organization', u'relation_type']
|
||||
|
||||
|
||||
class RestrictedApplicationAdmin(ModelAdmin):
|
||||
"""
|
||||
ModelAdmin for the Restricted Application
|
||||
@@ -98,5 +91,4 @@ class RestrictedApplicationAdmin(ModelAdmin):
|
||||
|
||||
|
||||
site.register(ApplicationAccess, ApplicationAccessAdmin)
|
||||
site.register(ApplicationOrganization, ApplicationOrganizationAdmin)
|
||||
site.register(RestrictedApplication, RestrictedApplicationAdmin)
|
||||
|
||||
@@ -10,21 +10,20 @@ from oauth2_provider.scopes import get_scopes_backend
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
from oauth2_provider.views import AuthorizationView
|
||||
|
||||
from openedx.core.djangoapps.oauth_dispatch.models import ApplicationOrganization
|
||||
from openedx.core.djangoapps.oauth_dispatch.models import ApplicationAccess
|
||||
|
||||
|
||||
# TODO (ARCH-83) remove once we have full support of OAuth Scopes
|
||||
class EdxOAuth2AuthorizationView(AuthorizationView):
|
||||
"""
|
||||
Override the AuthorizationView's GET method so the user isn't
|
||||
prompted to approve the application if they have already in
|
||||
the past, even if their access token is expired.
|
||||
|
||||
This is a temporary override of the base implementation
|
||||
in order to accommodate our Restricted Applications support
|
||||
until OAuth Scopes are fully supported.
|
||||
This is override of the base implementation accommodates our
|
||||
Restricted Applications support and custom filters.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
# pylint: disable=line-too-long
|
||||
# Note: This code is copied from https://github.com/evonove/django-oauth-toolkit/blob/34f3b7b3511c15686039079026165feaadb1b87d/oauth2_provider/views/base.py#L111
|
||||
# Places that we have changed are noted with ***.
|
||||
application = None
|
||||
@@ -46,10 +45,13 @@ class EdxOAuth2AuthorizationView(AuthorizationView):
|
||||
|
||||
# at this point we know an Application instance with such client_id exists in the database
|
||||
application = get_application_model().objects.get(client_id=credentials['client_id'])
|
||||
content_orgs = ApplicationOrganization.get_related_org_names(
|
||||
application,
|
||||
relation_type=ApplicationOrganization.RELATION_TYPE_CONTENT_ORG
|
||||
)
|
||||
try:
|
||||
content_orgs = list(ApplicationAccess.get_filter_values(application, ApplicationAccess.CONTENT_ORG_FILTER_NAME))
|
||||
except ApplicationAccess.DoesNotExist:
|
||||
# No application access policy for this application exists.
|
||||
# so we have no content orgs.
|
||||
content_orgs = []
|
||||
|
||||
kwargs['application'] = application
|
||||
kwargs['content_orgs'] = content_orgs
|
||||
kwargs['client_id'] = credentials['client_id']
|
||||
|
||||
@@ -5,7 +5,6 @@ Specialized models for oauth_dispatch djangoapp
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import six
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -71,6 +70,12 @@ class ApplicationAccess(models.Model):
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
# Content org filters are of the form "content_org:<org_name>" eg. "content_org:SchoolX"
|
||||
# and indicate that for anything that cares about the content_org filter, that the response
|
||||
# should be filtered based on the filter value. ie. We should only get responses pertain
|
||||
# to objects that are relevant to the SchoolX organization.
|
||||
CONTENT_ORG_FILTER_NAME = 'content_org'
|
||||
|
||||
application = models.OneToOneField(oauth2_settings.APPLICATION_MODEL, related_name='access',
|
||||
on_delete=models.CASCADE)
|
||||
scopes = ListCharField(
|
||||
@@ -100,6 +105,14 @@ class ApplicationAccess(models.Model):
|
||||
def get_filters(cls, application):
|
||||
return cls.objects.get(application=application).filters
|
||||
|
||||
@classmethod
|
||||
def get_filter_values(cls, application, filter_name):
|
||||
filters = cls.get_filters(application=application)
|
||||
for filter_constraint in filters:
|
||||
name, filter_value = filter_constraint.split(':', 1)
|
||||
if name == filter_name:
|
||||
yield filter_value
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a unicode representation of this object.
|
||||
@@ -114,12 +127,16 @@ class ApplicationAccess(models.Model):
|
||||
@python_2_unicode_compatible
|
||||
class ApplicationOrganization(models.Model):
|
||||
"""
|
||||
Associates a DOT Application to an Organization.
|
||||
DEPRECATED: Associates a DOT Application to an Organization.
|
||||
|
||||
See openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst
|
||||
for the intended use of this model.
|
||||
This model is no longer in use.
|
||||
|
||||
Deprecated: Use filters in ApplicationAccess instead.
|
||||
TODO: BOM-1270: This model and table will be removed post-Juniper
|
||||
so Open edX instances can migrate data if necessary.
|
||||
|
||||
To migrate, use ApplicationAccess and add a ``filter`` of the form
|
||||
``content_org:<ORG NAME>`` (e.g. content_org:edx), for each record
|
||||
in this model's table.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
@@ -140,31 +157,3 @@ class ApplicationOrganization(models.Model):
|
||||
class Meta:
|
||||
app_label = 'oauth_dispatch'
|
||||
unique_together = ('application', 'relation_type', 'organization')
|
||||
|
||||
@classmethod
|
||||
def get_related_org_names(cls, application, relation_type=None):
|
||||
"""
|
||||
Return the names of the Organizations related to the given DOT Application.
|
||||
|
||||
Filter by relation_type if provided.
|
||||
"""
|
||||
queryset = application.organizations.all()
|
||||
if relation_type:
|
||||
queryset = queryset.filter(relation_type=relation_type)
|
||||
return [r.organization.name for r in queryset]
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a unicode representation of this object.
|
||||
"""
|
||||
return u"{application_name}:{organization}:{relation_type}".format(
|
||||
application_name=self.application.name,
|
||||
organization=self.organization.short_name,
|
||||
relation_type=self.relation_type,
|
||||
)
|
||||
|
||||
def to_jwt_filter_claim(self):
|
||||
"""
|
||||
Serialize for use in JWT filter claim.
|
||||
"""
|
||||
return six.text_type(':'.join([self.relation_type, self.organization.short_name]))
|
||||
|
||||
@@ -8,9 +8,8 @@ import pytz
|
||||
from factory.django import DjangoModelFactory
|
||||
from factory.fuzzy import FuzzyText
|
||||
from oauth2_provider.models import AccessToken, Application, RefreshToken
|
||||
from organizations.tests.factories import OrganizationFactory
|
||||
|
||||
from openedx.core.djangoapps.oauth_dispatch.models import ApplicationAccess, ApplicationOrganization
|
||||
from openedx.core.djangoapps.oauth_dispatch.models import ApplicationAccess
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@@ -34,15 +33,6 @@ class ApplicationAccessFactory(DjangoModelFactory):
|
||||
scopes = ['grades:read']
|
||||
|
||||
|
||||
class ApplicationOrganizationFactory(DjangoModelFactory):
|
||||
class Meta(object):
|
||||
model = ApplicationOrganization
|
||||
|
||||
application = factory.SubFactory(ApplicationFactory)
|
||||
organization = factory.SubFactory(OrganizationFactory)
|
||||
relation_type = ApplicationOrganization.RELATION_TYPE_CONTENT_ORG
|
||||
|
||||
|
||||
class AccessTokenFactory(DjangoModelFactory):
|
||||
class Meta(object):
|
||||
model = AccessToken
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
"""
|
||||
Tests for oauth_dispatch models.
|
||||
"""
|
||||
|
||||
|
||||
import six
|
||||
from django.test import TestCase
|
||||
|
||||
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationOrganizationFactory
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class ApplicationOrganizationTestCase(TestCase):
|
||||
"""
|
||||
Tests for the ApplicationOrganization model.
|
||||
"""
|
||||
def test_to_jwt_filter_claim(self):
|
||||
""" Verify to_jwt_filter_claim returns the expected serialization of the model. """
|
||||
org_relation = ApplicationOrganizationFactory()
|
||||
organization = org_relation.organization
|
||||
jwt_filter_claim = org_relation.to_jwt_filter_claim()
|
||||
assert jwt_filter_claim == six.text_type(':'.join([org_relation.relation_type, organization.short_name]))
|
||||
@@ -15,7 +15,6 @@ from django.urls import reverse
|
||||
from jwkest import jwk
|
||||
from mock import call, patch
|
||||
from oauth2_provider import models as dot_models
|
||||
from organizations.tests.factories import OrganizationFactory
|
||||
from provider import constants
|
||||
|
||||
from openedx.core.djangoapps.oauth_dispatch.toggles import ENFORCE_JWT_SCOPES
|
||||
@@ -107,10 +106,6 @@ class _DispatchingViewTestCase(TestCase):
|
||||
application=self.dot_app,
|
||||
scopes=['grades:read'],
|
||||
)
|
||||
self.dot_app_org = models.ApplicationOrganization.objects.create(
|
||||
application=self.dot_app,
|
||||
organization=OrganizationFactory()
|
||||
)
|
||||
|
||||
# Create a "restricted" DOT Application which means any AccessToken/JWT
|
||||
# generated for this application will be immediately expired
|
||||
@@ -350,10 +345,6 @@ class TestAccessTokenView(AccessTokenLoginMixin, mixins.AccessTokenMixin, _Dispa
|
||||
scopes=['grades:read'],
|
||||
filters=['test:filter'],
|
||||
)
|
||||
models.ApplicationOrganization.objects.create(
|
||||
application=dot_app,
|
||||
organization=OrganizationFactory()
|
||||
)
|
||||
scopes = dot_app_access.scopes
|
||||
filters = self.dot_adapter.get_authorization_filters(dot_app)
|
||||
assert 'test:filter' in filters
|
||||
@@ -416,10 +407,10 @@ class TestAuthorizationView(_DispatchingViewTestCase):
|
||||
models.ApplicationAccess.objects.create(
|
||||
application=self.dot_app,
|
||||
scopes=['grades:read'],
|
||||
)
|
||||
self.dot_app_org = models.ApplicationOrganization.objects.create(
|
||||
application=self.dot_app,
|
||||
organization=OrganizationFactory()
|
||||
filters=[
|
||||
'content_org:test content org',
|
||||
'other_filter:filter_val',
|
||||
]
|
||||
)
|
||||
self.dop_app = self.dop_adapter.create_confidential_client(
|
||||
name='test dop client',
|
||||
@@ -497,7 +488,13 @@ class TestAuthorizationView(_DispatchingViewTestCase):
|
||||
# Are the content provider organizations listed on the page?
|
||||
self.assertContains(
|
||||
response,
|
||||
'<li>{org}</li>'.format(org=self.dot_app_org.organization.name)
|
||||
'<li>{org}</li>'.format(org='test content org')
|
||||
)
|
||||
|
||||
# Make sure other filters don't show up as orgs.
|
||||
self.assertNotContains(
|
||||
response,
|
||||
'<li>{org}</li>'.format(org='filter_val')
|
||||
)
|
||||
|
||||
def _check_dot_response(self, response):
|
||||
|
||||
Reference in New Issue
Block a user