Projet

Général

Profil

0001-auth_oidc-do-not-attempt-to-generate-one-s-own-clien.patch

Paul Marillonnet, 27 octobre 2022 09:40

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH] auth_oidc: do not attempt to generate one's own client
 credentials (#70749)

    these credentials must be issued to authentic by the OIDC provider,
    see for instance https://datatracker.ietf.org/doc/html/rfc6749#section-2.2
 .../commands/oidc-register-issuer.py          | 14 ++++++-----
 .../migrations/0001_initial.py                |  6 ++---
 src/authentic2_auth_oidc/models.py            |  5 ++--
 src/authentic2_auth_oidc/utils.py             |  4 +++-
 tests/test_auth_oidc.py                       | 18 ++++++++++++--
 tests/test_commands.py                        | 24 +++++++++++++++++--
 tests/test_manager_authenticators.py          |  9 +++++--
 7 files changed, 60 insertions(+), 20 deletions(-)
src/authentic2_auth_oidc/management/commands/oidc-register-issuer.py
60 60
        name = options['name']
61 61
        openid_configuration = options.get('openid_configuration')
62 62
        issuer = options.get('issuer')
63
        client_id = options.get('client_id')
64
        if not client_id:
65
            raise CommandError('Client identifier must be specified')
66
        client_secret = options.get('client_secret')
67
        if not client_secret:
68
            raise CommandError('Client secret must be specified')
63 69
        if openid_configuration:
64 70
            with open(openid_configuration) as fd:
65 71
                openid_configuration = json.load(fd)
......
70 76
                    ou = OrganizationalUnit.objects.get(slug=options['ou_slug'])
71 77
                provider = register_issuer(
72 78
                    name,
79
                    client_id,
80
                    client_secret,
73 81
                    issuer=issuer,
74 82
                    openid_configuration=openid_configuration,
75 83
                    verify=not options['no_verify'],
......
87 95
        except ValidationError as e:
88 96
            provider.delete()
89 97
            raise CommandError(e)
90
        client_id = options.get('client_id')
91
        if client_id:
92
            provider.client_id = client_id
93
        client_secret = options.get('client_secret')
94
        if client_secret:
95
            provider.client_secret = client_secret
96 98
        scope = options.get('scope')
97 99
        if scope is not None:
98 100
            provider.scopes = ' '.join(filter(None, options['scope']))
src/authentic2_auth_oidc/migrations/0001_initial.py
1
import uuid
2

  
3 1
import django.contrib.postgres.fields.jsonb
4 2
from django.conf import settings
5 3
from django.db import migrations, models
......
70 68
                    'issuer',
71 69
                    models.CharField(unique=True, max_length=256, verbose_name='issuer', db_index=True),
72 70
                ),
73
                ('client_id', models.CharField(default=uuid.uuid4, max_length=128, verbose_name='client id')),
71
                ('client_id', models.CharField(max_length=128, verbose_name='client id')),
74 72
                (
75 73
                    'client_secret',
76
                    models.CharField(default=uuid.uuid4, max_length=128, verbose_name='client secret'),
74
                    models.CharField(max_length=128, verbose_name='client secret'),
77 75
                ),
78 76
                (
79 77
                    'authorization_endpoint',
src/authentic2_auth_oidc/models.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import json
18
import uuid
19 18

  
20 19
from django.conf import settings
21 20
from django.contrib.postgres.fields import JSONField
......
81 80
    ]
82 81

  
83 82
    issuer = models.CharField(max_length=256, verbose_name=_('issuer'), db_index=True)
84
    client_id = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client id'))
85
    client_secret = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client secret'))
83
    client_id = models.CharField(max_length=128, verbose_name=_('client id'))
84
    client_secret = models.CharField(max_length=128, verbose_name=_('client secret'))
86 85
    # endpoints
87 86
    authorization_endpoint = models.URLField(max_length=128, verbose_name=_('authorization endpoint'))
88 87
    token_endpoint = models.URLField(max_length=128, verbose_name=_('token endpoint'))
src/authentic2_auth_oidc/utils.py
186 186
    return urllib.parse.urlparse(url).scheme == 'https'
187 187

  
188 188

  
189
def register_issuer(name, issuer=None, openid_configuration=None, verify=True, timeout=None, ou=None):
189
def register_issuer(
190
    name, client_id, client_secret, issuer=None, openid_configuration=None, verify=True, timeout=None, ou=None
191
):
190 192
    from . import models
191 193

  
192 194
    if issuer and not openid_configuration:
tests/test_auth_oidc.py
161 161
    idtoken_algo=OIDCProvider._meta.get_field('idtoken_algo').default,
162 162
    jwkset=None,
163 163
    claims_parameter_supported=False,
164
    client_id='abc',
165
    client_secret='def',
164 166
):
165 167
    slug = slug or name.lower()
