Projet

Général

Profil

0001-auth_oidc-add-support-for-claims-parameter-fixes-265.patch

Benjamin Dauvergne, 28 septembre 2018 12:02

Télécharger (6,7 ko)

Voir les différences:

Subject: [PATCH] auth_oidc: add support for "claims" parameter (fixes #26565)

It allows A2 to signal to OIDC OP that some claims are required,
see :

  https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
 ...oidcprovider_claims_parameter_supported.py | 19 ++++++++++++++
 src/authentic2_auth_oidc/models.py            | 18 +++++++++++++
 src/authentic2_auth_oidc/utils.py             |  4 ++-
 src/authentic2_auth_oidc/views.py             |  3 +++
 tests/test_auth_oidc.py                       | 26 ++++++++++++++++---
 5 files changed, 66 insertions(+), 4 deletions(-)
 create mode 100644 src/authentic2_auth_oidc/migrations/0005_oidcprovider_claims_parameter_supported.py
src/authentic2_auth_oidc/migrations/0005_oidcprovider_claims_parameter_supported.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('authentic2_auth_oidc', '0004_auto_20171017_1522'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='oidcprovider',
16
            name='claims_parameter_supported',
17
            field=models.BooleanField(default=False, verbose_name='Claims parameter supported'),
18
        ),
19
    ]
src/authentic2_auth_oidc/models.py
92 92
        default=ALGO_RSA,
93 93
        choices=ALGO_CHOICES,
94 94
        verbose_name=_('IDToken signature algorithm'))
95
    claims_parameter_supported = models.BooleanField(
96
        verbose_name=_('Claims parameter supported'),
97
        default=False)
95 98

  
96 99
    # ou where new users should be created
97 100
    strategy = models.CharField(
......
138 141
    def __unicode__(self):
139 142
        return self.name
140 143

  
144
    def authorization_claims_parameter(self):
145
        idtoken_claims = {}
146
        userinfo_claims = {}
147
        for claim_mapping in self.claim_mappings.all():
148
            d = idtoken_claims if claim_mapping.idtoken_claim else userinfo_claims
149
            value = {}
150
            if claim_mapping.required:
151
                value['essential'] = True
152
            value = value or None
153
            d[claim_mapping.claim] = value
154
        return {
155
            'id_token': idtoken_claims,
156
            'userinfo': userinfo_claims,
157
        }
158

  
141 159
    def __repr__(self):
142 160
        return '<OIDCProvider %r>' % self.issuer
143 161

  
src/authentic2_auth_oidc/utils.py
258 258
    else:
259 259
        raise ValueError(_('no common algorithm found for signing idtokens: %s') %
260 260
                         openid_configuration['id_token_signing_alg_values_supported'])
261
    claims_parameter_supported = openid_configuration.get('claims_parameter_supported') is True
261 262
    kwargs = dict(
262 263
        ou=ou or get_default_ou(),
263 264
        name=name,
......
267 268
        userinfo_endpoint=openid_configuration['userinfo_endpoint'],
268 269
        jwkset_json=jwkset_json,
269 270
        idtoken_algo=idtoken_algo,
270
        strategy=models.OIDCProvider.STRATEGY_CREATE)
271
        strategy=models.OIDCProvider.STRATEGY_CREATE,
272
        claims_parameter_supported=claims_parameter_supported)
271 273
    if old_pk:
272 274
        models.OIDCProvider.objects.filter(pk=old_pk).update(**kwargs)
273 275
        return models.OIDCProvider.objects.get(pk=old_pk)
src/authentic2_auth_oidc/views.py
1 1
import uuid
2 2
import logging
3
import json
3 4

  
4 5
import requests
5 6

  
......
35 36
        'state': state,
36 37
        'nonce': nonce,
37 38
    }
39
    if provider.claims_parameter_supported:
40
        params['claims'] = json.dumps(provider.authorization_claims_parameter())
38 41
    if 'login_hint' in request.GET:
39 42
        params['login_hint'] = request.GET['login_hint']
40 43
    if get_language():
tests/test_auth_oidc.py
88 88
    jwkset.add(key)
89 89
    return jwkset
90 90

  
91

  
92
@pytest.fixture(params=[OIDCProvider.ALGO_RSA, OIDCProvider.ALGO_HMAC])
91
OIDC_PROVIDER_PARAMS = [
92
    {},
93
    {
94
        'idtoken_algo': OIDCProvider.ALGO_HMAC
95
    },
96
    {
97
        'claims_parameter_supported': True,
98
    }
99
]
100

  
101

  
102
@pytest.fixture(params=OIDC_PROVIDER_PARAMS)
93 103
def oidc_provider(request, db, oidc_provider_jwkset):
94
    idtoken_algo = request.param
104
    idtoken_algo = request.param.get('idtoken_algo', OIDCProvider.ALGO_RSA)
105
    claims_parameter_supported = request.param.get('claims_parameter_supported', False)
95 106
    from authentic2_auth_oidc.utils import get_provider, get_provider_by_issuer
96 107
    get_provider.cache.clear()
97 108
    get_provider_by_issuer.cache.clear()
......
113 124
        strategy=OIDCProvider.STRATEGY_CREATE,
114 125
        jwkset_json=jwkset,
115 126
        idtoken_algo=idtoken_algo,
127
        claims_parameter_supported=claims_parameter_supported,
116 128
    )
117 129
    provider.full_clean()
118 130
    OIDCClaimMapping.objects.create(
......
266 278
    assert query['scope'] == 'openid'
267 279
    assert query['redirect_uri'] == 'http://testserver' + reverse('oidc-login-callback')
268 280

  
281
    if oidc_provider.claims_parameter_supported:
282
        claims = json.loads(query['claims'])
283
        assert claims['id_token']['sub'] is None
284
        assert claims['userinfo']['email']['essential']
285
        assert claims['userinfo']['given_name']['essential']
286
        assert claims['userinfo']['family_name']['essential']
287
        assert claims['userinfo']['ou'] is None
288

  
269 289
    User = get_user_model()
270 290
    assert User.objects.count() == 0
271 291

  
272
-