Skip to content

Commit

Permalink
Merge pull request #1 from allisson/openid-connect
Browse files Browse the repository at this point in the history
 Add support for oidc connect discovery
  • Loading branch information
wiliamsouza authored Mar 14, 2018
2 parents b2847e2 + 99948cf commit 266ff15
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 16 deletions.
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
nosetests.xml
Expand Down
6 changes: 3 additions & 3 deletions oauth2_provider/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class AbstractApplication(models.Model):
RS256_ALGORITHM = "RS256"
HS256_ALGORITHM = "HS256"
ALGORITHM_TYPES = (
("RS256", _("RSA with SHA-2 256")),
("HS256", _("HMAC with SHA-2 256")),
(RS256_ALGORITHM, _("RSA with SHA-2 256")),
(HS256_ALGORITHM, _("HMAC with SHA-2 256")),
)

id = models.BigAutoField(primary_key=True)
Expand Down Expand Up @@ -92,7 +92,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 = models.CharField(max_length=5, choices=ALGORITHM_TYPES, default=RS256_ALGORITHM)

class Meta:
abstract = True
Expand Down
4 changes: 2 additions & 2 deletions oauth2_provider/oauth2_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ def get_jwt_bearer_token(self, token, token_handler, request):

def get_id_token(self, token, token_handler, request):

key = jwk.JWK.from_pem(oauth2_settings.RSA_PRIVATE_KEY.encode("utf8"))
key = jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8"))

# TODO: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
# Save the id_token on database bound to code when the request come to
Expand Down Expand Up @@ -667,7 +667,7 @@ def validate_id_token(self, token, scopes, request):
if not token:
return False

key = jwk.JWK.from_pem(oauth2_settings.RSA_PRIVATE_KEY.encode("utf8"))
key = jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8"))

try:
jwt_token = jwt.JWT(key=key, jwt=token)
Expand Down
24 changes: 22 additions & 2 deletions oauth2_provider/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,21 @@
"REFRESH_TOKEN_MODEL": REFRESH_TOKEN_MODEL,
"REQUEST_APPROVAL_PROMPT": "force",
"ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https"],
"RSA_PRIVATE_KEY": "",
"OIDC_ISS_ENDPOINT": "",
"OIDC_USERINFO_ENDPOINT": "",
"OIDC_RSA_PRIVATE_KEY": "",
"OIDC_RESPONSE_TYPES_SUPPORTED": [
"code",
"token",
"id_token",
"id_token token",
"code token",
"code id_token",
"code id_token token",
],
"OIDC_SUBJECT_TYPES_SUPPORTED": ["public"],
"OIDC_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED": ["RS256", "HS256"],
"OIDC_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED": ["client_secret_post", "client_secret_basic"],

# Special settings that will be evaluated at runtime
"_SCOPES": [],
Expand All @@ -76,7 +90,13 @@
"OAUTH2_BACKEND_CLASS",
"SCOPES",
"ALLOWED_REDIRECT_URI_SCHEMES",
"RSA_PRIVATE_KEY",
"OIDC_ISS_ENDPOINT",
"OIDC_USERINFO_ENDPOINT",
"OIDC_RSA_PRIVATE_KEY",
"OIDC_RESPONSE_TYPES_SUPPORTED",
"OIDC_SUBJECT_TYPES_SUPPORTED",
"OIDC_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED",
"OIDC_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED"
)

# List of settings that may be in string import notation.
Expand Down
7 changes: 6 additions & 1 deletion oauth2_provider/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@
name="authorized-token-delete"),
]

oidc_urlpatterns = [
url(r"^\.well-known/openid-configuration/$", views.ConnectDiscoveryInfoView.as_view(), name="oidc-connect-discovery-info"),
url(r"^jwks/$", views.JwksInfoView.as_view(), name="jwks-info")
]


