Capturer et traiter des événements — WSO2 IS, une architecture événementielle [1/2]

Gregory EVE
Smile with WSO2
Published in
6 min readJun 10, 2021

WSO2 Identity Server est basé sur un framework événementiel permettant d’étendre les capacités du produit. Découvrons comment les utiliser.

Cette série, “WSO2 IS, une architecture événementielle” est composée de 2 parties :

1. Capturer et traiter des événements

2. Envoyer une notification (bientôt)

Introduite dans la version 5.3 de WSO2 Identity Server début 2017, le framework événementiel était au début réservé aux fonctionnalités de gestion d’identité. Sa souplesse et sa simplicité ont fait que peu à peu son rôle a été étendu pour devenir la clé de voûte des capacités d’extension du produit.

C’est sur cette base que WSO2 a implémenté ses différentes fonctionnalités de gouvernance comme le contrôle de la complexité du mot de passe, la limitation du nombre de tentative d’authentification ou le contrôle de la propriété d’une adresse mail. Puis étendu son usage à l’envoi de données analytiques ou permettre la déconnexion multi-protocole.

Les événements d’identités (Events)

L’Editeur a pris le principe, pour faciliter le branchement, d’introduire des événements précédents une tâche et suivant une tâche. Nous avons par exemple :

Un événement est défini par WSO2 comme une instance de la classe org.wso2.carbon.identity.event.event.Event Elle possède un nom et une liste de propriétés.

Les noms d’événements possibles (ie. type d’événement) sont référencés dans la classe org.wso2.carbon.identity.event.IdentityEventConstants.Event. Si l’on tente de les catégoriser nous avons des événements liés à :

  • des opérations CRUD sur les identités (user et claims)
  • des opérations CRUD sur les rôles et permissions
  • la gestion de compte (changement de mot de passe, récupération et suspension de compte, etc.)
  • le déclenchement de notification
  • l’authentification
  • la mise à jour à chaud des configurations (politiques de gouvernance)

La liste de propriétés est non finie. WSO2 a normalisé la nomenclature de certaines propriétés couramment utilisées. On peut en trouver la liste au sein de org.wso2.carbon.identity.event.IdentityEventConstants.EventProperty. Vous retrouvez ainsi les informations essentielles suivantes :

  • user-name : le nom du compte concerné
  • tenantId : le tenant auquel appartient cet utilisateur
  • userStoreManager : le gestionnaire d’annuaire du tenant concerné
  • USER_CLAIMS : les informations d’identités liés au compte concerné

Les plus curieux d’entre vous se poseront sans doute la question “Mais qui envoie ces évènements et comment?”. WSO2 a ajouté la déclaration d’un nouveau listener, org.wso2.carbon.identity.governance.listener.IdentityMgtEventListener de type UserOperationEventListener dans le fichier [WSO2_HOME]/repository/conf/identity/identity.xml . Celui-ci écoute toutes les opérations à destination des user stores et émet les évènements correspondants, CQFD!

Les gestionnaires d’événements d’identité (Handlers)

Vous pouvez ajouter autant de gestionnaire d’événements que vous le souhaitez. Leur rôle est d’intercepter un ou plusieurs événements et de réaliser un traitement spécifique. Un événement pouvant être écouté par plusieurs handlers l’éditeur a pris le parti de les exécuter séquentiellement par ordre de priorité. Ainsi un premier handler peut avoir des conséquences sur l’exécution ou non du second et ainsi de suite.

Pour pouvoir traiter des événements il vous faut :

  1. implémenter un gestionnaire d’événements, ou handler
  2. enregistrer ce dernier en tant que service OSGI
  3. déclarer celui-ci pour recevoir les événements souhaités
  4. optionnellement configurer celui-ci

Implémentation d’un Handler

Un handler est défini comme un héritage de la classe abstraite org.wso2.carbon.identity.event.handler.AbstractEventHandler.

Il vous faudra implémenter la méthode suivante pour traiter les événements :

public void handleEvent(Event event) throws IdentityEventException {
/* votre logique de traitement */ }

De manière optionnelle vous pourrez également surcharger les méthodes fournissant le nom du handler et sa priorité d’exécution :

/* par défaut le nom de la classe */
public String getName() { return "myHandler"; }
/* par défaut l'ordre de déclaration du handler */
public int getPriority(MessageContext messageContext) {
return 50; }

Enregistrement du service

Pour enregistrer votre handler en tant que service c’est très simple. Vous déclarez votre composant OSGI, et au moment de son activation, vous enregistrez une instance de votre handler en tant que service sous le type AbstractEventHandler.

@Component(name = "org.wso2.carbon.extension.identity.authenticator.myComponent", immediate = true)
public class MyServiceComponent {
private static Log log = LogFactory.getLog(MyServiceComponent.class);@Activate
protected void activate(ComponentContext context) {
try {
BundleContext bundleContext = context.getBundleContext();
// register the handler as an OSGI service bundleContext.registerService(AbstractEventHandler.class.getName(), new MyHandler(), null);log.info("MyServiceComponent bundle activated successfully..");
} catch (Throwable e) {
log.fatal("MyServiceComponent bundle can't be activated", e);
}
}
@Deactivate
protected void deactivate(ComponentContext context) {
log.info("MyServiceComponent is deactivated ");
}

Déclaration pour recevoir des événements

Maintenant que nous avons notre handler de disponible il faut configurer le broker d’événement pour lui signifier que nous souhaitons recevoir les événements qui portent tel ou tel nom.

Depuis WSO2 Identity Server 5.9.0 vous devez réaliser cette configuration au sein du fichier unique de configuration cdeployment.toml :

[[event_handler]]
name= "myHandler"
subscriptions =["PRE_ADD_USER", "POST_ADD_USER"]

Grâce à ces éléments, au lancement du produit, le fichier [WSO2_HOME]/repository/conf/identity/identity-event.properties sera généré puis lu par le broker.

module.name.7=myHandler
myHandler.subscription.1=PRE_ADD_USER
myHandler.subscription.2=POST_ADD_USER

Configuration

Si nous avons besoin de paramétrer ce handler pour l’activer ou le désactiver à chaud par exemple il nous faudra lui adjoindre un gestionnaire de configuration de gouvernance en implémentant l’interface org.wso2.carbon.identity.governance.common.IdentityConnectorConfig.

/* identifiant de la configuration */
public String getName();
/* nom affichable de la configuration */
public String getFriendlyName();
/* nom de la catégorie */
public String getCategory();
/* nom de la sous-catégorie */
public String getSubCategory();
/* liste des paramètres */
public String[] getPropertyNames()
/* nom affichable des paramètres */
public Map<String, String> getPropertyNameMapping();
/* description des paramètres */
public Map<String, String> getPropertyDescriptionMapping();
/* valeurs des paramètres selon le tenant */
public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityGovernanceException;
/* valeurs d'une liste finie de paramètres selon le tenant */
public Map<String, String> getDefaultPropertyValues(String[] propertyNames, String tenantDomain) throws IdentityGovernanceException;

Vous devrez ensuite, de la même manière que votre handler l’enregistrer en tant que service :

bundleContext.registerService(IdentityConnectorConfig.class.getName(), new myConfig(), null);

Ceci vous permettra de modifier les paramètres, à chaud, dans l’interface Carbon ou la console d’administration.

Et si vous souhaitez paramétrer votre handler par fichier, pas besoin d’implémenter cette interface, il vous suffit d’indiquer vos paramètres via votre [WSO2_HOME]/repository/conf/deployment.toml

[[event_handler]]
name="myHandler"
subscriptions=["PRE_ADD_USER", "POST_ADD_USER"]
[event_handler.properties]
enable=true

qui génèrera le fichier [WSO2_HOME]/repository/conf/identity/identiy-event.properties de cette manière

module.name.7=myHandler
myHandler.subscription.1=PRE_ADD_USER
myHandler.subscription.2=POST_ADD_USER
myHandler.enable=true

Et ensuite vous devrez récupérer les valeurs de vos paramètres dans votre handler via l’objet ModuleConfigurations chargé par défaut à l’initialisation dans propriété configs comme ceci:

//IdentityEventConfigBuilder.getInstance().getModuleConfigurations(NAME)
configs.getModuleProperties().getProperty(myParamName);

Et bien sûr vous pouvez mixer les 2 méthodes pour avoir des paramètres à chaud et par fichier ou bien même définir des valeurs par défaut par fichier surchargeable à chaud comme le fait l’éditeur dans certains de ses handlers.

Mise en pratique

Imaginons que nous souhaitons définir une politique de nomenclature des noms d’utilisateur. Le produit permet de configurer des Regex par User Store. Mais nous souhaitons mettre en oeuvre une politique plus globale à l’image de ce que le produit propose pour les mots de passe.

Nous allons implémenter un handler UsernameRegexValidatorHandler et comme nous souhaitons pouvoir le configurer à chaud nous lui ajoutons donc un IdentityConnectorConfig. Il nous faut ensuite enregistrer nos services OSGI. Puis, une fois compilé et déployé, nous pouvons déclarer et configurer notre handler pour s’abonner à l’évènement PRE_ADD_USER.

Et c’est fini!

--

--

Gregory EVE
Smile with WSO2

Solution Architect at Smile, french lover and open source supporter. Integrate, Search, Leverage and Secure your data what else?