Merge pull request #3 from juanifioren/v0.3.x

merge v0.3.x branch
This commit is contained in:
Wojciech Bartosiak 2016-03-01 17:08:04 +00:00
commit 80512c5528
13 changed files with 118 additions and 54 deletions

View file

@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
### [Unreleased]
### [0.3.0] - 2016-02-23
##### Added
- Support OAuth2 requests.
- Decorator for protecting views with OAuth2.
- Setting OIDC_IDTOKEN_PROCESSING_HOOK.
### [0.2.5] - 2016-02-03

View file

@ -1,13 +1,12 @@
Welcome to Django OIDC Provider Documentation!
==============================================
Django OIDC Provider can help you providing out of the box all the endpoints, data and logic needed to add OpenID Connect capabilities to your Django projects.
Django OIDC Provider can help you providing out of the box all the endpoints, data and logic needed to add OpenID Connect capabilities to your Django projects. And as a side effect a fair implementation of OAuth2.0 too.
--------------------------------------------------------------------------------
Before getting started there are some important things that you should know:
* Although OpenID was built on top of OAuth2, this isn't an OAuth2 server. Maybe in a future it will be.
* Despite that implementation MUST support TLS. You can make request without using SSL. There is no control on that.
* This cover **Authorization Code Flow** and **Implicit Flow**, NO support for **Hybrid Flow** at this moment.
* Only support for requesting Claims using Scope Values.
@ -24,6 +23,7 @@ Contents:
sections/serverkeys
sections/templates
sections/claims
sections/oauth2
sections/settings
sections/contribute
..

27
docs/sections/oauth2.rst Normal file
View file

@ -0,0 +1,27 @@
.. _oauth2:
OAuth2 Server
#############
Because OIDC is a layer on top of the OAuth 2.0 protocol, this package gives you a simple but effective OAuth2 server that you can use not only for logging in your users on multiple platforms, also to protect some resources you want to expose.
Protecting Views
================
Here we are going to protect a view with a scope called ``testscope``::
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from oidc_provider.lib.utils.oauth2 import protected_resource_view
@require_http_methods(['GET'])
@protected_resource_view(['testscope'])
def protected_api(request, *args, **kwargs):
dic = {
'protected': 'information',
}
return JsonResponse(dic, status=200)

View file

@ -1,2 +1,2 @@
django==1.9
https://github.com/juanifioren/django-oidc-provider/archive/v0.2.x.zip
https://github.com/juanifioren/django-oidc-provider/archive/v0.3.x.zip

View file