urlpatterns = base_urlpatterns + management_urlpatterns
urlpatterns = base_urlpatterns + management_urlpatterns + oidc_urlpatterns
16 changes: 11 additions & 5 deletions oauth2_provider/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# flake8: noqa
from .base import AuthorizationView, TokenView, RevokeTokenView
from .application import ApplicationRegistration, ApplicationDetail, ApplicationList, \
ApplicationDelete, ApplicationUpdate
from .generic import ProtectedResourceView, ScopedProtectedResourceView, ReadWriteScopedResourceView
from .token import AuthorizedTokensListView, AuthorizedTokenDeleteView
from .application import (
ApplicationDelete, ApplicationDetail, ApplicationList,
ApplicationRegistration, ApplicationUpdate
)
from .base import AuthorizationView, RevokeTokenView, TokenView
from .generic import (
ProtectedResourceView, ReadWriteScopedResourceView,
ScopedProtectedResourceView
)
from .introspect import IntrospectTokenView
from .oidc import ConnectDiscoveryInfoView, JwksInfoView
from .token import AuthorizedTokenDeleteView, AuthorizedTokensListView
51 changes: 51 additions & 0 deletions oauth2_provider/views/oidc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import absolute_import, unicode_literals

import json

from django.http import JsonResponse
from django.urls import reverse_lazy
from django.views.generic import View
from jwcrypto import jwk

from ..settings import oauth2_settings


class ConnectDiscoveryInfoView(View):
"""
View used to show oidc provider configuration information
"""
def get(self, request, *args, **kwargs):
issuer_url = oauth2_settings.OIDC_ISS_ENDPOINT
data = {
"issuer": issuer_url,
"authorization_endpoint": "{}{}".format(issuer_url, reverse_lazy("oauth2_provider:authorize")),
"token_endpoint": "{}{}".format(issuer_url, reverse_lazy("oauth2_provider:token")),
"userinfo_endpoint": oauth2_settings.OIDC_USERINFO_ENDPOINT,
"jwks_uri": "{}{}".format(issuer_url, reverse_lazy("oauth2_provider:jwks-info")),
"response_types_supported": oauth2_settings.OIDC_RESPONSE_TYPES_SUPPORTED,
"subject_types_supported": oauth2_settings.OIDC_SUBJECT_TYPES_SUPPORTED,
"id_token_signing_alg_values_supported": oauth2_settings.OIDC_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED,
"token_endpoint_auth_methods_supported": oauth2_settings.OIDC_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED,
}
response = JsonResponse(data)
response["Access-Control-Allow-Origin"] = "*"
return response


class JwksInfoView(View):
"""
View used to show oidc json web key set document
"""
def get(self, request, *args, **kwargs):
key = jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8"))
data = {
"keys": [{
"alg": "RS256",
"use": "sig",
"kid": key.thumbprint()
}]
}
data["keys"][0].update(json.loads(key.export_public()))
response = JsonResponse(data)
response["Access-Control-Allow-Origin"] = "*"
return response
6 changes: 6 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,9 @@
},
}
}

