Projet

Général

Profil

0001-auth_oidc-factorize-claim-mapping-resolution-72418.patch

Paul Marillonnet, 31 janvier 2023 15:02

Télécharger (7,37 ko)

Voir les différences:

Subject: [PATCH] auth_oidc: factorize claim mapping resolution (#72418)

 src/authentic2_auth_oidc/backends.py | 29 ++++-----------------------
 src/authentic2_auth_oidc/models.py   | 20 ++++++++-----------
 src/authentic2_auth_oidc/utils.py    | 30 ++++++++++++++++++++++++++++
 3 files changed, 42 insertions(+), 37 deletions(-)
src/authentic2_auth_oidc/backends.py
34 34
from authentic2.models import Lock
35 35
from authentic2.utils import hooks
36 36
from authentic2.utils.crypto import base64url_encode
37
from authentic2.utils.template import Template
38 37

  
39 38
from . import models, utils
40 39

  
......
152 151
        user_ou = provider.ou
153 152
        user_info = None
154 153
        save_user = False
155
        mappings = []
156 154
        context = id_token_content.copy()
157 155
        need_user_info = False
158 156
        for claim_mapping in provider.claim_mappings.all():
......
182 180
                    logger.debug('auth_oidc: user_info content %s', user_info)
183 181
                    context.update(user_info or {})
184 182

  
185
        for claim_mapping in provider.claim_mappings.all():
186
            claim = claim_mapping.claim
187
            if claim_mapping.idtoken_claim:
188
                source = id_token
189
            else:
190
                source = user_info
191
            if not source or claim not in source and not ('{{' in claim or '{%' in claim):
192
                continue
193
            verified = False
194
            attribute = claim_mapping.attribute
195
            if '{{' in claim or '{%' in claim:
196
                template = Template(claim)
197
                value = template.render(context=context)
198
                # xxx missing verification logic for templated claims
199
            else:
200
                value = source.get(claim)
201
                if claim_mapping.verified == models.OIDCClaimMapping.VERIFIED_CLAIM:
202
                    verified = bool(source.get(claim + '_verified', False))
183
        mappings = utils.resolve_claim_mappings(provider, context, id_token, user_info)
184
        for attribute, value, dummy in mappings:
203 185
            if attribute == 'ou__slug' and value in ou_map:
204 186
                user_ou = ou_map[value]
205
                continue
206
            if claim_mapping.verified == models.OIDCClaimMapping.ALWAYS_VERIFIED:
207
                verified = True
208
            mappings.append((attribute, value, verified))
187
                break
209 188

  
210 189
        # check for required claims
211 190
        for claim_mapping in provider.claim_mappings.all():
......
393 372

  
394 373
        # new style attributes
395 374
        for attribute, value, verified in mappings:
396
            if attribute in ('username', 'email'):
375
            if attribute in ('username', 'email', 'ou__slug'):
397 376
                continue
398 377
            if attribute in ('first_name', 'last_name') and not verified:
399 378
                continue
src/authentic2_auth_oidc/models.py
38 38
from authentic2.utils.misc import make_url
39 39
from authentic2.utils.template import Template, validate_template
40 40

  
41
from . import managers
41
from . import managers, utils
42 42

  
43 43
if django.VERSION < (3, 1):
44 44
    from django.contrib.postgres.fields.jsonb import JSONField  # noqa pylint: disable=ungrouped-imports
......
334 334
                except OIDCAccount.MultipleObjectsReturned:
335 335
                    continue
336 336
                had_changes = False
337
                for claim in self.claim_mappings.all():
338
                    if '{{' in claim.claim or '{%' in claim.claim:
339
                        template = Template(claim.claim)
340
                        attribute_value = template.render(context=user_dict)
341
                    else:
342
                        attribute_value = user_dict.get(claim.claim)
337
                mappings = utils.resolve_claim_mappings(self, user_dict)
338
                for attribute, value, dummy in mappings:
343 339
                    try:
344
                        old_attribute_value = getattr(account.user, claim.attribute)
340
                        old_attribute_value = getattr(account.user, attribute)
345 341
                    except AttributeError:
346 342
                        try:
347
                            old_attribute_value = getattr(account.user.attributes, claim.attribute)
343
                            old_attribute_value = getattr(account.user.attributes, attribute)
348 344
                        except AttributeError:
349 345
                            old_attribute_value = None
350
                    if old_attribute_value == attribute_value:
346
                    if old_attribute_value == value:
351 347
                        continue
352 348
                    had_changes = True
353
                    setattr(account.user, claim.attribute, attribute_value)
349
                    setattr(account.user, attribute, value)
354 350
                    try:
355
                        setattr(account.user.attributes, claim.attribute, attribute_value)
351
                        setattr(account.user.attributes, attribute, value)
356 352
                    except AttributeError:
357 353
                        pass
358 354
                if had_changes:
src/authentic2_auth_oidc/utils.py
28 28
from authentic2.a2_rbac.utils import get_default_ou
29 29
from authentic2.models import Attribute
30 30
from authentic2.utils.cache import GlobalCache
31
from authentic2.utils.template import Template
32

  
33
from . import models
31 34

  
32 35
TIMEOUT = 1
33 36

  
......
73 76
    return json_decode(jwt.claims)
74 77

  
75 78

  
79
def resolve_claim_mappings(provider, context, id_token=None, user_info=None):
80
    mappings = []
81
    for claim_mapping in provider.claim_mappings.all():
82
        claim = claim_mapping.claim
83
        if id_token is None and user_info is None:
84
            source = context
85
        elif claim_mapping.idtoken_claim:
86
            source = id_token
87
        else:
88
            source = user_info
89
        if not source or claim not in source and not ('{{' in claim or '{%' in claim):
90
            continue
91
        verified = False
92
        attribute = claim_mapping.attribute
93
        if '{{' in claim or '{%' in claim:
94
            template = Template(claim)
95
            value = template.render(context=context)
96
        else:
97
            value = source.get(claim)
98
            if claim_mapping.verified == models.OIDCClaimMapping.VERIFIED_CLAIM:
99
                verified = bool(source.get(claim + '_verified', False))
100
        if claim_mapping.verified == models.OIDCClaimMapping.ALWAYS_VERIFIED:
101
            verified = True
102
        mappings.append((attribute, value, verified))
103
    return mappings
104

  
105

  
76 106
REQUIRED_ID_TOKEN_KEYS = {'iss', 'sub', 'aud', 'exp', 'iat'}
77 107
KEY_TYPES = {
78 108
    'iss': str,
79
-