diff --git a/openedx/core/djangoapps/oauth_dispatch/migrations/0002_scopedapplication_scopedapplicationorganization.py b/openedx/core/djangoapps/oauth_dispatch/migrations/0002_scopedapplication_scopedapplicationorganization.py new file mode 100644 index 0000000000000000000000000000000000000000..17e78f09a48171c99b42e31a44f989833e7616cc --- /dev/null +++ b/openedx/core/djangoapps/oauth_dispatch/migrations/0002_scopedapplication_scopedapplicationorganization.py @@ -0,0 +1,46 @@ +# -*- 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)), + ], + ), + ] diff --git a/openedx/core/djangoapps/oauth_dispatch/migrations/0003_application_data.py b/openedx/core/djangoapps/oauth_dispatch/migrations/0003_application_data.py new file mode 100644 index 0000000000000000000000000000000000000000..2727d5960fe697828dab6547a632eb898fd150f3 --- /dev/null +++ b/openedx/core/djangoapps/oauth_dispatch/migrations/0003_application_data.py @@ -0,0 +1,40 @@ +# -*- 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), + ] diff --git a/openedx/core/djangoapps/oauth_dispatch/models.py b/openedx/core/djangoapps/oauth_dispatch/models.py index 6c661e36477e7bec13da77c3d23300b09274a2df..0faeb1f0f8a28234c5d3b4fa5d937c0330990150 100644 --- a/openedx/core/djangoapps/oauth_dispatch/models.py +++ b/openedx/core/djangoapps/oauth_dispatch/models.py @@ -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, + ) diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index e9615245188a9774389fc35238b1b5dba49fb6cd..d5054928210f1e19f84ce9bd3fd6b0f2b91e3b2c 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -4,7 +4,6 @@ # # make upgrade # - common/lib/calc common/lib/chem common/lib/sandbox-packages diff --git a/requirements/edx-sandbox/shared.txt b/requirements/edx-sandbox/shared.txt index 9967096c8c0ca1b638ed4eda31fe9bcb132006cc..9c112a860a4fec3a6e4194125cdbb4f92af9afeb 100644 --- a/requirements/edx-sandbox/shared.txt +++ b/requirements/edx-sandbox/shared.txt @@ -4,7 +4,6 @@ # # make upgrade # - -e common/lib/calc -e common/lib/chem -e common/lib/sandbox-packages diff --git a/requirements/edx/base.in b/requirements/edx/base.in index 9a73deb015248f5b90068c481d7489edda8b8aa3..92d70aa0ff25764a5b53ad41233aa6571979eab3 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -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 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index bc68eb0d0f6a94b82c6fa89ed8fc7080fd6abeb5..2efae32450570df98798e3e72ab0e497f129d68e 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -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 diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt index c721171b4352594d172a109434b7ed4e726160b0..2ee117248633a7370d6ef9a90a4f4d4446b8a8b8 100644 --- a/requirements/edx/coverage.txt +++ b/requirements/edx/coverage.txt @@ -4,7 +4,6 @@ # # make upgrade # - coverage==4.2 diff-cover==0.9.8 inflect==0.3.1 # via jinja2-pluralize diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 358fbcd3f67c48f85d291c6a4c542a5a426b978a..069fbd6a30667ac77047d40153300d387ff097b9 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -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 diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt index 697c166940116662a6a5e67dc7a7300492608635..9a6a6a4b3ee23d2de190162cd698048648282040 100644 --- a/requirements/edx/paver.txt +++ b/requirements/edx/paver.txt @@ -4,7 +4,6 @@ # # make upgrade # - argh==0.26.2 # via watchdog argparse==1.4.0 # via stevedore edx-opaque-keys==0.4.4 diff --git a/requirements/edx/pip-tools.txt b/requirements/edx/pip-tools.txt index 44aa65b2a3db3f7d157707ec5a6a178b34be3a38..e7027f3fb21c5071c9b7628a407345d2b12bd001 100644 --- a/requirements/edx/pip-tools.txt +++ b/requirements/edx/pip-tools.txt @@ -4,7 +4,6 @@ # # make upgrade # - click==6.7 # via pip-tools first==2.0.1 # via pip-tools pip-tools==2.0.2 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 40bdf4608b3173c4b039d62870174b6d8589845e..f7c09f2049ede6d4a1d529939387109cba47fde2 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -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