@ -29,11 +29,14 @@ class AuthorizeEndpoint(object):
# Determine which flow to use.
if self.params.response_type in ['code']:
self.grant_type = 'authorization_code'
elif self.params.response_type in ['id_token', 'id_token token']:
elif self.params.response_type in ['id_token', 'id_token token', 'token']:
self.grant_type = 'implicit'
else:
self.grant_type = None
# Determine if it's an OpenID Authentication request (or OAuth2).
self.is_authentication = 'openid' in self.params.scope
def _extract_params(self):
"""
Get all the params used by the Authorization Code Flow
@ -54,36 +57,31 @@ class AuthorizeEndpoint(object):
self.params.nonce = query_dict.get('nonce', '')
def validate_params(self):
if not self.params.redirect_uri:
logger.error('[Authorize] Missing redirect uri.')
raise RedirectUriError()
if not ('openid' in self.params.scope):
logger.error('[Authorize] Missing openid scope.')
raise AuthorizeError(self.params.redirect_uri, 'invalid_scope',
self.grant_type)
# http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters
if self.grant_type == 'implicit' and not self.params.nonce:
raise AuthorizeError(self.params.redirect_uri, 'invalid_request',
self.grant_type)
try:
self.client = Client.objects.get(client_id=self.params.client_id)
except Client.DoesNotExist:
logger.error('[Authorize] Invalid client identifier: %s', self.params.client_id)
raise ClientIdError()
if self.is_authentication and not self.params.redirect_uri:
logger.error('[Authorize] Missing redirect uri.')
raise RedirectUriError()
if not self.grant_type:
logger.error('[Authorize] Invalid response type: %s', self.params.response_type)
raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type',
self.grant_type)
if self.is_authentication and self.grant_type == 'implicit' and not self.params.nonce:
raise AuthorizeError(self.params.redirect_uri, 'invalid_request',
self.grant_type)
clean_redirect_uri = urlsplit(self.params.redirect_uri)
clean_redirect_uri = urlunsplit(clean_redirect_uri._replace(query=''))
if not (clean_redirect_uri in self.client.redirect_uris):
logger.error('[Authorize] Invalid redirect uri: %s', self.params.redirect_uri)
raise RedirectUriError()
if not self.grant_type or not (self.params.response_type == self.client.response_type):
logger.error('[Authorize] Invalid response type: %s', self.params.response_type)
raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type',
self.grant_type)
def create_response_uri(self):
uri = urlsplit(self.params.redirect_uri)
@ -96,7 +94,8 @@ class AuthorizeEndpoint(object):
user=self.request.user,
client=self.client,
scope=self.params.scope,
nonce=self.params.nonce)
nonce=self.params.nonce,
is_authentication=self.is_authentication)
code.save()
@ -104,10 +103,15 @@ class AuthorizeEndpoint(object):
query_params['state'] = self.params.state if self.params.state else ''
elif self.grant_type == 'implicit':
id_token_dic = create_id_token(
user=self.request.user,
aud=self.client.client_id,
nonce=self.params.nonce)
# We don't need id_token if it's an OAuth2 request.
if self.is_authentication:
id_token_dic = create_id_token(
user=self.request.user,
aud=self.client.client_id,
nonce=self.params.nonce)
query_fragment['id_token'] = encode_id_token(id_token_dic)
else:
id_token_dic = {}
token = create_token(
user=self.request.user,
@ -119,12 +123,12 @@ class AuthorizeEndpoint(object):
token.save()
query_fragment['token_type'] = 'bearer'
query_fragment['id_token'] = encode_id_token(id_token_dic)
# TODO: Create setting 'OIDC_TOKEN_EXPIRE'.
query_fragment['expires_in'] = 60 * 10
# Check if response_type is 'id_token token' then
# add access_token to the fragment.
if self.params.response_type == 'id_token token':
# Check if response_type is an OpenID request with value 'id_token token'
# or it's an OAuth2 Implicit Flow request.
if self.params.response_type in ['id_token token', 'token']:
query_fragment['access_token'] = token.access_token
query_fragment['state'] = self.params.state if self.params.state else ''

View file

@ -64,7 +64,6 @@ class TokenEndpoint(object):
def validate_params(self):
try:
self.client = Client.objects.get(client_id=self.params.client_id)
except Client.DoesNotExist:
logger.error('[Token] Client does not exist: %s', self.params.client_id)
raise TokenError('invalid_client')
@ -81,7 +80,6 @@ class TokenEndpoint(object):
try:
self.code = Code.objects.get(code=self.params.code)
except Code.DoesNotExist:
logger.error('[Token] Code does not exist: %s', self.params.code)
raise TokenError('invalid_grant')
@ -114,16 +112,16 @@ class TokenEndpoint(object):
return self.create_code_response_dic()
elif self.params.grant_type == 'refresh_token':
return self.create_refresh_response_dic()
else:
# Should have already been catched by validate_params
raise RuntimeError('Invalid grant type')
def create_code_response_dic(self):
id_token_dic = create_id_token(
user=self.code.user,
aud=self.client.client_id,
nonce=self.code.nonce,
)
if self.code.is_authentication:
id_token_dic = create_id_token(
user=self.code.user,
aud=self.client.client_id,
nonce=self.code.nonce,
)
else:
id_token_dic = {}
token = create_token(
user=self.code.user,
@ -148,11 +146,15 @@ class TokenEndpoint(object):
return dic
def create_refresh_response_dic(self):
id_token_dic = create_id_token(
user=self.token.user,
aud=self.client.client_id,
nonce=None,
)
# If the Token has an id_token it's an Authentication request.
if self.token.id_token:
id_token_dic = create_id_token(
user=self.token.user,
aud=self.client.client_id,
nonce=None,
)
else:
id_token_dic = {}
token = create_token(
user=self.token.user,

View file

@ -55,7 +55,10 @@ def default_idtoken_processing_hook(id_token, user):
:param id_token: dictionary contains values that going to be serialized into `id_token`
:type id_token: dict
:param user: user instance
:param user: user for whom id_token is generated
:type user: User
:return: custom modified dictionary of values for `id_token`
:rtype dict
"""

View file

@ -44,7 +44,7 @@ def create_id_token(user, aud, nonce):
if nonce:
dic['nonce'] = str(nonce)
dic = settings.get('OIDC_IDTOKEN_PROCESSING_HOOK', import_str=True)(dic, user)
dic = settings.get('OIDC_IDTOKEN_PROCESSING_HOOK', import_str=True)(dic, user=user)
return dic
@ -89,7 +89,7 @@ def create_token(user, client, id_token_dic, scope):
return token
def create_code(user, client, scope, nonce):
def create_code(user, client, scope, nonce, is_authentication):
"""
Create and populate a Code object.
@ -103,5 +103,6 @@ def create_code(user, client, scope, nonce):
seconds=settings.get('OIDC_CODE_EXPIRE'))
code.scope = scope
code.nonce = nonce
code.is_authentication = is_authentication
return code

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-02-16 20:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('oidc_provider', '0009_auto_20160202_1945'),
]
operations = [
migrations.AddField(
model_name='code',
name='is_authentication',
field=models.BooleanField(default=False),
),
]

View file

@ -80,6 +80,7 @@ class Code(BaseCodeTokenModel):
code = models.CharField(max_length=255, unique=True)
nonce = models.CharField(max_length=255, blank=True, default='')
is_authentication = models.BooleanField(default=False)
class Meta:
verbose_name = _(u'Authorization Code')

View file

@ -81,7 +81,8 @@ class TokenTestCase(TestCase):
user=self.user,
client=self.client,
scope=['openid', 'email'],
nonce=FAKE_NONCE)
nonce=FAKE_NONCE,
is_authentication=True)
code.save()
return code

View file

@ -57,7 +57,8 @@ class AuthorizeView(View):
# Remove `openid` from scope list
# since we don't need to print it.
authorize.params.scope.remove('openid')
if 'openid' in authorize.params.scope:
authorize.params.scope.remove('openid')
context = {
'client': authorize.client,
@ -117,7 +118,7 @@ class AuthorizeView(View):
class TokenView(View):
def post(self, request, *args, **kwargs):
token = TokenEndpoint(request)
try:

View file

@ -7,7 +7,7 @@ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
setup(
name='django-oidc-provider',
version='0.2.5',
version='0.3.0',
packages=[
'oidc_provider', 'oidc_provider/lib', 'oidc_provider/lib/endpoints',
'oidc_provider/lib/utils', 'oidc_provider/tests', 'oidc_provider/tests/app',