Skip to content
Snippets Groups Projects
Commit bab6e366 authored by Douglas Hall's avatar Douglas Hall
Browse files

Add new custom DOT Application model to support OAuth2 per-application scopes.

This also introduces a model for persisting organization-based filters on
a per-application basis. See openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst
for additional details.
parent b2737709
No related merge requests found
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-20 18:22
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_mysql.models
import oauth2_provider.generators
import oauth2_provider.validators
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
('oauth_dispatch', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ScopedApplication',
fields=[
('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)),
('redirect_uris', models.TextField(blank=True, help_text='Allowed URIs list, space separated', validators=[oauth2_provider.validators.validate_uris])),
('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], max_length=32)),
('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials')], max_length=32)),
('client_secret', models.CharField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=255)),
('name', models.CharField(blank=True, max_length=255)),
('skip_authorization', models.BooleanField(default=False)),
('id', models.IntegerField(primary_key=True, serialize=False)),
('scopes', django_mysql.models.ListCharField(models.CharField(max_length=32), help_text='Comma-separated list of scopes that this application will be allowed to request.', max_length=825, size=25)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='oauth_dispatch_scopedapplication', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='ScopedApplicationOrganization',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('short_name', models.CharField(help_text='The short_name of an existing Organization.', max_length=255)),
('provider_type', models.CharField(choices=[(b'content_org', 'Content Provider')], default=b'content_org', max_length=32)),
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
],
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-05 13:19
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations
def migrate_application_data(apps, schema_editor):
"""
Migrate existing DOT Application models to new ScopedApplication models.
"""
Application = apps.get_model(settings.OAUTH2_PROVIDER_APPLICATION_MODEL)
ScopedApplication = apps.get_model('oauth_dispatch', 'ScopedApplication')
for application in Application.objects.all():
ScopedApplication.objects.update_or_create(
id=application.id,
defaults={
'client_id': application.client_id,
'user': application.user,
'redirect_uris': application.redirect_uris,
'client_type': application.client_type,
'authorization_grant_type': application.authorization_grant_type,
'client_secret': application.client_secret,
'name': application.name,
'skip_authorization': application.skip_authorization,
}
)
class Migration(migrations.Migration):
dependencies = [
('oauth_dispatch', '0002_scopedapplication_scopedapplicationorganization'),
]
operations = [
migrations.RunPython(migrate_application_data, reverse_code=migrations.RunPython.noop),
]
......@@ -5,6 +5,9 @@ Specialized models for oauth_dispatch djangoapp
from datetime import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django_mysql.models import ListCharField
from oauth2_provider.models import AbstractApplication
from oauth2_provider.settings import oauth2_settings
from pytz import utc
......@@ -43,3 +46,88 @@ class RestrictedApplication(models.Model):
is set at the beginning of the epoch which is Jan. 1, 1970
"""
return access_token.expires == datetime(1970, 1, 1, tzinfo=utc)
class ScopedApplication(AbstractApplication):
"""
Custom Django OAuth Toolkit Application model that enables the definition
of scopes that are authorized for the given Application.
"""
FILTER_USER_ME = 'user:me'
# TODO: Remove the id field once we perform the inital migrations for this model.
# We need to copy data over from the oauth2_provider.models.Application model to
# this new model with the intial migration and the model IDs will need to match
# so that existing AccessTokens will still work when switching over to the new model.
# Once we have the data copied over we can move back to an auto-increment primary key.
id = models.IntegerField(primary_key=True)
scopes = ListCharField(
base_field=models.CharField(max_length=32),
size=25,
max_length=(25 * 33), # 25 * 32 character scopes, plus commas
help_text=_('Comma-separated list of scopes that this application will be allowed to request.'),
)
class Meta:
app_label = 'oauth_dispatch'
def __unicode__(self):
"""
Return a unicode representation of this object.
"""
return u"<ScopedApplication '{name}'>".format(
name=self.name
)
@property
def authorization_filters(self):
"""
Return the list of authorization filters for this application.
"""
filters = [':'.join([org.provider_type, org.short_name]) for org in self.organizations.all()]
if self.authorization_grant_type == self.GRANT_CLIENT_CREDENTIALS:
filters.append(self.FILTER_USER_ME)
return filters
class ScopedApplicationOrganization(models.Model):
"""
Associates an organization to a given ScopedApplication including the
provider type of the organization so that organization-based filters
can be added to access tokens provided to the given Application.
See openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst
for the intended use of this model.
"""
CONTENT_PROVIDER_TYPE = 'content_org'
ORGANIZATION_PROVIDER_TYPES = (
(CONTENT_PROVIDER_TYPE, _('Content Provider')),
)
# In practice, short_name should match the short_name of an Organization model.
# This is not a foreign key because the organizations app is not installed by default.
short_name = models.CharField(
max_length=255,
help_text=_('The short_name of an existing Organization.'),
)
provider_type = models.CharField(
max_length=32,
choices=ORGANIZATION_PROVIDER_TYPES,
default=CONTENT_PROVIDER_TYPE,
)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL,
related_name='organizations',
)
class Meta:
app_label = 'oauth_dispatch'
def __unicode__(self):
"""
Return a unicode representation of this object.
"""
return u"<ScopedApplicationOrganization '{application_name}':'{org}'>".format(
application_name=self.application.name,
org=self.short_name,
)
......@@ -4,7 +4,6 @@
#
# make upgrade
#
common/lib/calc
common/lib/chem
common/lib/sandbox-packages
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
-e common/lib/calc
-e common/lib/chem
-e common/lib/sandbox-packages
......
......@@ -46,6 +46,7 @@ django-memcached-hashring
django-method-override==0.1.0
django-model-utils==3.0.0
django-mptt>=0.8.6,<0.9
django-mysql
django-oauth-toolkit==0.12.0
django-pyfs
django-ratelimit
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e common/lib/calc
-e common/lib/capa
......@@ -84,6 +83,7 @@ django-method-override==0.1.0
django-model-utils==3.0.0
django-mptt==0.8.7
django-multi-email-field==0.5.1 # via edx-enterprise
django-mysql==2.3.0
django-oauth-toolkit==0.12.0
django-object-actions==0.10.0 # via edx-enterprise
django-pyfs==2.0
......@@ -93,7 +93,7 @@ django-require==1.0.11
django-rest-swagger==2.2.0
django-sekizai==0.10.0
django-ses==0.8.4
django-simple-history==2.1.0
django-simple-history==2.1.1
django-splash==0.2.2
django-statici18n==1.4.0
django-storages==1.4.1
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
coverage==4.2
diff-cover==0.9.8
inflect==0.3.1 # via jinja2-pluralize
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e common/lib/calc
-e common/lib/capa
......@@ -44,7 +43,7 @@ git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.1.6#egg=xblock-d
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
git+https://github.com/edx/xblock-utils.git@v1.1.1#egg=xblock-utils==1.1.1
-e common/lib/xmodule
alabaster==0.7.10 # via sphinx
alabaster==0.7.11 # via sphinx
amqp==1.4.9
analytics-python==1.1.0
anyjson==0.3.3
......@@ -104,6 +103,7 @@ django-method-override==0.1.0
django-model-utils==3.0.0
django-mptt==0.8.7
django-multi-email-field==0.5.1
django-mysql==2.3.0
django-oauth-toolkit==0.12.0
django-object-actions==0.10.0
django-pyfs==2.0
......@@ -113,7 +113,7 @@ django-require==1.0.11
django-rest-swagger==2.2.0
django-sekizai==0.10.0
django-ses==0.8.4
django-simple-history==2.1.0
django-simple-history==2.1.1
django-splash==0.2.2
django-statici18n==1.4.0
django-storages==1.4.1
......@@ -156,7 +156,7 @@ event-tracking==0.2.4
execnet==1.5.0
extras==1.0.0
factory_boy==2.8.1
faker==0.8.15
faker==0.8.16
feedparser==5.1.3
firebase-token-generator==1.3.2
first==2.0.1
......@@ -270,7 +270,7 @@ pytest-django==3.1.2
pytest-forked==0.2
pytest-randomly==1.2.3
pytest-xdist==1.22.2
pytest==3.6.1
pytest==3.6.2
python-dateutil==2.4.0
python-levenshtein==0.12.0
python-memcached==1.48
......@@ -316,7 +316,7 @@ sqlparse==0.2.4 # via django-debug-toolbar
stevedore==1.10.0
sure==1.4.11
sympy==0.7.1
testfixtures==6.1.0
testfixtures==6.2.0
testtools==2.3.0
text-unidecode==1.2
tox-battery==0.5.1
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
argh==0.26.2 # via watchdog
argparse==1.4.0 # via stevedore
edx-opaque-keys==0.4.4
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
click==6.7 # via pip-tools
first==2.0.1 # via pip-tools
pip-tools==2.0.2
......
......@@ -4,7 +4,6 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e common/lib/calc
-e common/lib/capa
......@@ -100,6 +99,7 @@ django-method-override==0.1.0
django-model-utils==3.0.0
django-mptt==0.8.7
django-multi-email-field==0.5.1
django-mysql==2.3.0
django-oauth-toolkit==0.12.0
django-object-actions==0.10.0
django-pyfs==2.0
......@@ -109,7 +109,7 @@ django-require==1.0.11
django-rest-swagger==2.2.0
django-sekizai==0.10.0
django-ses==0.8.4
django-simple-history==2.1.0
django-simple-history==2.1.1
django-splash==0.2.2
django-statici18n==1.4.0
django-storages==1.4.1
......@@ -150,7 +150,7 @@ event-tracking==0.2.4
execnet==1.5.0 # via pytest-xdist
extras==1.0.0 # via python-subunit, testtools
factory_boy==2.8.1
faker==0.8.15 # via factory-boy
faker==0.8.16 # via factory-boy
feedparser==5.1.3
firebase-token-generator==1.3.2
fixtures==3.0.0 # via testtools
......@@ -259,7 +259,7 @@ pytest-django==3.1.2
pytest-forked==0.2 # via pytest-xdist
pytest-randomly==1.2.3
pytest-xdist==1.22.2
pytest==3.6.1
pytest==3.6.2
python-dateutil==2.4.0
python-levenshtein==0.12.0
python-memcached==1.48
......@@ -300,7 +300,7 @@ splinter==0.8.0
stevedore==1.10.0
sure==1.4.11
sympy==0.7.1
testfixtures==6.1.0
testfixtures==6.2.0
testtools==2.3.0 # via fixtures, python-subunit
text-unidecode==1.2 # via faker
tox-battery==0.5.1
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment