Development #21870
oidc : configuration pour les attributs liés aux scopes
100%
Description
create_user_info() dans src/authentic2_idp_oidc/utils.py hardcode quelques attributs pour les scopes oidc profile et email; côté CUT c'est étendu via le hook idp_oidc_modify_user_info mais peut-être qu'il pourrait quand même déjà exister une mécanique de base pour étendre ça sans code; en ayant les scopes sous forme d'objets qui pourraient ainsi être modifiés (comme on peut configurer côté SAML les attributs envoyés), ou en ayant les scopes dans les settings.
Fichiers
Demandes liées
Révisions associées
Historique
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0001-idp-oidc-allow-extension-of-user-info-21870.patch 0001-idp-oidc-allow-extension-of-user-info-21870.patch ajouté
- Statut changé de Nouveau à En cours
- Assigné à mis à Josué Kouka
- Patch proposed changé de Non à Oui
C'est un patch draft
. Pour l'instant il ne permet l'extension des user info
via un settings A2_IDP_OIDC_USER_INFO_EXTRA
pour le domaine profile
.
Pour les scopes email
et roles
je ne vois pas trop de cas d'utilisation ou l'on voudrait modifier les infos retournées (il y'en a peut etre).
Je me dis que l'on pourrait vouloir supprimer des attributs retournés, du coup je me demande si une config comme celle ci dessous conviendrait ?:
{ "<scope>": { "add": [...], "remove": [...] } }
Mis à jour par Benjamin Dauvergne il y a environ 6 ans
Je suis contre ce ticket, c'est soit on fait les choses bien, et donc stocker avec le reste de la configuration OIDC, soit on reste sur le hook pour l'instant.
Mis à jour par Josué Kouka il y a environ 6 ans
Benjamin Dauvergne a écrit :
Je suis contre ce ticket, c'est soit on fait les choses bien, et donc stocker avec le reste de la configuration OIDC
Ok, par config oidc tu entends config du client ?
Mis à jour par Benjamin Dauvergne il y a environ 6 ans
Oui, un modèle du genre:
class OIDCAttribute client = FK(OIDCClient) name = CharField() # "adress.street" <- sera déplié value = CharField() # le nom d'un attribut comme coté SAML scope = CharField() # une liste de scopes dont il fait partie
idéalement ça devrait gérer le cas où un attribut est vérifié en servant le même nom avec le suffixe _verified
comme le prévoit la norme.
Une migration de donnée devrait ajouter les attributs existants comme OIDCAttribute et on pourrait ensuite supprimer le code en dur.
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0001-idp-oidc-add-extra-attributes-configuration-21870.patch 0001-idp-oidc-add-extra-attributes-configuration-21870.patch ajouté
Benjamin Dauvergne a écrit :
Oui, un modèle du genre:
[...]
idéalement ça devrait gérer le cas où un attribut est vérifié en servant le même nom avec le suffixe
_verified
comme le prévoit la norme.Une migration de donnée devrait ajouter les attributs existants comme OIDCAttribute et on pourrait ensuite supprimer le code en dur.
Ok.
Un patch un peu brouillon de ce qui est expliqué plus haut. Je rencontre un souci lors de la récupération des valeurs de certains attributs. J'ai un test qui échoue parce que authentic2.attributes_ng.engine.get_attributes
ne me renvoie pas la bonne valeur. Dans le cas ou je demande à renvoyer django_user_ou
je suis censé avoir en retour une valeur sérialisée, mais ce n'est pas la cas ou quand je demande a2_role_names
je reçois une QuerySet
à la place des noms de roles.
Je creuse pour essayer de comprendre le pourquoi.
Mis à jour par Benjamin Dauvergne il y a environ 6 ans
Oui le code est un peu brut:
def get_attributes(instance, ctx): user = ctx.get('user') User = get_user_model() if not user or not isinstance(user, User): return ctx for field in User._meta.fields: value = getattr(user, field.name) if value is None: continue ctx['django_user_' + str(field.name)] = getattr(user, field.name) # pour django_user_ou on va obtenir le modèle directement for av in AttributeValue.objects.with_owner(user): ctx['django_user_' + str(av.attribute.name)] = av.to_python() ctx['django_user_' + str(av.attribute.name) + ':verified'] = av.verified ctx['django_user_groups'] = [group for group in user.groups.all()] ctx['django_user_group_names'] = [unicode(group) for group in user.groups.all()] if user.username: splitted = user.username.rsplit('@', 1) ctx['django_user_domain'] = splitted[1] if '@' in user.username else '' ctx['django_user_identifier'] = splitted[0] if '@' in user.username else '' ctx['django_user_full_name'] = user.get_full_name() Role = get_role_model() roles = Role.objects.for_user(user) ctx['a2_role_slugs'] = roles.values_list('slug', flat=True) ctx['a2_role_names'] = roles.values_list('name', flat=True) ctx['a2_role_uuids'] = roles.values_list('uuid', flat=True) if 'service' in ctx and getattr(ctx['service'], 'ou', None): ou = ctx['service'].ou ctx['a2_service_ou_role_slugs'] = roles.filter(ou=ou).values_list('slug', flat=True) ctx['a2_service_ou_role_names'] = roles.filter(ou=ou).values_list('name', flat=True) <-- ValuesQuerySet ctx['a2_service_ou_role_uuids'] = roles.filter(ou=ou).values_list('uuid', flat=True) return ctx
Coté SAML je suppose que ça applique unicode() là dessus ou que ça génère plusieurs valeurs si ça détecte une liste, et oui c'est ça (ctx est le résultat de l'appel à get_attributes()).
tuples = [tuple(t) for definition in qs for t in definition.to_tuples(ctx)] seen = set() for name, name_format, friendly_name, value in tuples:
def to_tuples(self, ctx): if not self.attribute_name in ctx: return name_format = self.name_format_uri() name = self.name friendly_name = self.friendly_name or None values = ctx.get(self.attribute_name) for text_value in normalize_attribute_values(values): yield (name, name_format, friendly_name, text_value)
def normalize_attribute_values(values): '''Take a list of values or a single one and normalize it''' values_set = set() if isinstance(values, basestring) or not hasattr(values, '__iter__'): values = [values] for value in values: if isinstance(value, bool): value = str(value).lower() values_set.add(unicode(value)) return values_set
Mis à jour par Benjamin Dauvergne il y a environ 6 ans
- en OIDC on appelle les attributs des "claim"s, renomme OIDCAttribut en OIDCClaim pour rester dans le jargon OIDC
- modifie normalize_attribute_values() pour ne pas systématiquement convertir les valeurs uniques en liste (c'est normal en SAML mais en OIDC qui fait la différence entre liste et valeur unique puisque le format de transfert c'est du JSON, ce n'est pas nécessaire), tu peux en faire un flag ou mieux deux fonctions différentes, idem sur la normalisation vers unicode, je ne suis pas sûr que ce soit toujours nécessaire, il vaut peut-être mieux le réserver pour les modèles, en fait il faut peut-être mieux un normalize_attribute_values() spécifique à OIDC
- applique simplement normalize_attribute_values() à tes valeurs avant d'utiliser la valeur dans user_info
- manque la migration de donnée pour créer les OIDCClaim pour la configuration de base
- dans l'admin si on ne définit pas d'attributs (il y a un InlineFormSet, on doit pouvoir détecter qu'il est vide, et/ou l'initialiser dans la vue de création d'un OIDCClient) créer aussi la configuration de base
- j'aurai plutôt vu l'association scope<->claim sur un objet OIDCScope mais restons comme cela pour l'instant
- d'après la spécification OIDC on peut demander un claim via son scope mais aussi directement par son nom (chaque nom de claim est lui même un scope normalement)
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0002-idp-oidc-add-extra-attributes-configuration-21870.patch 0002-idp-oidc-add-extra-attributes-configuration-21870.patch ajouté
- Fichier 0001-make-attribute-engine-properly-return-user-ou-data.patch 0001-make-attribute-engine-properly-return-user-ou-data.patch ajouté
- renommage de
OIDCAttribute
enOIDCClaim
- pré-définition des différents
claim
lors de la création (dans l'admin) - migration des données pour la création des
OIDCClaim
pour les client déja existant + test.
Par contre, j'ai un test qui échoue car l'email de l'utilisateur n'est pasvérifié
. Je suis en train de chercher le pourquoi.
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0002-idp-oidc-add-extra-attributes-configuration-21870.patch 0002-idp-oidc-add-extra-attributes-configuration-21870.patch ajouté
- Fichier 0001-make-attribute-engine-properly-return-user-ou-data.patch 0001-make-attribute-engine-properly-return-user-ou-data.patch ajouté
> Par contre, j'ai un test qui échoue car l'email de l'utilisateur n'est pas vérifié. Je suis en train de chercher le pourquoi.
Dans l'ancienne implémentation, email_verified
était forcé à True
.
Patch avec tous les tests qui passent.
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0002-idp-oidc-add-extra-attributes-configuration-21870.patch 0002-idp-oidc-add-extra-attributes-configuration-21870.patch ajouté
- Fichier 0001-make-attribute-engine-properly-return-user-ou-data.patch 0001-make-attribute-engine-properly-return-user-ou-data.patch ajouté
J'avais intorduit un bug (les valeurs des attributs des OU étaient fausses) dans get_attributes
dans mon précédent patch. C'est corrigé dans ceux-ci.
Mis à jour par Benjamin Dauvergne il y a environ 6 ans
Tu ne devrais pas avoir à poser oidcclient explicitement dans une ModelForm, l'objet en cours est disponible dans self.instance.
L'initialisation de initial peut se faire même en dehors d'un POST et juste avant cette ligne:
formset.__init__ = curry(formset.__init__, initial=initial)
Je te propose de définir un get_service_variables(service)
à partager avec le code SAML équivalent dans authentic2/saml/admin.py.
Au passage un besoin de dernière minute, ce serait bien de regarder aussi la présence d'une clé claim.value + ':verified'
dans attributes
et si c'est présent et vrai de poser un claim claim.name + '_verified'
dans user_info
avec la valeur True
. C'est la façon standard en OIDC de passer le statut vérifié d'un attribut.
Mis à jour par Josué Kouka il y a environ 6 ans
- Fichier 0002-idp-oidc-add-extra-attributes-configuration-21870.patch 0002-idp-oidc-add-extra-attributes-configuration-21870.patch ajouté
- Fichier 0001-make-attribute-engine-properly-return-user-ou-data.patch 0001-make-attribute-engine-properly-return-user-ou-data.patch ajouté
J'ai pris en compte des remarques, par contre en ce qui concerne
L'initialisation de initial peut se faire même en dehors d'un POST et juste avant cette ligne
cela ne semble pas fonctionner. D'apres les tests, on a l'impréssion que les attributs sont définis alors qu'en vrai ils ne le sont pas.
Mis à jour par Benjamin Dauvergne il y a presque 6 ans
Ajoute une commentaire au dessus de
# formsets are only saved if formset.has_changed() is True, so only set initial # values on the GET (display of the creation form) if request.method == 'GET' and not obj:
et ack.
Mis à jour par Josué Kouka il y a presque 6 ans
- Lié à Bug #23544: Ajouter la possibilité de demander un CLAIM via son nom ajouté
Mis à jour par Josué Kouka il y a presque 6 ans
- Statut changé de En cours à Résolu (à déployer)
- % réalisé changé de 0 à 100
commit f06900ead4e8ebd1b0b6d759aa0547344d152a64 (HEAD -> master, origin/master, origin/HEAD) Author: Josue Kouka <jkouka@entrouvert.com> Date: Fri Apr 13 09:17:19 2018 +0200 idp oidc: add extra attributes configuration (#21870)
Mis à jour par Frédéric Péters il y a plus de 5 ans
- Statut changé de Résolu (à déployer) à Fermé
idp oidc: add extra attributes configuration (#21870)