commit
68ce127e31
10 changed files with 85 additions and 23 deletions
|
@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### [Unreleased]
|
### [Unreleased]
|
||||||
|
|
||||||
|
##### Added
|
||||||
|
- Choose type of client on creation.
|
||||||
|
|
||||||
|
### [0.3.1] - 2016-03-09
|
||||||
|
|
||||||
|
##### Fixed
|
||||||
|
- response_type was not being validated (OpenID request).
|
||||||
|
|
||||||
### [0.3.0] - 2016-02-23
|
### [0.3.0] - 2016-02-23
|
||||||
|
|
||||||
##### Added
|
##### Added
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Juan Ignacio Fiorentino
|
Copyright (c) 2014-2016 Juan Ignacio Fiorentino
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -30,10 +30,19 @@ class ClientForm(ModelForm):
|
||||||
|
|
||||||
def clean_client_secret(self):
|
def clean_client_secret(self):
|
||||||
instance = getattr(self, 'instance', None)
|
instance = getattr(self, 'instance', None)
|
||||||
|
|
||||||
|
secret = ''
|
||||||
|
|
||||||
if instance and instance.pk:
|
if instance and instance.pk:
|
||||||
return instance.client_secret
|
if (self.cleaned_data['client_type'] == 'confidential') and not instance.client_secret:
|
||||||
|
secret = md5(uuid4().hex.encode()).hexdigest()
|
||||||
|
elif (self.cleaned_data['client_type'] == 'confidential') and instance.client_secret:
|
||||||
|
secret = instance.client_secret
|
||||||
else:
|
else:
|
||||||
return md5(uuid4().hex.encode()).hexdigest()
|
if (instance.client_type == 'confidential'):
|
||||||
|
secret = md5(uuid4().hex.encode()).hexdigest()
|
||||||
|
|
||||||
|
return secret
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Client)
|
@admin.register(Client)
|
||||||
|
|
|
@ -60,15 +60,15 @@ class AuthorizeEndpoint(object):
|
||||||
try:
|
try:
|
||||||
self.client = Client.objects.get(client_id=self.params.client_id)
|
self.client = Client.objects.get(client_id=self.params.client_id)
|
||||||
except Client.DoesNotExist:
|
except Client.DoesNotExist:
|
||||||
logger.error('[Authorize] Invalid client identifier: %s', self.params.client_id)
|
logger.debug('[Authorize] Invalid client identifier: %s', self.params.client_id)
|
||||||
raise ClientIdError()
|
raise ClientIdError()
|
||||||
|
|
||||||
if self.is_authentication and not self.params.redirect_uri:
|
if self.is_authentication and not self.params.redirect_uri:
|
||||||
logger.error('[Authorize] Missing redirect uri.')
|
logger.debug('[Authorize] Missing redirect uri.')
|
||||||
raise RedirectUriError()
|
raise RedirectUriError()
|
||||||
|
|
||||||
if not self.grant_type:
|
if not self.grant_type:
|
||||||
logger.error('[Authorize] Invalid response type: %s', self.params.response_type)
|
logger.debug('[Authorize] Invalid response type: %s', self.params.response_type)
|
||||||
raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type',
|
raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type',
|
||||||
self.grant_type)
|
self.grant_type)
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ class AuthorizeEndpoint(object):
|
||||||
clean_redirect_uri = urlsplit(self.params.redirect_uri)
|
clean_redirect_uri = urlsplit(self.params.redirect_uri)
|
||||||
clean_redirect_uri = urlunsplit(clean_redirect_uri._replace(query=''))
|
clean_redirect_uri = urlunsplit(clean_redirect_uri._replace(query=''))
|
||||||
if not (clean_redirect_uri in self.client.redirect_uris):
|
if not (clean_redirect_uri in self.client.redirect_uris):
|
||||||
logger.error('[Authorize] Invalid redirect uri: %s', self.params.redirect_uri)
|
logger.debug('[Authorize] Invalid redirect uri: %s', self.params.redirect_uri)
|
||||||
raise RedirectUriError()
|
raise RedirectUriError()
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class AuthorizeEndpoint(object):
|
||||||
query_fragment['state'] = self.params.state if self.params.state else ''
|
query_fragment['state'] = self.params.state if self.params.state else ''
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('[Authorize] Error when trying to create response uri: %s', error)
|
logger.debug('[Authorize] Error when trying to create response uri: %s', error)
|
||||||
raise AuthorizeError(
|
raise AuthorizeError(
|
||||||
self.params.redirect_uri,
|
self.params.redirect_uri,
|
||||||
'server_error',
|
'server_error',
|
||||||
|
|
|
@ -65,34 +65,34 @@ class TokenEndpoint(object):
|
||||||
try:
|
try:
|
||||||
self.client = Client.objects.get(client_id=self.params.client_id)
|
self.client = Client.objects.get(client_id=self.params.client_id)
|
||||||
except Client.DoesNotExist:
|
except Client.DoesNotExist:
|
||||||
logger.error('[Token] Client does not exist: %s', self.params.client_id)
|
logger.debug('[Token] Client does not exist: %s', self.params.client_id)
|
||||||
raise TokenError('invalid_client')
|
raise TokenError('invalid_client')
|
||||||
|
|
||||||
if not (self.client.client_secret == self.params.client_secret):
|
if not (self.client.client_secret == self.params.client_secret):
|
||||||
logger.error('[Token] Invalid client secret: client %s do not have secret %s',
|
logger.debug('[Token] Invalid client secret: client %s do not have secret %s',
|
||||||
self.client.client_id, self.client.client_secret)
|
self.client.client_id, self.client.client_secret)
|
||||||
raise TokenError('invalid_client')
|
raise TokenError('invalid_client')
|
||||||
|
|
||||||
if self.params.grant_type == 'authorization_code':
|
if self.params.grant_type == 'authorization_code':
|
||||||
if not (self.params.redirect_uri in self.client.redirect_uris):
|
if not (self.params.redirect_uri in self.client.redirect_uris):
|
||||||
logger.error('[Token] Invalid redirect uri: %s', self.params.redirect_uri)
|
logger.debug('[Token] Invalid redirect uri: %s', self.params.redirect_uri)
|
||||||
raise TokenError('invalid_client')
|
raise TokenError('invalid_client')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.code = Code.objects.get(code=self.params.code)
|
self.code = Code.objects.get(code=self.params.code)
|
||||||
except Code.DoesNotExist:
|
except Code.DoesNotExist:
|
||||||
logger.error('[Token] Code does not exist: %s', self.params.code)
|
logger.debug('[Token] Code does not exist: %s', self.params.code)
|
||||||
raise TokenError('invalid_grant')
|
raise TokenError('invalid_grant')
|
||||||
|
|
||||||
if not (self.code.client == self.client) \
|
if not (self.code.client == self.client) \
|
||||||
or self.code.has_expired():
|
or self.code.has_expired():
|
||||||
logger.error('[Token] Invalid code: invalid client or code has expired',
|
logger.debug('[Token] Invalid code: invalid client or code has expired',
|
||||||
self.params.redirect_uri)
|
self.params.redirect_uri)
|
||||||
raise TokenError('invalid_grant')
|
raise TokenError('invalid_grant')
|
||||||
|
|
||||||
elif self.params.grant_type == 'refresh_token':
|
elif self.params.grant_type == 'refresh_token':
|
||||||
if not self.params.refresh_token:
|
if not self.params.refresh_token:
|
||||||
logger.error('[Token] Missing refresh token')
|
logger.debug('[Token] Missing refresh token')
|
||||||
raise TokenError('invalid_grant')
|
raise TokenError('invalid_grant')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -100,11 +100,11 @@ class TokenEndpoint(object):
|
||||||
client=self.client)
|
client=self.client)
|
||||||
|
|
||||||
except Token.DoesNotExist:
|
except Token.DoesNotExist:
|
||||||
logger.error('[Token] Refresh token does not exist: %s', self.params.refresh_token)
|
logger.debug('[Token] Refresh token does not exist: %s', self.params.refresh_token)
|
||||||
raise TokenError('invalid_grant')
|
raise TokenError('invalid_grant')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error('[Token] Invalid grant type: %s', self.params.grant_type)
|
logger.debug('[Token] Invalid grant type: %s', self.params.grant_type)
|
||||||
raise TokenError('unsupported_grant_type')
|
raise TokenError('unsupported_grant_type')
|
||||||
|
|
||||||
def create_response_dic(self):
|
def create_response_dic(self):
|
||||||
|
|
|
@ -42,15 +42,15 @@ def protected_resource_view(scopes=[]):
|
||||||
try:
|
try:
|
||||||
kwargs['token'] = Token.objects.get(access_token=access_token)
|
kwargs['token'] = Token.objects.get(access_token=access_token)
|
||||||
except Token.DoesNotExist:
|
except Token.DoesNotExist:
|
||||||
logger.error('[UserInfo] Token does not exist: %s', access_token)
|
logger.debug('[UserInfo] Token does not exist: %s', access_token)
|
||||||
raise BearerTokenError('invalid_token')
|
raise BearerTokenError('invalid_token')
|
||||||
|
|
||||||
if kwargs['token'].has_expired():
|
if kwargs['token'].has_expired():
|
||||||
logger.error('[UserInfo] Token has expired: %s', access_token)
|
logger.debug('[UserInfo] Token has expired: %s', access_token)
|
||||||
raise BearerTokenError('invalid_token')
|
raise BearerTokenError('invalid_token')
|
||||||
|
|
||||||
if not set(scopes).issubset(set(kwargs['token'].scope)):
|
if not set(scopes).issubset(set(kwargs['token'].scope)):
|
||||||
logger.error('[UserInfo] Missing openid scope.')
|
logger.debug('[UserInfo] Missing openid scope.')
|
||||||
raise BearerTokenError('insufficient_scope')
|
raise BearerTokenError('insufficient_scope')
|
||||||
except (BearerTokenError) as error:
|
except (BearerTokenError) as error:
|
||||||
response = HttpResponse(status=error.status)
|
response = HttpResponse(status=error.status)
|
||||||
|
|
20
oidc_provider/migrations/0011_client_client_type.py
Normal file
20
oidc_provider/migrations/0011_client_client_type.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-04-04 19:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('oidc_provider', '0010_code_is_authentication'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='client',
|
||||||
|
name='client_type',
|
||||||
|
field=models.CharField(choices=[(b'confidential', b'Confidential'), (b'public', b'Public')], default=b'confidential', help_text='<b>Confidential</b> clients are capable of maintaining the confidentiality of their credentials. <b>Public</b> clients are incapable.', max_length=30),
|
||||||
|
),
|
||||||
|
]
|
20
oidc_provider/migrations/0012_auto_20160405_2041.py
Normal file
20
oidc_provider/migrations/0012_auto_20160405_2041.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-04-05 20:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('oidc_provider', '0011_client_client_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='client',
|
||||||
|
name='client_secret',
|
||||||
|
field=models.CharField(blank=True, default=b'', max_length=255),
|
||||||
|
),
|
||||||
|
]
|
|
@ -10,6 +10,11 @@ from django.conf import settings
|
||||||
|
|
||||||
class Client(models.Model):
|
class Client(models.Model):
|
||||||
|
|
||||||
|
CLIENT_TYPE_CHOICES = [
|
||||||
|
('confidential', 'Confidential'),
|
||||||
|
('public', 'Public'),
|
||||||
|
]
|
||||||
|
|
||||||
RESPONSE_TYPE_CHOICES = [
|
RESPONSE_TYPE_CHOICES = [
|
||||||
('code', 'code (Authorization Code Flow)'),
|
('code', 'code (Authorization Code Flow)'),
|
||||||
('id_token', 'id_token (Implicit Flow)'),
|
('id_token', 'id_token (Implicit Flow)'),
|
||||||
|
@ -17,10 +22,10 @@ class Client(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
name = models.CharField(max_length=100, default='')
|
name = models.CharField(max_length=100, default='')
|
||||||
|
client_type = models.CharField(max_length=30, choices=CLIENT_TYPE_CHOICES, default='confidential', help_text=_(u'<b>Confidential</b> clients are capable of maintaining the confidentiality of their credentials. <b>Public</b> clients are incapable.'))
|
||||||
client_id = models.CharField(max_length=255, unique=True)
|
client_id = models.CharField(max_length=255, unique=True)
|
||||||
client_secret = models.CharField(max_length=255, unique=True)
|
client_secret = models.CharField(max_length=255, blank=True, default='')
|
||||||
response_type = models.CharField(max_length=30,
|
response_type = models.CharField(max_length=30, choices=RESPONSE_TYPE_CHOICES)
|
||||||
choices=RESPONSE_TYPE_CHOICES)
|
|
||||||
date_created = models.DateField(auto_now_add=True)
|
date_created = models.DateField(auto_now_add=True)
|
||||||
|
|
||||||
_redirect_uris = models.TextField(default='', verbose_name=_(u'Redirect URI'), help_text=_(u'Enter each URI on a new line.'))
|
_redirect_uris = models.TextField(default='', verbose_name=_(u'Redirect URI'), help_text=_(u'Enter each URI on a new line.'))
|
||||||
|
|
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.3.0',
|
version='0.3.1',
|
||||||
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