Merge branch 'v0.5.x' into develop
This commit is contained in:
commit
101130e47a
12 changed files with 51 additions and 47 deletions
|
@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
|
||||||
- Signals when user accept/decline the authorization page.
|
- Signals when user accept/decline the authorization page.
|
||||||
- `OIDC_AFTER_END_SESSION_HOOK` setting for additional business logic
|
- `OIDC_AFTER_END_SESSION_HOOK` setting for additional business logic
|
||||||
- Feature granttype password
|
- Feature granttype password
|
||||||
|
- require_consent and reuse_consent are added to Client model.
|
||||||
|
|
||||||
|
##### Changed
|
||||||
|
- OIDC_SKIP_CONSENT_ALWAYS and OIDC_SKIP_CONSENT_ENABLE are removed from settings.
|
||||||
|
|
||||||
##### Fixed
|
##### Fixed
|
||||||
- Timestamps with unixtime (instead of django timezone).
|
- Timestamps with unixtime (instead of django timezone).
|
||||||
|
|
|
@ -60,5 +60,6 @@ The RP application obtains a new access token by sending a POST request to the `
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
"http://localhost:8000/token/" \
|
"http://localhost:8000/token/" \
|
||||||
-d "client_id=651462" \
|
-d "client_id=651462" \
|
||||||
|
-d "client_secret=37b1c4ff826f8d78bd45e25bad75a2c0" \
|
||||||
-d "grant_type=refresh_token" \
|
-d "grant_type=refresh_token" \
|
||||||
-d "refresh_token=0bac2d80d75d46658b0b31d3778039bb"
|
-d "refresh_token=0bac2d80d75d46658b0b31d3778039bb"
|
||||||
|
|
|
@ -22,6 +22,8 @@ Properties
|
||||||
* ``jwt_alg``: Clients can choose wich algorithm will be used to sign id_tokens. Values are ``HS256`` and ``RS256``.
|
* ``jwt_alg``: Clients can choose wich algorithm will be used to sign id_tokens. Values are ``HS256`` and ``RS256``.
|
||||||
* ``date_created``: Date automatically added when created.
|
* ``date_created``: Date automatically added when created.
|
||||||
* ``redirect_uris``: List of redirect URIs.
|
* ``redirect_uris``: List of redirect URIs.
|
||||||
|
* ``require_consent``: If checked, the Server will never ask for consent (only applies to confidential clients).
|
||||||
|
* ``reuse_consent``: If enabled, the Server will save the user consent given to a specific client, so that user won't be prompted for the same authorization multiple times.
|
||||||
|
|
||||||
Optional information:
|
Optional information:
|
||||||
|
|
||||||
|
|
|
@ -117,20 +117,6 @@ OPTIONAL. Supply a fixed string to use as browser-state key for unauthenticated
|
||||||
|
|
||||||
Default is a string generated at startup.
|
Default is a string generated at startup.
|
||||||
|
|
||||||
OIDC_SKIP_CONSENT_ALWAYS
|
|
||||||
========================
|
|
||||||
|
|
||||||
OPTIONAL. ``bool``. If enabled, the Server will NEVER ask the user for consent if the client is confidential.
|
|
||||||
|
|
||||||
Default is ``False``.
|
|
||||||
|
|
||||||
OIDC_SKIP_CONSENT_ENABLE
|
|
||||||
========================
|
|
||||||
|
|
||||||
OPTIONAL. ``bool``. If enabled, the Server will save the user consent given to a specific client, so that user won't be prompted for the same authorization multiple times.
|
|
||||||
|
|
||||||
Default is ``True``.
|
|
||||||
|
|
||||||
OIDC_SKIP_CONSENT_EXPIRE
|
OIDC_SKIP_CONSENT_EXPIRE
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" style="margin-top: 150px;">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class ClientAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
[_(u''), {
|
[_(u''), {
|
||||||
'fields': ('name', 'client_type', 'response_type','_redirect_uris', 'jwt_alg'),
|
'fields': ('name', 'client_type', 'response_type','_redirect_uris', 'jwt_alg', 'require_consent', 'reuse_consent'),
|
||||||
}],
|
}],
|
||||||
[_(u'Credentials'), {
|
[_(u'Credentials'), {
|
||||||
'fields': ('client_id', 'client_secret'),
|
'fields': ('client_id', 'client_secret'),
|
||||||
|
|
25
oidc_provider/migrations/0022_auto_20170331_1626.py
Normal file
25
oidc_provider/migrations/0022_auto_20170331_1626.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.6 on 2017-03-31 16:26
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('oidc_provider', '0021_refresh_token_not_unique'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='client',
|
||||||
|
name='require_consent',
|
||||||
|
field=models.BooleanField(default=True, help_text='If disabled, the Server will NEVER ask the user for consent.', verbose_name='Require Consent?'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='client',
|
||||||
|
name='reuse_consent',
|
||||||
|
field=models.BooleanField(default=True, help_text="If enabled, the Server will save the user consent given to a specific client, so that user won't be prompted for the same authorization multiple times.", verbose_name='Reuse Consent?'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -43,6 +43,8 @@ class Client(models.Model):
|
||||||
terms_url = models.CharField(max_length=255, blank=True, default='', verbose_name=_(u'Terms URL'), help_text=_(u'External reference to the privacy policy of the client.'))
|
terms_url = models.CharField(max_length=255, blank=True, default='', verbose_name=_(u'Terms URL'), help_text=_(u'External reference to the privacy policy of the client.'))
|
||||||
contact_email = models.CharField(max_length=255, blank=True, default='', verbose_name=_(u'Contact Email'))
|
contact_email = models.CharField(max_length=255, blank=True, default='', verbose_name=_(u'Contact Email'))
|
||||||
logo = models.FileField(blank=True, default='', upload_to='oidc_provider/clients', verbose_name=_(u'Logo Image'))
|
logo = models.FileField(blank=True, default='', upload_to='oidc_provider/clients', verbose_name=_(u'Logo Image'))
|
||||||
|
reuse_consent = models.BooleanField(default=True, verbose_name=_('Reuse Consent?'), help_text=_('If enabled, the Server will save the user consent given to a specific client, so that user won\'t be prompted for the same authorization multiple times.'))
|
||||||
|
require_consent = models.BooleanField(default=True, verbose_name=_('Require Consent?'), help_text=_('If disabled, the Server will NEVER ask the user for consent.'))
|
||||||
|
|
||||||
_redirect_uris = models.TextField(default='', verbose_name=_(u'Redirect URIs'), help_text=_(u'Enter each URI on a new line.'))
|
_redirect_uris = models.TextField(default='', verbose_name=_(u'Redirect URIs'), help_text=_(u'Enter each URI on a new line.'))
|
||||||
def redirect_uris():
|
def redirect_uris():
|
||||||
|
|
|
@ -91,23 +91,6 @@ class DefaultSettings(object):
|
||||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
|
||||||
return self._unauthenticated_session_management_key
|
return self._unauthenticated_session_management_key
|
||||||
|
|
||||||
@property
|
|
||||||
def OIDC_SKIP_CONSENT_ALWAYS(self):
|
|
||||||
"""
|
|
||||||
OPTIONAL. If enabled, the Server will NEVER ask the user for consent.
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def OIDC_SKIP_CONSENT_ENABLE(self):
|
|
||||||
"""
|
|
||||||
OPTIONAL. If enabled, the Server will save the user consent
|
|
||||||
given to a specific client, so that user won't be prompted for
|
|
||||||
the same authorization multiple times.
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def OIDC_SKIP_CONSENT_EXPIRE(self):
|
def OIDC_SKIP_CONSENT_EXPIRE(self):
|
||||||
"""
|
"""
|
||||||
OPTIONAL. User consent expiration after been granted.
|
OPTIONAL. User consent expiration after been granted.
|
||||||
|
|
|
@ -36,7 +36,7 @@ def create_fake_user():
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def create_fake_client(response_type, is_public=False):
|
def create_fake_client(response_type, is_public=False, require_consent=True):
|
||||||
"""
|
"""
|
||||||
Create a test client, response_type argument MUST be:
|
Create a test client, response_type argument MUST be:
|
||||||
'code', 'id_token' or 'id_token token'.
|
'code', 'id_token' or 'id_token token'.
|
||||||
|
@ -53,6 +53,7 @@ def create_fake_client(response_type, is_public=False):
|
||||||
client.client_secret = str(random.randint(1, 999999)).zfill(6)
|
client.client_secret = str(random.randint(1, 999999)).zfill(6)
|
||||||
client.response_type = response_type
|
client.response_type = response_type
|
||||||
client.redirect_uris = ['http://example.com/']
|
client.redirect_uris = ['http://example.com/']
|
||||||
|
client.require_consent = require_consent
|
||||||
|
|
||||||
client.save()
|
client.save()
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,9 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.user = create_fake_user()
|
self.user = create_fake_user()
|
||||||
self.client = create_fake_client(response_type='code')
|
self.client = create_fake_client(response_type='code')
|
||||||
|
self.client_with_no_consent = create_fake_client(response_type='code', require_consent=False)
|
||||||
self.client_public = create_fake_client(response_type='code', is_public=True)
|
self.client_public = create_fake_client(response_type='code', is_public=True)
|
||||||
|
self.client_public_with_no_consent = create_fake_client(response_type='code', is_public=True, require_consent=False)
|
||||||
self.state = uuid.uuid4().hex
|
self.state = uuid.uuid4().hex
|
||||||
self.nonce = uuid.uuid4().hex
|
self.nonce = uuid.uuid4().hex
|
||||||
|
|
||||||
|
@ -214,8 +216,8 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
|
||||||
authorization multiple times, the server skip it.
|
authorization multiple times, the server skip it.
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
'client_id': self.client.client_id,
|
'client_id': self.client_with_no_consent.client_id,
|
||||||
'redirect_uri': self.client.default_redirect_uri,
|
'redirect_uri': self.client_with_no_consent.default_redirect_uri,
|
||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'scope': 'openid email',
|
'scope': 'openid email',
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
|
@ -227,16 +229,15 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
|
||||||
# Simulate that the user is logged.
|
# Simulate that the user is logged.
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
|
|
||||||
with self.settings(OIDC_SKIP_CONSENT_ALWAYS=True):
|
response = self._auth_request('post', data, is_user_authenticated=True)
|
||||||
response = self._auth_request('post', data, is_user_authenticated=True)
|
|
||||||
|
|
||||||
self.assertIn('code', response['Location'], msg='Code is missing in the returned url.')
|
self.assertIn('code', response['Location'], msg='Code is missing in the returned url.')
|
||||||
|
|
||||||
response = self._auth_request('post', data, is_user_authenticated=True)
|
response = self._auth_request('post', data, is_user_authenticated=True)
|
||||||
|
|
||||||
is_code_ok = is_code_valid(url=response['Location'],
|
is_code_ok = is_code_valid(url=response['Location'],
|
||||||
user=self.user,
|
user=self.user,
|
||||||
client=self.client)
|
client=self.client_with_no_consent)
|
||||||
self.assertEqual(is_code_ok, True, msg='Code returned is invalid.')
|
self.assertEqual(is_code_ok, True, msg='Code returned is invalid.')
|
||||||
|
|
||||||
del data['allow']
|
del data['allow']
|
||||||
|
@ -244,7 +245,7 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
|
||||||
|
|
||||||
is_code_ok = is_code_valid(url=response['Location'],
|
is_code_ok = is_code_valid(url=response['Location'],
|
||||||
user=self.user,
|
user=self.user,
|
||||||
client=self.client)
|
client=self.client_with_no_consent)
|
||||||
self.assertEqual(is_code_ok, True, msg='Code returned is invalid or missing.')
|
self.assertEqual(is_code_ok, True, msg='Code returned is invalid or missing.')
|
||||||
|
|
||||||
def test_response_uri_is_properly_constructed(self):
|
def test_response_uri_is_properly_constructed(self):
|
||||||
|
@ -266,15 +267,14 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
|
||||||
It's recommended not auto-approving requests for non-confidential clients.
|
It's recommended not auto-approving requests for non-confidential clients.
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
'client_id': self.client_public.client_id,
|
'client_id': self.client_public_with_no_consent.client_id,
|
||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'redirect_uri': self.client_public.default_redirect_uri,
|
'redirect_uri': self.client_public_with_no_consent.default_redirect_uri,
|
||||||
'scope': 'openid email',
|
'scope': 'openid email',
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
}
|
}
|
||||||
|
|
||||||
with self.settings(OIDC_SKIP_CONSENT_ALWAYS=True):
|
response = self._auth_request('get', data, is_user_authenticated=True)
|
||||||
response = self._auth_request('get', data, is_user_authenticated=True)
|
|
||||||
|
|
||||||
self.assertIn('Request for Permission', response.content.decode('utf-8'))
|
self.assertIn('Request for Permission', response.content.decode('utf-8'))
|
||||||
|
|
||||||
|
|
|
@ -67,11 +67,11 @@ class AuthorizeView(View):
|
||||||
if hook_resp:
|
if hook_resp:
|
||||||
return hook_resp
|
return hook_resp
|
||||||
|
|
||||||
if settings.get('OIDC_SKIP_CONSENT_ALWAYS') and not (authorize.client.client_type == 'public') \
|
if not authorize.client.require_consent and not (authorize.client.client_type == 'public') \
|
||||||
and not (authorize.params['prompt'] == 'consent'):
|
and not (authorize.params['prompt'] == 'consent'):
|
||||||
return redirect(authorize.create_response_uri())
|
return redirect(authorize.create_response_uri())
|
||||||
|
|
||||||
if settings.get('OIDC_SKIP_CONSENT_ENABLE'):
|
if authorize.client.reuse_consent:
|
||||||
# Check if user previously give consent.
|
# Check if user previously give consent.
|
||||||
if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public') \
|
if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public') \
|
||||||
and not (authorize.params['prompt'] == 'consent'):
|
and not (authorize.params['prompt'] == 'consent'):
|
||||||
|
|
Loading…
Reference in a new issue