OAUTH2_PROVIDER = {
"OIDC_ISS_ENDPOINT": "http://localhost",
"OIDC_USERINFO_ENDPOINT": "http://localhost/userinfo/",
"OIDC_RSA_PRIVATE_KEY": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQCbCYh5h2NmQuBqVO6G+/CO+cHm9VBzsb0MeA6bbQfDnbhstVOT\nj0hcnZJzDjYc6ajBZZf6gxVP9xrdm9Uh599VI3X5PFXLbMHrmzTAMzCGIyg+/fnP\n0gocYxmCX2+XKyj/Zvt1pUX8VAN2AhrJSfxNDKUHERTVEV9bRBJg4F0C3wIDAQAB\nAoGAP+i4nNw+Ec/8oWh8YSFm4xE6qKG0NdTtSMAOyWwy+KTB+vHuT1QPsLn1vj77\n+IQrX/moogg6F1oV9YdA3vat3U7rwt1sBGsRrLhA+Spp9WEQtglguNo4+QfVo2ju\nYBa2rG+h75qjiA3xnU//F3rvwnAsOWv0NUVdVeguyR+u6okCQQDBUmgWeH2WHmUn\n2nLNCz+9wj28rqhfOr9Ptem2gqk+ywJmuIr4Y5S1OdavOr2UZxOcEwncJ/MLVYQq\nMH+x4V5HAkEAzU2GMR5OdVLcxfVTjzuIC76paoHVWnLibd1cdANpPmE6SM+pf5el\nfVSwuH9Fmlizu8GiPCxbJUoXB/J1tGEKqQJBALhClEU+qOzpoZ6/voYi/6kdN3zc\nuEy0EN6n09AKb8gS9QH1STgAqh+ltjMkeMe3C2DKYK5/QU9/Pc58lWl1FkcCQG67\nZamQgxjcvJ85FvymS1aqW45KwNysIlzHjFo2jMlMf7dN6kobbPMQftDENLJvLWIT\nqoFyGycdsxZiPAIyZSECQQCZFn3Dl6hnJxWZH8Fsa9hj79kZ/WVkIXGmtdgt0fNr\ndTnvCVtA59ne4LEVie/PMH/odQWY0SxVm/76uBZv/1vY\n-----END RSA PRIVATE KEY-----"
}
2 changes: 1 addition & 1 deletion tests/test_implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def setUp(self):
"write": "Writing scope",
"openid": "OpenID connect"
}
self.key = jwk.JWK.from_pem(oauth2_settings.RSA_PRIVATE_KEY.encode("utf8"))
self.key = jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8"))

def tearDown(self):
self.application.delete()
Expand Down
47 changes: 47 additions & 0 deletions tests/test_oidc_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from __future__ import unicode_literals

from django.test import TestCase
from django.urls import reverse


class TestConnectDiscoveryInfoView(TestCase):
def test_get_connect_discovery_info(self):
expected_response = {
"issuer": "http://localhost",
"authorization_endpoint": "http://localhost/o/authorize/",
"token_endpoint": "http://localhost/o/token/",
"userinfo_endpoint": "http://localhost/userinfo/",
"jwks_uri": "http://localhost/o/jwks/",
"response_types_supported": [
"code",
"token",
"id_token",
"id_token token",
"code token",
"code id_token",
"code id_token token"
],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256", "HS256"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"]
}
response = self.client.get(reverse("oauth2_provider:oidc-connect-discovery-info"))
self.assertEqual(response.status_code, 200)
assert response.json() == expected_response


class TestJwksInfoView(TestCase):
def test_get_jwks_info(self):
expected_response = {
"keys": [{
"alg": "RS256",
"use": "sig",
"kid": "s4a1o8mFEd1tATAIH96caMlu4hOxzBUaI2QTqbYNBHs",
"e": "AQAB",
"kty": "RSA",
"n": "mwmIeYdjZkLgalTuhvvwjvnB5vVQc7G9DHgOm20Hw524bLVTk49IXJ2Scw42HOmowWWX-oMVT_ca3ZvVIeffVSN1-TxVy2zB65s0wDMwhiMoPv35z9IKHGMZgl9vlyso_2b7daVF_FQDdgIayUn8TQylBxEU1RFfW0QSYOBdAt8"
}]
}
response = self.client.get(reverse("oauth2_provider:jwks-info"))
self.assertEqual(response.status_code, 200)
assert response.json() == expected_response
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ envlist =
django_find_project = false

[testenv]
commands = pytest --cov=oauth2_provider --cov-report= --cov-append {posargs}
commands =
pip install https://github.com/oauthlib/oauthlib/archive/master.tar.gz
pytest --cov=oauth2_provider --cov-report= --cov-append {posargs}
setenv =
DJANGO_SETTINGS_MODULE = tests.settings
PYTHONPATH = {toxinidir}
Expand Down

0 comments on commit 266ff15

Please sign in to comment.