Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dc20d26
Add OpenID connect hybrid grant type
wiliamsouza Oct 29, 2017
106e2fa
Add OpenID connect algorithm type to Application model
wiliamsouza Oct 29, 2017
b3795b7
Add OpenID connect id token model
wiliamsouza Oct 29, 2017
7f39d4c
Add nonce Authorization as required by OpenID connect Implicit Flow
wiliamsouza Oct 29, 2017
5e0f53a
Add body to create_authorization_response to pass nonce and future Op…
wiliamsouza Oct 29, 2017
9a393b6
Add OpenID connect ID token creation and validation methods and scopes
wiliamsouza Oct 29, 2017
c8a1c87
Add OpenID connect response types
wiliamsouza Oct 29, 2017
a352189
Add OpenID connect authorization code flow test
wiliamsouza Oct 29, 2017
33d1333
Add OpenID connect implicit flow tests
wiliamsouza Oct 29, 2017
ca0c508
Add validate_user_match method to OAuth2Validator
wiliamsouza Oct 29, 2017
67940bc
Add RSA_PRIVATE_KEY setting with blank value
wiliamsouza Oct 29, 2017
56cf5ff
Update tox
wiliamsouza Oct 29, 2017
6e8af82
Add get_jwt_bearer_token to OAuth2Validator
wiliamsouza Dec 19, 2017
62776ee
Add validate_jwt_bearer_token to OAuth2Validator
wiliamsouza Dec 19, 2017
975496f
Add allowed_schemes to HttpResponseUriRedirect
wiliamsouza Dec 19, 2017
fe43b36
Change OAuth2Validator.validate_id_token default return value to Fals…
wiliamsouza Dec 19, 2017
7b48376
Change to use .encode to avoid py2.7 tox test error
wiliamsouza Jan 15, 2018
395efd9
Add OpenID connect hybrid flow tests
wiliamsouza Jan 15, 2018
6ab6fe0
Change to use .encode to avoid py2.7 tox test error
wiliamsouza Jan 16, 2018
3b3e8a1
Add RSA_PRIVATE_KEY to the list of settings that cannot be empt
wiliamsouza Jan 18, 2018
bda7d42
Add support for oidc connect discovery
allisson Mar 14, 2018
2d8a78b
Use double quotes for strings
allisson Mar 14, 2018
ad093ed
fix some tests
Jan 8, 2019
0c24056
remove upper bound for `oauthlib`
Jan 8, 2019
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ __pycache__
pip-log.txt

# Unit test / coverage reports
.cache
.pytest_cache
.coverage
.tox
.pytest_cache/
Expand Down
14 changes: 12 additions & 2 deletions oauth2_provider/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from django.contrib import admin

from .models import (
get_access_token_model, get_application_model,
get_grant_model, get_refresh_token_model
get_access_token_model,
get_application_model,
get_grant_model,
get_refresh_token_model,
get_id_token_model,
)


Expand All @@ -26,6 +29,11 @@ class AccessTokenAdmin(admin.ModelAdmin):
raw_id_fields = ("user", )


class IDTokenAdmin(admin.ModelAdmin):
list_display = ("token", "user", "application", "expires")
raw_id_fields = ("user", )


class RefreshTokenAdmin(admin.ModelAdmin):
list_display = ("token", "user", "application")
raw_id_fields = ("user", "access_token")
Expand All @@ -34,9 +42,11 @@ class RefreshTokenAdmin(admin.ModelAdmin):
Application = get_application_model()
Grant = get_grant_model()
AccessToken = get_access_token_model()
IDToken = get_id_token_model()
RefreshToken = get_refresh_token_model()

