Merge pull request #23199 from edx/robrap/BOM-1292-remove-application-organization

BOM-1292: remove uses of ApplicationOrganization
This commit is contained in:
Feanil Patel
2020-02-27 10:36:46 -05:00
committed by GitHub
6 changed files with 46 additions and 99 deletions

View File

@@ -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)

View File

@@ -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']

View File

@@ -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]))

View File

@@ -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

View File

@@ -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]))

View File

@@ -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):