166 168
    issuer = issuer or ('https://%s.example.com' % slug)
......
169 171
        ou=get_default_ou(),
170 172
        name=name,
171 173
        slug=slug,
174
        client_id=client_id,
175
        client_secret=client_secret,
172 176
        enabled=True,
173 177
        issuer=issuer,
174 178
        authorization_endpoint='%s/authorize' % issuer,
......
843 847
        return oidc_provider_jwkset.export()
844 848

  
845 849
    with HTTMock(jwks_mock):
846
        register_issuer(name='test_issuer', issuer='https://default.issuer', openid_configuration=oidc_conf)
850
        register_issuer(
851
            name='test_issuer',
852
            client_id='abc',
853
            client_secret='def',
854
            issuer='https://default.issuer',
855
            openid_configuration=oidc_conf,
856
        )
847 857

  
848 858
    oidc_conf['id_token_signing_alg_values_supported'] = ['HS256']
849 859
    with HTTMock(jwks_mock):
850 860
        register_issuer(
851
            name='test_issuer_hmac_only', issuer='https://hmac_only.issuer', openid_configuration=oidc_conf
861
            name='test_issuer_hmac_only',
862
            client_id='ghi',
863
            client_secret='jkl',
864
            issuer='https://hmac_only.issuer',
865
            openid_configuration=oidc_conf,
852 866
        )
853 867

  
854 868

  
tests/test_commands.py
274 274
    with oidc_conf_f.open() as f:
275 275
        oidc_conf = json.load(f)
276 276

  
277
    def register_issuer(name, issuer=None, openid_configuration=None, verify=True, timeout=None, ou=None):
277
    def register_issuer(
278
        name,
279
        client_id,
280
        client_secret,
281
        issuer=None,
282
        openid_configuration=None,
283
        verify=True,
284
        timeout=None,
285
        ou=None,
286
    ):
278 287
        ou = OrganizationalUnit.objects.get(default=True)
279 288
        # skipping jwkset retrieval in mocked function
280 289
        jwkset = JWKSet()
......
283 292
        return OIDCProvider.objects.create(
284 293
            name=name,
285 294
            slug='test',
295
            client_id='abc',
296
            client_secret='def',
286 297
            enabled=True,
287 298
            ou=ou,
288 299
            issuer=issuer,
......
299 310

  
300 311
    oidc_conf = py.path.local(__file__).dirpath('openid_configuration.json').strpath
301 312
    call_command(
302
        'oidc-register-issuer', '--openid-configuration', oidc_conf, '--issuer', 'issuer', 'somename'
313
        'oidc-register-issuer',
314
        '--openid-configuration',
315
        oidc_conf,
316
        '--issuer',
317
        'issuer',
318
        '--client-id',
319
        'auie',
320
        '--client-secret',
321
        'tsrn',
322
        'somename',
303 323
    )
304 324

  
305 325
    provider = OIDCProvider.objects.get(name='somename')
tests/test_manager_authenticators.py
166 166

  
167 167
    resp = resp.click('Edit')
168 168
    assert 'enabled' not in resp.form.fields
169
    assert resp.pyquery('input#id_client_id').val() == ''
170
    assert resp.pyquery('input#id_client_secret').val() == ''
169 171
    resp.form['ou'] = ou1.pk
170 172
    resp.form['issuer'] = 'https://oidc.example.com'
171 173
    resp.form['scopes'] = 'profile email'
......
176 178
    resp.form['idtoken_algo'] = 2
177 179
    resp.form['button_label'] = 'Test'
178 180
    resp.form['button_description'] = 'test'
181
    resp.form['client_id'] = 'auie'
182
    resp.form['client_secret'] = 'tsrn'
179 183
    resp = resp.form.submit().follow()
180 184
    assert_event('authenticator.edit', user=superuser, session=app.session)
181 185

  
......
195 199
    resp = resp.click('Journal')
196 200
    assert 'enable' in resp.text
197 201
    assert (
198
        'edit (ou, issuer, scopes, strategy, button_label, idtoken_algo, token_endpoint, '
199
        'userinfo_endpoint, button_description, authorization_endpoint)' in resp.text
202
        'edit (ou, issuer, scopes, strategy, client_id, button_label, idtoken_algo, '
203
        'client_secret, token_endpoint, userinfo_endpoint, button_description, authorization_endpoint)'
204
        in resp.text
200 205
    )
201 206
    assert 'creation' in resp.text
202 207

  
203
-