Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Configurable AccessToken and RefreshToken #252

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion 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)
Expand Down
40 changes: 40 additions & 0 deletions 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),
),
]
105 changes: 76 additions & 29 deletions oauth2_provider/models.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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. """
Expand All @@ -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
21 changes: 20 additions & 1 deletion oauth2_provider/oauth2_validators.py
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions oauth2_provider/settings.py
Expand Up @@ -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'],

Expand Down
4 changes: 2 additions & 2 deletions oauth2_provider/tests/test_auth_backends.py
Expand Up @@ -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):
"""
Expand Down
4 changes: 3 additions & 1 deletion oauth2_provider/tests/test_authorization_code.py
Expand Up @@ -10,14 +10,16 @@
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

from .test_utils import TestCaseUtils


Application = get_application_model()
AccessToken = get_access_token_model()
RefreshToken = get_access_token_model()
UserModel = get_user_model()


Expand Down
3 changes: 2 additions & 1 deletion oauth2_provider/tests/test_client_credential.py
Expand Up @@ -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
Expand All @@ -24,6 +24,7 @@


Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()


Expand Down
3 changes: 2 additions & 1 deletion oauth2_provider/tests/test_decorators.py
Expand Up @@ -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()


Expand Down
4 changes: 3 additions & 1 deletion oauth2_provider/tests/test_models.py
Expand Up @@ -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()


Expand Down
3 changes: 2 additions & 1 deletion oauth2_provider/tests/test_rest_framework.py
Expand Up @@ -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()


Expand Down
3 changes: 2 additions & 1 deletion oauth2_provider/tests/test_scopes.py
Expand Up @@ -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()


Expand Down
4 changes: 3 additions & 1 deletion oauth2_provider/tests/test_token_revocation.py
Expand Up @@ -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()


Expand Down