admin.site.register(Application, ApplicationAdmin)
admin.site.register(Grant, GrantAdmin)
admin.site.register(AccessToken, AccessTokenAdmin)
admin.site.register(IDToken, IDTokenAdmin)
admin.site.register(RefreshToken, RefreshTokenAdmin)
1 change: 1 addition & 0 deletions oauth2_provider/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class AllowForm(forms.Form):
allow = forms.BooleanField(required=False)
redirect_uri = forms.CharField(widget=forms.HiddenInput())
scope = forms.CharField(widget=forms.HiddenInput())
nonce = forms.CharField(required=False, widget=forms.HiddenInput())
client_id = forms.CharField(widget=forms.HiddenInput())
state = forms.CharField(required=False, widget=forms.HiddenInput())
response_type = forms.CharField(widget=forms.HiddenInput())
20 changes: 20 additions & 0 deletions oauth2_provider/migrations/0006_auto_20170903_1632.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-03 16:32
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('oauth2_provider', '0005_auto_20170514_1141'),
]

operations = [
migrations.AlterField(
model_name='application',
name='authorization_grant_type',
field=models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials'), ('openid-hybrid', 'OpenID connect hybrid')], max_length=32),
),
]
20 changes: 20 additions & 0 deletions oauth2_provider/migrations/0007_application_algorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-16 18:55
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('oauth2_provider', '0006_auto_20170903_1632'),
]

operations = [
migrations.AddField(
model_name='application',
name='algorithm',
field=models.CharField(choices=[('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='RS256', max_length=5),
),
]
38 changes: 38 additions & 0 deletions oauth2_provider/migrations/0008_idtoken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-01 19:13
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion

from oauth2_provider.settings import oauth2_settings


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
migrations.swappable_dependency(oauth2_settings.APPLICATION_MODEL),
('oauth2_provider', '0007_application_algorithm'),
]

operations = [
migrations.CreateModel(
name='IDToken',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('token', models.TextField(unique=True)),
('expires', models.DateTimeField()),
('scope', models.TextField(blank=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=oauth2_settings.APPLICATION_MODEL)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='oauth2_provider_idtoken', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'swappable': 'OAUTH2_PROVIDER_ID_TOKEN_MODEL',
},
),
]
14 changes: 14 additions & 0 deletions oauth2_provider/migrations/0009_merge_20190108_1732.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.1.5 on 2019-01-08 17:32

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('oauth2_provider', '0006_auto_20171214_2232'),
('oauth2_provider', '0008_idtoken'),
]

operations = [
]
102 changes: 102 additions & 0 deletions oauth2_provider/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,20 @@ class AbstractApplication(models.Model):
GRANT_IMPLICIT = "implicit"
GRANT_PASSWORD = "password"
GRANT_CLIENT_CREDENTIALS = "client-credentials"
GRANT_OPENID_HYBRID = "openid-hybrid"
GRANT_TYPES = (
(GRANT_AUTHORIZATION_CODE, _("Authorization code")),
(GRANT_IMPLICIT, _("Implicit")),
(GRANT_PASSWORD, _("Resource owner password-based")),
(GRANT_CLIENT_CREDENTIALS, _("Client credentials")),
(GRANT_OPENID_HYBRID, _("OpenID connect hybrid")),
)

RS256_ALGORITHM = "RS256"
HS256_ALGORITHM = "HS256"
ALGORITHM_TYPES = (
(RS256_ALGORITHM, _("RSA with SHA-2 256")),
(HS256_ALGORITHM, _("HMAC with SHA-2 256")),
)

id = models.BigAutoField(primary_key=True)
Expand Down Expand Up @@ -78,6 +87,7 @@ class AbstractApplication(models.Model):

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
algorithm = models.CharField(max_length=5, choices=ALGORITHM_TYPES, default=RS256_ALGORITHM)

class Meta:
abstract = True
Expand Down Expand Up @@ -398,6 +408,93 @@ class Meta(AbstractRefreshToken.Meta):
swappable = "OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL"


class AbstractIDToken(models.Model):
"""
An IDToken instance represents the actual token to
access user's resources, as in :openid:`2`.

Fields:

* :attr:`user` The Django user representing resources' owner
* :attr:`token` ID token
* :attr:`application` Application instance
* :attr:`expires` Date and time of token expiration, in DateTime format
* :attr:`scope` Allowed scopes
"""
id = models.BigAutoField(primary_key=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True,
related_name="%(app_label)s_%(class)s"
)
token = models.TextField(unique=True)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, blank=True, null=True,
)
expires = models.DateTimeField()
scope = models.TextField(blank=True)

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

