0001-idp_oidc-make-access_token-validity-depends-on-expir.patch
src/authentic2_idp_oidc/migrations/0014_auto_20201126_1812.py | ||
---|---|---|
1 |
# Generated by Django 2.2.17 on 2020-11-26 17:12 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('authentic2_idp_oidc', '0013_auto_20200630_1007'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.AlterField( |
|
14 |
model_name='oidcaccesstoken', |
|
15 |
name='expired', |
|
16 |
field=models.DateTimeField(null=True, verbose_name='expire'), |
|
17 |
), |
|
18 |
] |
src/authentic2_idp_oidc/models.py | ||
---|---|---|
24 | 24 |
from django.utils.translation import ugettext_lazy as _ |
25 | 25 |
from django.conf import settings |
26 | 26 |
from django.utils import six |
27 |
from django.utils.functional import cached_property |
|
27 | 28 |
from django.utils.timezone import now |
28 | 29 |
from django.utils.six.moves.urllib import parse as urlparse |
29 | 30 |
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation |
... | ... | |
407 | 408 |
verbose_name=_('created'), |
408 | 409 |
auto_now_add=True) |
409 | 410 |
expired = models.DateTimeField( |
410 |
verbose_name=_('expire')) |
|
411 |
verbose_name=_('expire'), |
|
412 |
null=True) |
|
411 | 413 | |
412 | 414 |
objects = managers.OIDCExpiredManager() |
413 | 415 | |
... | ... | |
415 | 417 |
return utils.scope_set(self.scopes) |
416 | 418 | |
417 | 419 |
def is_valid(self): |
418 |
if self.expired < now(): |
|
420 |
if self.expired is not None and self.expired < now():
|
|
419 | 421 |
return False |
420 | 422 |
if not self.session_key: |
421 | 423 |
return True |
422 |
if not self.session: |
|
424 |
session = get_session(self.session_key) |
|
425 |
if session is None: |
|
423 | 426 |
return False |
424 |
if self.session.get('_auth_user_id') != str(self.user_id):
|
|
427 |
if session.get('_auth_user_id') != str(self.user_id): |
|
425 | 428 |
return False |
426 | 429 |
return True |
427 | 430 |
src/authentic2_idp_oidc/views.py | ||
---|---|---|
172 | 172 |
return client.idtoken_duration or datetime.timedelta(seconds=app_settings.IDTOKEN_DURATION) |
173 | 173 | |
174 | 174 | |
175 |
def access_token_duration(client): |
|
176 |
return client.access_token_duration or datetime.timedelta(seconds=app_settings.IDTOKEN_DURATION) |
|
177 | ||
178 | ||
179 | 175 |
def allowed_scopes(client): |
180 | 176 |
return client.scope_set() or app_settings.SCOPES or ['openid', 'email', 'profile'] |
181 | 177 | |
... | ... | |
405 | 401 |
response = redirect(request, redirect_uri, params=params, resolve=False) |
406 | 402 |
else: |
407 | 403 |
need_access_token = 'token' in response_type.split() |
408 |
expires_in = access_token_duration(client) |
|
409 | 404 |
if need_access_token: |
405 |
if client.access_token_duration is None: |
|
406 |
expires_in = datetime.timedelta(request.session.get_expiry_age()) |
|
407 |
expired = None |
|
408 |
else: |
|
409 |
expires_in = client.access_token_duration |
|
410 |
expired = iat + client.access_token_duration |
|
410 | 411 |
access_token = models.OIDCAccessToken.objects.create( |
411 | 412 |
client=client, |
412 | 413 |
user=request.user, |
413 | 414 |
scopes=u' '.join(scopes), |
414 | 415 |
session_key=request.session.session_key, |
415 |
expired=iat + expires_in)
|
|
416 |
expired=expired)
|
|
416 | 417 |
acr = '0' |
417 | 418 |
if nonce is not None and last_auth.get('nonce') == nonce: |
418 | 419 |
acr = '1' |
... | ... | |
601 | 602 |
exponential_backoff.success(*backoff_keys) |
602 | 603 |
iat = now() # iat = issued at |
603 | 604 |
# make access_token |
604 |
expires_in = access_token_duration(client) |
|
605 |
if client.access_token_duration is None: |
|
606 |
expires_in = datetime.timedelta(seconds=app_settings.ACCESS_TOKEN_DURATION) |
|
607 |
else: |
|
608 |
expires_in = client.access_token_duration |
|
605 | 609 |
access_token = models.OIDCAccessToken.objects.create( |
606 | 610 |
client=client, |
607 | 611 |
user=user, |
... | ... | |
648 | 652 |
redirect_uri = request.POST.get('redirect_uri') |
649 | 653 |
if oidc_code.redirect_uri != redirect_uri: |
650 | 654 |
raise InvalidRequest(_('Parameter "redirect_uri" does not match the code.'), client=client) |
651 |
expires_in = access_token_duration(client) |
|
655 |
if client.access_token_duration is None: |
|
656 |
expires_in = datetime.timedelta(oidc_code.session.get_expiry_age()) |
|
657 |
expired = None |
|
658 |
else: |
|
659 |
expires_in = client.access_token_duration |
|
660 |
expired = oidc_code.created + expires_in |
|
652 | 661 |
access_token = models.OIDCAccessToken.objects.create( |
653 | 662 |
client=client, |
654 | 663 |
user=oidc_code.user, |
655 | 664 |
scopes=oidc_code.scopes, |
656 | 665 |
session_key=oidc_code.session_key, |
657 |
expired=oidc_code.created + expires_in)
|
|
666 |
expired=expired)
|
|
658 | 667 |
start = now() |
659 | 668 |
acr = '0' |
660 | 669 |
if (oidc_code.nonce is not None |
tests/test_idp_oidc.py | ||
---|---|---|
17 | 17 |
import base64 |
18 | 18 |
import datetime |
19 | 19 |
import functools |
20 |
from importlib import import_module |
|
20 | 21 |
import json |
21 | 22 | |
22 | 23 |
import pytest |
... | ... | |
1857 | 1858 |
response['WWW-Authenticate'] |
1858 | 1859 |
== 'Bearer error="invalid_token", error_description="Token is expired or user is disconnected"' |
1859 | 1860 |
) |
1861 | ||
1862 | ||
1863 |
@pytest.fixture |
|
1864 |
def session(settings, db, simple_user): |
|
1865 |
engine = import_module(settings.SESSION_ENGINE) |
|
1866 |
session = engine.SessionStore() |
|
1867 |
session['_auth_user_id'] = str(simple_user.id) |
|
1868 |
session.create() |
|
1869 |
return session |
|
1870 | ||
1871 | ||
1872 |
def test_access_token_is_valid_session(simple_oidc_client, simple_user, session): |
|
1873 |
token = OIDCAccessToken.objects.create( |
|
1874 |
client=simple_oidc_client, |
|
1875 |
user=simple_user, |
|
1876 |
scopes='openid', |
|
1877 |
session_key=session.session_key) |
|
1878 | ||
1879 |
assert token.is_valid() |
|
1880 |
session.flush() |
|
1881 |
assert not token.is_valid() |
|
1882 | ||
1883 | ||
1884 |
def test_access_token_is_valid_expired(simple_oidc_client, simple_user, freezer): |
|
1885 |
start = now() |
|
1886 |
expired = start + datetime.timedelta(seconds=30) |
|
1887 | ||
1888 |
token = OIDCAccessToken.objects.create( |
|
1889 |
client=simple_oidc_client, |
|
1890 |
user=simple_user, |
|
1891 |
scopes='openid', |
|
1892 |
expired=expired) |
|
1893 | ||
1894 |
assert token.is_valid() |
|
1895 |
freezer.move_to(expired) |
|
1896 |
assert token.is_valid() |
|
1897 |
freezer.move_to(expired + datetime.timedelta(seconds=1)) |
|
1898 |
assert not token.is_valid() |
|
1899 | ||
1900 | ||
1901 |
def test_access_token_is_valid_session_and_expired(simple_oidc_client, |
|
1902 |
simple_user, |
|
1903 |
session, freezer): |
|
1904 |
start = now() |
|
1905 |
expired = start + datetime.timedelta(seconds=30) |
|
1906 | ||
1907 |
token = OIDCAccessToken.objects.create( |
|
1908 |
client=simple_oidc_client, |
|
1909 |
user=simple_user, |
|
1910 |
scopes='openid', |
|
1911 |
session_key=session.session_key, |
|
1912 |
expired=expired) |
|
1913 | ||
1914 |
assert token.is_valid() |
|
1915 |
freezer.move_to(expired) |
|
1916 |
assert token.is_valid() |
|
1917 |
freezer.move_to(expired + datetime.timedelta(seconds=1)) |
|
1918 |
assert not token.is_valid() |
|
1919 |
freezer.move_to(start) |
|
1920 |
assert token.is_valid() |
|
1921 |
session.flush() |
|
1922 |
assert not token.is_valid() |
|
1860 |
- |