diff --git a/oauth2_provider/admin.py b/oauth2_provider/admin.py index d3c764adf..2135877b1 100644 --- a/oauth2_provider/admin.py +++ b/oauth2_provider/admin.py @@ -1,12 +1,14 @@ from django.contrib import admin -from .models import Grant, AccessToken, RefreshToken, get_application_model +from .models import Grant, get_access_token_model, get_refresh_token_model, get_application_model class RawIDAdmin(admin.ModelAdmin): raw_id_fields = ('user',) Application = get_application_model() +AccessToken = get_access_token_model() +RefreshToken = get_refresh_token_model() admin.site.register(Application, RawIDAdmin) admin.site.register(Grant, RawIDAdmin) diff --git a/oauth2_provider/migrations/0003_auto_20150909_1131.py b/oauth2_provider/migrations/0003_auto_20150909_1131.py new file mode 100644 index 000000000..75a2db3e1 --- /dev/null +++ b/oauth2_provider/migrations/0003_auto_20150909_1131.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('oauth2_provider', '0002_08_updates'), + ] + + operations = [ + migrations.AlterField( + model_name='accesstoken', + name='application', + field=models.ForeignKey(related_name='accesstoken_set', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), + ), + migrations.AlterField( + model_name='accesstoken', + name='user', + field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, related_name='accesstoken_set'), + ), + migrations.AlterField( + model_name='refreshtoken', + name='access_token', + field=models.OneToOneField(to=settings.OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL, related_name='refresh_token'), + ), + migrations.AlterField( + model_name='refreshtoken', + name='application', + field=models.ForeignKey(related_name='refreshtoken_set', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), + ), + migrations.AlterField( + model_name='refreshtoken', + name='user', + field=models.ForeignKey(related_name='refreshtoken_set', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 1d26726be..beb972099 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -164,26 +164,15 @@ def __str__(self): return self.code -@python_2_unicode_compatible -class AccessToken(models.Model): - """ - An AccessToken instance represents the actual access token to - access user's resources, as in :rfc:`5`. - - Fields: - - * :attr:`user` The Django user representing resources' owner - * :attr:`token` Access token - * :attr:`application` Application instance - * :attr:`expires` Date and time of token expiration, in DateTime format - * :attr:`scope` Allowed scopes - """ - user = models.ForeignKey(AUTH_USER_MODEL, blank=True, null=True) - token = models.CharField(max_length=255, db_index=True) - application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL) +class AbstractAccessToken(models.Model): + user = models.ForeignKey(AUTH_USER_MODEL, related_name='accesstoken_set', blank=True, null=True) + application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, related_name='accesstoken_set') expires = models.DateTimeField() scope = models.TextField(blank=True) + class Meta: + abstract = True + def is_valid(self, scopes=None): """ Checks if the access token is valid. @@ -219,12 +208,50 @@ def revoke(self): """ self.delete() + +@python_2_unicode_compatible +class AccessToken(AbstractAccessToken): + """ + An AccessToken instance represents the actual access token to + access user's resources, as in :rfc:`5`. + + Fields: + + * :attr:`user` The Django user representing resources' owner + * :attr:`token` Access token + * :attr:`application` Application instance + * :attr:`expires` Expire time in seconds, defaults to + :data:`settings.ACCESS_TOKEN_EXPIRE_SECONDS` + * :attr:`scope` Allowed scopes + """ + token = models.CharField(max_length=255, db_index=True) + def __str__(self): return self.token +# Add swappable like this to not break django 1.4 compatibility +AccessToken._meta.swappable = 'OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL' + + +class AbstractRefreshToken(models.Model): + user = models.ForeignKey(AUTH_USER_MODEL, related_name='refreshtoken_set') + application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, related_name='refreshtoken_set') + access_token = models.OneToOneField(oauth2_settings.ACCESS_TOKEN_MODEL, + related_name='refresh_token') + + class Meta: + abstract = True + + def revoke(self): + """ + Delete this refresh token along with related access token + """ + get_access_token_model().objects.get(id=self.access_token.id).revoke() + self.delete() + @python_2_unicode_compatible -class RefreshToken(models.Model): +class RefreshToken(AbstractRefreshToken): """ A RefreshToken instance represents a token that can be swapped for a new access token when it expires. @@ -237,22 +264,14 @@ class RefreshToken(models.Model): * :attr:`access_token` AccessToken instance this refresh token is bounded to """ - user = models.ForeignKey(AUTH_USER_MODEL) token = models.CharField(max_length=255, db_index=True) - application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL) - access_token = models.OneToOneField(AccessToken, - related_name='refresh_token') - - def revoke(self): - """ - Delete this refresh token along with related access token - """ - AccessToken.objects.get(id=self.access_token.id).revoke() - self.delete() def __str__(self): return self.token +# Add swappable like this to not break django 1.4 compatibility +RefreshToken._meta.swappable = 'OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL' + def get_application_model(): """ Return the Application model that is active in this project. """ @@ -266,3 +285,31 @@ def get_application_model(): e = "APPLICATION_MODEL refers to model {0} that has not been installed" raise ImproperlyConfigured(e.format(oauth2_settings.APPLICATION_MODEL)) return app_model + + +def get_access_token_model(): + """ Return the AccessToken model that is active in this project. """ + try: + app_label, model_name = oauth2_settings.ACCESS_TOKEN_MODEL.split('.') + except ValueError: + e = "ACCESS_TOKEN_MODEL must be of the form 'app_label.model_name'" + raise ImproperlyConfigured(e) + access_token_model = get_model(app_label, model_name) + if access_token_model is None: + e = "ACCESS_TOKEN_MODEL refers to model {0} that has not been installed" + raise ImproperlyConfigured(e.format(oauth2_settings.ACCESS_TOKEN_MODEL)) + return access_token_model + + +def get_refresh_token_model(): + """ Return the RefreshToken model that is active in this project. """ + try: + app_label, model_name = oauth2_settings.REFRESH_TOKEN_MODEL.split('.') + except ValueError: + e = "REFRESH_TOKEN_MODEL must be of the form 'app_label.model_name'" + raise ImproperlyConfigured(e) + refresh_token_model = get_model(app_label, model_name) + if refresh_token_model is None: + e = "REFRESH_TOKEN_MODEL refers to model {0} that has not been installed" + raise ImproperlyConfigured(e.format(oauth2_settings.REFRESH_TOKEN_MODEL)) + return refresh_token_model diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index cc669f04f..97a6c04f9 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -11,7 +11,7 @@ from oauthlib.oauth2 import RequestValidator from .compat import unquote_plus -from .models import Grant, AccessToken, RefreshToken, get_application_model, AbstractApplication +from .models import Grant, get_access_token_model, get_refresh_token_model, get_application_model, AbstractApplication from .settings import oauth2_settings log = logging.getLogger('oauth2_provider') @@ -219,6 +219,9 @@ def validate_bearer_token(self, token, scopes, request): if not token: return False + # Load the AccessToken model + AccessToken = get_access_token_model() + try: access_token = AccessToken.objects.select_related("application", "user").get( token=token) @@ -290,6 +293,13 @@ def save_bearer_token(self, token, request, *args, **kwargs): Save access and refresh token, If refresh token is issued, remove old refresh tokens as in rfc:`6` """ + + # Load the AccessToken model + AccessToken = get_access_token_model() + + # Load the RefreshToken model + RefreshToken = get_refresh_token_model() + if request.refresh_token: # remove used refresh token try: @@ -332,6 +342,12 @@ def revoke_token(self, token, token_type_hint, request, *args, **kwargs): if token_type_hint not in ['access_token', 'refresh_token']: token_type_hint = None + # Load the AccessToken model + AccessToken = get_access_token_model() + + # Load the RefreshToken model + RefreshToken = get_refresh_token_model() + token_types = { 'access_token': AccessToken, 'refresh_token': RefreshToken, @@ -366,6 +382,9 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs Check refresh_token exists and refers to the right client. Also attach User instance to the request object """ + # Load the RefreshToken model + RefreshToken = get_refresh_token_model() + try: rt = RefreshToken.objects.get(token=refresh_token) request.user = rt.user diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index db5768686..e0a37b937 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -41,6 +41,8 @@ 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60, 'ACCESS_TOKEN_EXPIRE_SECONDS': 36000, 'APPLICATION_MODEL': getattr(settings, 'OAUTH2_PROVIDER_APPLICATION_MODEL', 'oauth2_provider.Application'), + 'ACCESS_TOKEN_MODEL': getattr(settings, 'OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL', 'oauth2_provider.AccessToken'), + 'REFRESH_TOKEN_MODEL': getattr(settings, 'OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL', 'oauth2_provider.RefreshToken'), 'REQUEST_APPROVAL_PROMPT': 'force', 'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'], diff --git a/oauth2_provider/tests/test_auth_backends.py b/oauth2_provider/tests/test_auth_backends.py index 24944664e..d44f3af41 100644 --- a/oauth2_provider/tests/test_auth_backends.py +++ b/oauth2_provider/tests/test_auth_backends.py @@ -7,13 +7,13 @@ from ..compat import get_user_model from ..models import get_application_model -from ..models import AccessToken +from ..models import get_access_token_model from ..backends import OAuth2Backend from ..middleware import OAuth2TokenMiddleware UserModel = get_user_model() ApplicationModel = get_application_model() - +AccessToken = get_access_token_model() class BaseTest(TestCase): """ diff --git a/oauth2_provider/tests/test_authorization_code.py b/oauth2_provider/tests/test_authorization_code.py index 5c51c35cf..a25a38e58 100644 --- a/oauth2_provider/tests/test_authorization_code.py +++ b/oauth2_provider/tests/test_authorization_code.py @@ -10,7 +10,7 @@ from django.utils import timezone from ..compat import urlparse, parse_qs, urlencode, get_user_model -from ..models import get_application_model, Grant, AccessToken, RefreshToken +from ..models import get_application_model, Grant, get_access_token_model, get_refresh_token_model from ..settings import oauth2_settings from ..views import ProtectedResourceView @@ -18,6 +18,8 @@ Application = get_application_model() +AccessToken = get_access_token_model() +RefreshToken = get_access_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_client_credential.py b/oauth2_provider/tests/test_client_credential.py index 8136f5d71..7a8d00a0f 100644 --- a/oauth2_provider/tests/test_client_credential.py +++ b/oauth2_provider/tests/test_client_credential.py @@ -13,7 +13,7 @@ from oauthlib.oauth2 import BackendApplicationServer -from ..models import get_application_model, AccessToken +from ..models import get_application_model, get_access_token_model from ..oauth2_backends import OAuthLibCore from ..oauth2_validators import OAuth2Validator from ..settings import oauth2_settings @@ -24,6 +24,7 @@ Application = get_application_model() +AccessToken = get_access_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_decorators.py b/oauth2_provider/tests/test_decorators.py index b9e22bc93..5a0339c1d 100644 --- a/oauth2_provider/tests/test_decorators.py +++ b/oauth2_provider/tests/test_decorators.py @@ -6,12 +6,13 @@ from ..decorators import protected_resource, rw_protected_resource from ..settings import oauth2_settings -from ..models import get_application_model, AccessToken +from ..models import get_application_model, get_access_token_model from ..compat import get_user_model from .test_utils import TestCaseUtils Application = get_application_model() +AccessToken = get_access_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_models.py b/oauth2_provider/tests/test_models.py index d082ab8dc..05538b649 100644 --- a/oauth2_provider/tests/test_models.py +++ b/oauth2_provider/tests/test_models.py @@ -11,11 +11,13 @@ from django.core.exceptions import ValidationError from django.utils import timezone -from ..models import get_application_model, Grant, AccessToken, RefreshToken +from ..models import get_application_model, Grant, get_access_token_model, get_refresh_token_model from ..compat import get_user_model Application = get_application_model() +AccessToken = get_access_token_model() +RefreshToken = get_refresh_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_rest_framework.py b/oauth2_provider/tests/test_rest_framework.py index ccf4e0b5a..d2767611c 100644 --- a/oauth2_provider/tests/test_rest_framework.py +++ b/oauth2_provider/tests/test_rest_framework.py @@ -7,12 +7,13 @@ from .test_utils import TestCaseUtils -from ..models import AccessToken, get_application_model +from ..models import get_access_token_model, get_application_model from ..settings import oauth2_settings from ..compat import get_user_model Application = get_application_model() +AccessToken = get_access_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_scopes.py b/oauth2_provider/tests/test_scopes.py index 76e8ea51a..6fd7ff109 100644 --- a/oauth2_provider/tests/test_scopes.py +++ b/oauth2_provider/tests/test_scopes.py @@ -8,11 +8,12 @@ from .test_utils import TestCaseUtils from ..compat import urlparse, parse_qs, get_user_model, urlencode -from ..models import get_application_model, Grant, AccessToken +from ..models import get_application_model, Grant, get_access_token_model from ..settings import oauth2_settings from ..views import ScopedProtectedResourceView, ReadWriteScopedResourceView Application = get_application_model() +AccessToken = get_access_token_model() UserModel = get_user_model() diff --git a/oauth2_provider/tests/test_token_revocation.py b/oauth2_provider/tests/test_token_revocation.py index ce8024fa9..c9e7c2b4f 100644 --- a/oauth2_provider/tests/test_token_revocation.py +++ b/oauth2_provider/tests/test_token_revocation.py @@ -7,13 +7,15 @@ from django.utils import timezone from ..compat import urlencode, get_user_model -from ..models import get_application_model, AccessToken, RefreshToken +from ..models import get_application_model, get_access_token_model, get_refresh_token_model from ..settings import oauth2_settings from .test_utils import TestCaseUtils Application = get_application_model() +AccessToken = get_access_token_model() +RefreshToken = get_refresh_token_model() UserModel = get_user_model()