def is_valid(self, scopes=None):
"""
Checks if the access token is valid.

:param scopes: An iterable containing the scopes to check or None
"""
return not self.is_expired() and self.allow_scopes(scopes)

def is_expired(self):
"""
Check token expiration with timezone awareness
"""
if not self.expires:
return True

return timezone.now() >= self.expires

def allow_scopes(self, scopes):
"""
Check if the token allows the provided scopes

:param scopes: An iterable containing the scopes to check
"""
if not scopes:
return True

provided_scopes = set(self.scope.split())
resource_scopes = set(scopes)

return resource_scopes.issubset(provided_scopes)

def revoke(self):
"""
Convenience method to uniform tokens' interface, for now
simply remove this token from the database in order to revoke it.
"""
self.delete()

@property
def scopes(self):
"""
Returns a dictionary of allowed scope names (as keys) with their descriptions (as values)
"""
all_scopes = get_scopes_backend().get_all_scopes()
token_scopes = self.scope.split()
return {name: desc for name, desc in all_scopes.items() if name in token_scopes}

def __str__(self):
return self.token

class Meta:
abstract = True


class IDToken(AbstractIDToken):
class Meta(AbstractIDToken.Meta):
swappable = "OAUTH2_PROVIDER_ID_TOKEN_MODEL"


def get_application_model():
""" Return the Application model that is active in this project. """
return apps.get_model(oauth2_settings.APPLICATION_MODEL)
Expand All @@ -413,6 +510,11 @@ def get_access_token_model():
return apps.get_model(oauth2_settings.ACCESS_TOKEN_MODEL)


def get_id_token_model():
""" Return the AccessToken model that is active in this project. """
return apps.get_model(oauth2_settings.ID_TOKEN_MODEL)


def get_refresh_token_model():
""" Return the RefreshToken model that is active in this project. """
return apps.get_model(oauth2_settings.REFRESH_TOKEN_MODEL)
Expand Down
11 changes: 6 additions & 5 deletions oauth2_provider/oauth2_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,16 @@ def validate_authorization_request(self, request):
except oauth2.OAuth2Error as error:
raise OAuthToolkitError(error=error)

def create_authorization_response(self, request, scopes, credentials, allow):
def create_authorization_response(self, uri, request, scopes, credentials, body, allow):
"""
A wrapper method that calls create_authorization_response on `server_class`
instance.

:param request: The current django.http.HttpRequest object
:param scopes: A list of provided scopes
:param credentials: Authorization credentials dictionary containing
`client_id`, `state`, `redirect_uri`, `response_type`
`client_id`, `state`, `redirect_uri` and `response_type`
:param body: Other body parameters not used in credentials dictionary
:param allow: True if the user authorize the client, otherwise False
"""
try:
Expand All @@ -115,10 +116,10 @@ def create_authorization_response(self, request, scopes, credentials, allow):
credentials["user"] = request.user

headers, body, status = self.server.create_authorization_response(
uri=credentials["redirect_uri"], scopes=scopes, credentials=credentials)
uri = headers.get("Location", None)
uri=uri, scopes=scopes, credentials=credentials, body=body)
redirect_uri = headers.get("Location", None)

return uri, headers, body, status
return redirect_uri, headers, body, status

except oauth2.FatalClientError as error:
raise FatalClientError(error=error, redirect_uri=credentials["redirect_uri"])
Expand Down
Loading