Skip to content

Oauth toolkit auth service django5 #1589

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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion oauth2_provider/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@


class ApplicationAdmin(admin.ModelAdmin):
list_display = ("pk", "name", "user", "client_type", "authorization_grant_type")
list_display = (
"pk",
"name",
"user",
"access_token_expire_seconds",
"refresh_token_expire_seconds",
"client_type",
"authorization_grant_type",
)
list_filter = ("client_type", "authorization_grant_type", "skip_authorization")
radio_fields = {
"client_type": admin.HORIZONTAL,
Expand Down
2 changes: 2 additions & 0 deletions oauth2_provider/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class Migration(migrations.Migration):
('skip_authorization', models.BooleanField(default=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('access_token_expire_seconds', models.IntegerField(default=oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS)),
('refresh_token_expire_seconds', models.IntegerField(default=oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS)),
],
options={
'abstract': False,
Expand Down
29 changes: 29 additions & 0 deletions oauth2_provider/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ class AbstractApplication(models.Model):
default="",
)

access_token_expire_seconds = models.IntegerField(
default=oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
)
refresh_token_expire_seconds = models.IntegerField(
default=oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS
)
class Meta:
abstract = True

Expand Down Expand Up @@ -516,6 +522,29 @@ class AbstractRefreshToken(models.Model):
updated = models.DateTimeField(auto_now=True)
revoked = models.DateTimeField(null=True)

@property
def is_expired(self):
"""Determine if RefreshToken is expired."""
expire_seconds = self.application.refresh_token_expire_seconds
expires = self.created + timedelta(seconds=expire_seconds)

now = timezone.now()
is_refresh_token_expired = now >= expires

# Access token assumed to be expired, by default.
is_access_token_expired = True

# RefreshToken should not outlive AccessToken.
# NOTE: Check AccessToken expiration for backwards compatibility with
# long-lived tokens.
if self.access_token:
access_token_expires = self.access_token.expires
is_access_token_expired = now >= access_token_expires

# RefreshToken expired if and only if both refresh and access tokens
# are expired.
return is_refresh_token_expired and is_access_token_expired

def revoke(self):
"""
Mark this refresh token revoked and revoke related access token
Expand Down
49 changes: 40 additions & 9 deletions oauth2_provider/oauth2_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,14 +589,22 @@ def _save_bearer_token(self, token, request, *args, **kwargs):
if "scope" not in token:
raise FatalClientError("Failed to renew access token: missing scope")

# "authenticate_client" sets the client (Application) on request.
app = request.client

# Users on older app versions should get long-lived tokens for
# backwards compatibility.
TRUE_VALUES = [True, 'True', 'true']
is_legacy_token = getattr(request, 'is_legacy_token', False)

if is_legacy_token in TRUE_VALUES:
expire_seconds = oauth2_settings.LEGACY_ACCESS_TOKEN_EXPIRE_SECONDS
else:
expire_seconds = app.access_token_expire_seconds

# expires_in is passed to Server on initialization
# custom server class can have logic to override this
expires = timezone.now() + timedelta(
seconds=token.get(
"expires_in",
oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
)
)
expires = timezone.now() + timedelta(seconds=expire_seconds)

if request.grant_type == "client_credentials":
request.user = None
Expand Down Expand Up @@ -760,7 +768,10 @@ def validate_user(self, username, password, client, request, *args, **kwargs):
getattr(http_request, request.http_method).update(dict(request.decoded_body))
http_request.META = request.headers
u = authenticate(http_request, username=username, password=password)
if u is not None and u.is_active:

# NOTE: [11/20/2020] Removed check for u.is_active because the check
# will be made *before* calling DOT (Django OAuth Toolkit)
if u is not None:
request.user = u
return True
return False
Expand All @@ -782,11 +793,25 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs
Also attach User instance to the request object
"""

rt = RefreshToken.objects.filter(token=refresh_token).select_related("access_token").first()

rt = (
RefreshToken.objects
.filter(token=refresh_token)
.select_related("user", "access_token", "application")
.first()
)

if not rt:
return False

# Revoke token if expired.
if rt.is_expired:
try:
rt.revoke()
# Catch exception in case access or refresh token do not exist
except (AccessToken.DoesNotExist, RefreshToken.DoesNotExist):
pass

if rt.revoked is not None and rt.revoked <= timezone.now() - timedelta(
seconds=oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS
):
Expand All @@ -801,7 +826,13 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs
# Temporary store RefreshToken instance to be reused by get_original_scopes and save_bearer_token.
request.refresh_token_instance = rt

return rt.application == client
# Token is valid if it refers to the right client AND is not expired
is_valid = (
rt.application == client and
not rt.is_expired
)

return is_valid

def _save_id_token(self, jti, request, expires, *args, **kwargs):
scopes = request.scope or " ".join(request.scopes)
Expand Down
8 changes: 6 additions & 2 deletions oauth2_provider/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@
"READ_SCOPE": "read",
"WRITE_SCOPE": "write",
"AUTHORIZATION_CODE_EXPIRE_SECONDS": 60,
"ACCESS_TOKEN_EXPIRE_SECONDS": 36000,
"ACCESS_TOKEN_EXPIRE_SECONDS": 86400, # 24 hours in seconds.
"ID_TOKEN_EXPIRE_SECONDS": 36000,
"REFRESH_TOKEN_EXPIRE_SECONDS": None,
"REFRESH_TOKEN_EXPIRE_SECONDS": 31556952, # 1 year in seconds.

# Older app versions should get long-lived auth tokens.
"LEGACY_ACCESS_TOKEN_EXPIRE_SECONDS": 315569520, # 10 years

"REFRESH_TOKEN_GRACE_PERIOD_SECONDS": 0,
"REFRESH_TOKEN_REUSE_PROTECTION": False,
"ROTATE_REFRESH_TOKEN": True,
Expand Down