commit
1e8352791d
11 changed files with 65 additions and 13 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### [Unreleased]
|
### [Unreleased]
|
||||||
|
|
||||||
|
### [0.4.2] - 2016-10-13
|
||||||
|
|
||||||
|
##### Added
|
||||||
|
- Support for client redirect URIs with query strings.
|
||||||
|
|
||||||
|
##### Fixed
|
||||||
|
- Bug when generating secret_key value using admin.
|
||||||
|
|
||||||
|
##### Changed
|
||||||
|
- Client is available to OIDC_EXTRA_SCOPE_CLAIMS implementations via `self.client`.
|
||||||
|
- The constructor signature for `ScopeClaims` has changed, it now is called with the `Token` as its single argument.
|
||||||
|
|
||||||
### [0.4.1] - 2016-10-03
|
### [0.4.1] - 2016-10-03
|
||||||
|
|
||||||
##### Changed
|
##### Changed
|
||||||
|
|
|
@ -41,8 +41,8 @@ Add the provider urls::
|
||||||
|
|
||||||
Generate server RSA key and run migrations (if you don't)::
|
Generate server RSA key and run migrations (if you don't)::
|
||||||
|
|
||||||
$ python manage.py creatersakey
|
|
||||||
$ python manage.py migrate
|
$ python manage.py migrate
|
||||||
|
$ python manage.py creatersakey
|
||||||
|
|
||||||
Add required variables to your project settings::
|
Add required variables to your project settings::
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,7 @@ Inside your oidc_provider_settings.py file add the following class::
|
||||||
# self.user - Django user instance.
|
# self.user - Django user instance.
|
||||||
# self.userinfo - Dict returned by OIDC_USERINFO function.
|
# self.userinfo - Dict returned by OIDC_USERINFO function.
|
||||||
# self.scopes - List of scopes requested.
|
# self.scopes - List of scopes requested.
|
||||||
|
# self.client - Client requesting this claims.
|
||||||
dic = {
|
dic = {
|
||||||
'bar': 'Something dynamic here',
|
'bar': 'Something dynamic here',
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@ STANDARD_CLAIMS = {
|
||||||
|
|
||||||
class ScopeClaims(object):
|
class ScopeClaims(object):
|
||||||
|
|
||||||
def __init__(self, user, scopes):
|
def __init__(self, token):
|
||||||
self.user = user
|
self.user = token.user
|
||||||
self.userinfo = settings.get('OIDC_USERINFO', import_str=True)(STANDARD_CLAIMS, self.user)
|
self.userinfo = settings.get('OIDC_USERINFO', import_str=True)(STANDARD_CLAIMS, self.user)
|
||||||
self.scopes = scopes
|
self.scopes = token.scope
|
||||||
|
self.client = token.client
|
||||||
|
|
||||||
def create_response_dic(self):
|
def create_response_dic(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -43,7 +43,8 @@ class TokenEndpoint(object):
|
||||||
|
|
||||||
self.params['client_id'] = client_id
|
self.params['client_id'] = client_id
|
||||||
self.params['client_secret'] = client_secret
|
self.params['client_secret'] = client_secret
|
||||||
self.params['redirect_uri'] = unquote(self.request.POST.get('redirect_uri', ''))
|
self.params['redirect_uri'] = unquote(
|
||||||
|
self.request.POST.get('redirect_uri', '').split('?', 1)[0])
|
||||||
self.params['grant_type'] = self.request.POST.get('grant_type', '')
|
self.params['grant_type'] = self.request.POST.get('grant_type', '')
|
||||||
self.params['code'] = self.request.POST.get('code', '')
|
self.params['code'] = self.request.POST.get('code', '')
|
||||||
self.params['state'] = self.request.POST.get('state', '')
|
self.params['state'] = self.request.POST.get('state', '')
|
||||||
|
|
|
@ -17,9 +17,7 @@ def cleanup_url_from_query_string(uri):
|
||||||
:type uri: str
|
:type uri: str
|
||||||
:return: cleaned URI without query string
|
:return: cleaned URI without query string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clean_uri = urlsplit(uri)
|
clean_uri = urlsplit(uri)
|
||||||
# noinspection PyProtectedMember
|
|
||||||
clean_uri = urlunsplit(clean_uri._replace(query=''))
|
clean_uri = urlunsplit(clean_uri._replace(query=''))
|
||||||
return clean_uri
|
return clean_uri
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urlparse import parse_qs, urlsplit
|
from urlparse import parse_qs, urlsplit
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib.parse import parse_qs, urlsplit
|
from urllib.parse import parse_qs, urlsplit
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from oidc_provider.models import (
|
from oidc_provider.models import (
|
||||||
Client,
|
Client,
|
||||||
Code,
|
Code,
|
||||||
)
|
Token)
|
||||||
|
|
||||||
|
|
||||||
FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d'
|
FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d'
|
||||||
|
@ -58,6 +61,16 @@ def create_fake_client(response_type, is_public=False):
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def create_fake_token(user, scopes, client):
|
||||||
|
expires_at = timezone.now() + timezone.timedelta(seconds=60)
|
||||||
|
token = Token(user=user, client=client, expires_at=expires_at)
|
||||||
|
token.scope = scopes
|
||||||
|
|
||||||
|
token.save()
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
def is_code_valid(url, user, client):
|
def is_code_valid(url, user, client):
|
||||||
"""
|
"""
|
||||||
Check if the code inside the url is valid. Supporting both query string and fragment.
|
Check if the code inside the url is valid. Supporting both query string and fragment.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from oidc_provider.lib.claims import ScopeClaims
|
from oidc_provider.lib.claims import ScopeClaims
|
||||||
from oidc_provider.tests.app.utils import create_fake_user
|
from oidc_provider.tests.app.utils import create_fake_user, create_fake_client, create_fake_token
|
||||||
|
|
||||||
|
|
||||||
class ClaimsTestCase(TestCase):
|
class ClaimsTestCase(TestCase):
|
||||||
|
@ -8,7 +9,9 @@ class ClaimsTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = create_fake_user()
|
self.user = create_fake_user()
|
||||||
self.scopes = ['openid', 'address', 'email', 'phone', 'profile']
|
self.scopes = ['openid', 'address', 'email', 'phone', 'profile']
|
||||||
self.scopeClaims = ScopeClaims(self.user, self.scopes)
|
self.client = create_fake_client('code')
|
||||||
|
self.token = create_fake_token(self.user, self.scopes, self.client)
|
||||||
|
self.scopeClaims = ScopeClaims(self.token)
|
||||||
|
|
||||||
def test_clean_dic(self):
|
def test_clean_dic(self):
|
||||||
""" assert that _clean_dic function returns a clean dictionnary
|
""" assert that _clean_dic function returns a clean dictionnary
|
||||||
|
|
|
@ -319,6 +319,29 @@ class TokenTestCase(TestCase):
|
||||||
False,
|
False,
|
||||||
msg='Client authentication fails using HTTP Basic Auth.')
|
msg='Client authentication fails using HTTP Basic Auth.')
|
||||||
|
|
||||||
|
def test_client_redirect_url(self):
|
||||||
|
"""
|
||||||
|
Validate that client redirect URIs with query strings match registered
|
||||||
|
URIs, and that unregistered URIs are rejected.
|
||||||
|
"""
|
||||||
|
SIGKEYS = self._get_keys()
|
||||||
|
code = self._create_code()
|
||||||
|
post_data = self._auth_code_post_data(code=code.code)
|
||||||
|
|
||||||
|
# Unregistered URI
|
||||||
|
post_data['redirect_uri'] = 'http://invalid.example.org'
|
||||||
|
|
||||||
|
response = self._post_request(post_data)
|
||||||
|
|
||||||
|
self.assertIn('invalid_client', response.content.decode('utf-8')),
|
||||||
|
|
||||||
|
# Registered URI contained a query string
|
||||||
|
post_data['redirect_uri'] = 'http://example.com/?client=OidcClient'
|
||||||
|
|
||||||
|
response = self._post_request(post_data)
|
||||||
|
|
||||||
|
self.assertNotIn('invalid_client', response.content.decode('utf-8')),
|
||||||
|
|
||||||
def test_access_token_contains_nonce(self):
|
def test_access_token_contains_nonce(self):
|
||||||
"""
|
"""
|
||||||
If present in the Authentication Request, Authorization Servers MUST
|
If present in the Authentication Request, Authorization Servers MUST
|
||||||
|
|
|
@ -162,11 +162,11 @@ def userinfo(request, *args, **kwargs):
|
||||||
'sub': token.id_token.get('sub'),
|
'sub': token.id_token.get('sub'),
|
||||||
}
|
}
|
||||||
|
|
||||||
standard_claims = StandardScopeClaims(token.user, token.scope)
|
standard_claims = StandardScopeClaims(token)
|
||||||
dic.update(standard_claims.create_response_dic())
|
dic.update(standard_claims.create_response_dic())
|
||||||
|
|
||||||
if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
|
if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
|
||||||
extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token.user, token.scope)
|
extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token)
|
||||||
dic.update(extra_claims.create_response_dic())
|
dic.update(extra_claims.create_response_dic())
|
||||||
|
|
||||||
response = JsonResponse(dic, status=200)
|
response = JsonResponse(dic, status=200)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -7,7 +7,7 @@ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-oidc-provider',
|
name='django-oidc-provider',
|
||||||
version='0.4.1',
|
version='0.4.2',
|
||||||
packages=[
|
packages=[
|
||||||
'oidc_provider', 'oidc_provider/lib', 'oidc_provider/lib/endpoints',
|
'oidc_provider', 'oidc_provider/lib', 'oidc_provider/lib/endpoints',
|
||||||
'oidc_provider/lib/utils', 'oidc_provider/tests', 'oidc_provider/tests/app',
|
'oidc_provider/lib/utils', 'oidc_provider/tests', 'oidc_provider/tests/app',
|
||||||
|
|
Loading…
Reference in a new issue