JDBC
Le connecteur Scripted-JDBC permet la synchronisation des données complexe entre RAC/M Identity et les bases de données relationnelles utilisant le pilote JDBC.
Le connecteur a été testé avec SQL Server(2017, 2019, 2020), Azure SQL, Oracle, MySQL de la solution.
Jar
Cette configuration est basée sur le connecteur ICF Scripted SQL - 1.1.1.0.
Configuration
Les paramètres suivants sont nécessaires:
| Configuration | Description | Exemple |
|---|---|---|
| Name Quoting | Définit le style de guillemets utilisé pour les noms d'entités dans les scripts. | ", ', ou aucun |
| Reload Script on Execution | Indique si le script doit être rechargé à chaque exécution. | true ou false |
| Delete Script Filename | Chemin du fichier script utilisé pour supprimer des enregistrements de la base de données. | C:\Program Files\Okiok Data\RACM Identity\ICF\scripts 1.1.0.0\racm\Update.groovy |
| Search Script Filename | Chemin du fichier script utilisé pour rechercher des enregistrements dans la base de données. | C:\Program Files\Okiok Data\RACM Identity\ICF\scripts 1.1.0.0\racm\Search.groovy |
| Update Script Filename | Chemin du fichier script utilisé pour mettre à jour des enregistrements dans la base de données. | C:\Program Files\Okiok Data\RACM Identity\ICF\scripts 1.1.0.0\racm\Update.groovy |
| Schema Script Filename | Chemin du fichier script utilisé pour initialiser ou valider le schéma de la base de données. | C:\Program Files\Okiok Data\RACM Identity\ICF\scripts 1.1.0.0\racm\Schema.groovy |
| Test Script Filename | Chemin du fichier script utilisé pour tester la connectivité ou une fonctionnalité spécifique. | C:\Program Files\Okiok Data\RACM Identity\ICF\scripts 1.1.0.0\racm\TestSecurity.groovy |
| Host | Adresse ou nom de domaine du serveur de base de données. | localhost |
| Port | Numéro de port utilisé pour se connecter au serveur de base de données. | 1433 |
| User | Nom d'utilisateur pour la connexion au serveur. Utilisez un compte dédié restreint aux tables LOGIN et LOGIN_ROLE, et non un compte administrateur intégré tel que sa. | racm_connector |
| User Password | Mot de passe pour la connexion au serveur. | Password123 |
| Database | Nom de la base de données à laquelle se connecter. | racm_demo_config |
| JDBC Driver | Nom de la classe Java implémentant le pilote JDBC. | com.microsoft.sqlserver.jdbc.SQLServerDriver |
| JDBC Connection URL | URL utilisée pour la connexion. | jdbc:sqlserver://ip:1433;databaseName=RACM_PROD_DATA |
Configuration avancée
| Configuration | Description | Exemple |
|---|---|---|
| Name Quoting | Définit le style de guillemets utilisé pour les noms d'entités dans les scripts. | ", ', ou aucun |
| Reload Script on Execution | Indique si le script doit être rechargé à chaque exécution. | true ou false |
| Authenticate Script Filename | Chemin du fichier script utilisé pour authentifier les utilisateurs ou les sessions. | |
| Clear Text Password to Script | Spécifie si les mots de passe en clair doivent être transmis au script. | true ou false |
| autoCommit | Option permettant d'activer ou de désactiver la validation automatique des transactions. | true ou false |
| Scripting Language | Langage utilisé pour les scripts. | Groovy |
| Sync Script Filename | Chemin du fichier script utilisé pour synchroniser les données entre différents systèmes. | |
| Enable Writing Empty String | Option pour autoriser ou interdire l'écriture de chaînes vides dans la base de données. | true ou false |
| Rethrow All SQLExceptions | Option permettant de relancer toutes les exceptions SQL capturées pour le débogage ou la journalisation. | true ou false |
| Native Timestamps | Option pour utiliser les types natifs de la base de données pour les horodatages, au lieu de conversions applicatives. | true ou false |
| All Native | Option pour imposer l'utilisation exclusive des types natifs de la base de données pour tous les champs pris en charge. | true ou false |
| Validate Connection Query | Requête SQL utilisée pour valider la connexion à la base de données. | SELECT 1 |
| Datasource Path | Chemin JNDI ou chemin direct vers la configuration de la source de données. | |
| Initial JNDI Properties | Propriétés spécifiques nécessaires pour initialiser le contexte JNDI de la source de données. |
Note
Pour Azure SQL : Le seul driver disponible est com.microsoft.sqlserver.jdbc.SQLServerDriver.
Certificat
Un certificat pourrait être nécessaire en mode SSL.
Note
Au niveau de l'URL JDBC pour Microsoft Azure, le paramètre encrypt doit être présent afin de gérer le chiffrement des connexions SQL :
encrypt=false: Désactive le chiffrement, ce qui exclut l'utilisation d'un certificat. Cette configuration n'est pas recommandée pour des environnements de production.encrypt=true: Active le chiffrement, ce qui inclut l'utilisation d'un certificat. Dans ce cas, le certificat SQL correspondant doit être présent dans le truststore de l'ICF (Identity Connector Framework).
Ajoutez le certificat au truststore de l'ICF.
Redémarrage
Attention
Lorsque vous effectuez un changement dans le truststore par défaut de l'ICF, un redémarrage du service RAC/M est obligatoire.
Test
Vous pouvez tester le connecteur Script sql en appuyant sur le bouton TEST. Par contre, une implémentation du Test Script doit être réalisée.
Importation
Les schémas définis dans Schema Script Filename seront disponibles pour la Colonne de la table source avec la primitive ModuleICFImportData dans les classes d'objets sous le connecteur ICF choisi.
_ACCOUNT_est la variable par défaut d'ICF pour représenter les comptes définis dans le schéma._GROUP_est la variable par défaut d'ICF pour représenter les groupes définis dans le schéma.
Mappage de comptes
- ICF Connecteur -> Table source
_ACCOUNT_-> table cibleAPPLICATION_ACCOUNT_IMPORT.
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | _NAME_ | ||
| EIN | GIA_ID | ||
| EXT_APPLICATION_ID | RACM | ||
| EXTRA1 | LANGUAGE | ||
| EXTRA2 | |||
| FULL_NAME | FULL_NAME | ||
| GUID | _GUID_ | ||
| STATUS | Actif |
Mappage de groupes
Chargement des groupes avec ModuleICFImportData.
- ICF Connecteur -> Table source
_GROUP_-> table ciblePROFILE_IMPORT.
| Colonne RACM | Attribut du système cible | Formatage (Constante) |
|---|---|---|
| GUID | _UID_ | |
| EXT_PROFILE_ID | _UID_ | |
| PROFIL_NAME | DESCRIPTION | |
| EXT_APPLICATION_ID | RACM |
Mappage de membres
Chargement des membres avec ModuleICFImportData.
- ICF Connecteur -> Table source
_ROLE_MEMBERS_-> table ciblePROFILE_IMPORT.
| Colonne RACM | Attribut du système cible | Formatage (Constante) |
|---|---|---|
| ACCOUNT_NAME | LOGIN | |
| EXT_PROFILE_ID | LOGIN_ROLE_ID | |
| GUID | LOGIN_ROLE_ID | |
| IDENTIFIER1 | _UID_ | |
| EXT_APPLICATION_ID | RACM |
Matérialisation
Dans l'actif une configuration doit être faite pour activer le provisionnement.
- Sélectionner un ICF Connecteur.
- Sélectionner une stratégie de comptes pour créer le nom de compte et terminer le ou les comptes.
- Définir les politiques de mot de passe. Dans notre exemple, il n'y a pas de mot de passe.
- Définir les politiques de nom d'utilisateur. Elles seront assignées à cette variable durant le mapping :
${ACCOUNT_NAME}.
Ensuite, des modules doivent être configurés pour traiter les activations, modifications, créations, terminaisons et désactivations.
Le traitement de ces modules correspond à la matérialisation, qui consiste à créer les données nécessaires pour que le provisionnement puisse être traité.
- La matérialisation est effectuée dans la table
APPLICATION_ACCOUNT_IMPORT. SYNC_STATUSsera défini à 0 lorsque le compte sera matérialisé et en attente du provisionnement.
Note
Si les informations ne sont pas disponibles dans le mapping de base ou si une jointure est nécessaire pour récupérer les données, un module SQL devrait être créé pour mettre à jour notre matérialisation.
Activer
Configurer une primitive ModuleModifyAccountFromAccountMng
- Matérialiser un
ACTIVATE. - Table source
APPLICATION_ACCOUNT-> table cibleAPPLICATION_ACCOUNT_IMPORT.
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | ACCOUNT_NAME | X | |
| EIN | EIN | ||
| EXT_APPLICATION_ID | RACM | ||
| EXTRA1 | EXTRA1 | ||
| EXTRA2 | update | ||
| FULL_NAME | FULL_NAME | ||
| STATUS | Actif |
Modifier
Configurer une primitive ModuleModifyAccountFromAccountMng
- Matérialiser un
MODIFY - Table source
APPLICATION_ACCOUNT-> table cibleAPPLICATION_ACCOUNT_IMPORT.
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | ACCOUNT_NAME | X | |
| EIN | EIN | ||
| EXT_APPLICATION_ID | RACM | ||
| EXTRA1 | EXTRA1 | ||
| EXTRA2 | update | ||
| FULL_NAME | FULL_NAME | ||
| STATUS | Actif |
Création
Configurer une primitive ModuleCreateAccountFromAccountMng
- Table source
APPLICATION_ACCOUNT-> table cibleAPPLICATION_ACCOUNT_IMPORT ${ACCOUNT_NAME}sera résolu par la politique de création de compte.- EIN représentera le GIA_ID dans notre provisionnement.
- Si une politique de mot de passe a été créée.
- Un mot de passe sera généré même si le mapping n'est pas présent dans les mappings.
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | ${ACCOUNT_NAME} | X | |
| EIN | EIN | ||
| EXT_APPLICATION_ID | RACM | ||
| EXTRA1 | EXTRA1 | ||
| EXTRA2 | create | ||
| FULL_NAME | FULL_NAME | ||
| STATUS | Actif |
Terminer
Configurer une primitive ModuleModifyAccountFromAccountMng
- Matérialiser un
TERMINATE - Table source
APPLICATION_ACCOUNT-> table cibleAPPLICATION_ACCOUNT_IMPORT
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | ACCOUNT_NAME | X | |
| EXT_APPLICATION_ID | RACM | ||
| FULL_NAME | FULL_NAME | ||
| EXTRA2 | terminate | ||
| STATUS | Terminé |
Désactiver
Configurer une primitive ModuleModifyAccountFromAccountMng
- Matérialiser un
DEACTIVATE - Table source
APPLICATION_ACCOUNT-> table cibleAPPLICATION_ACCOUNT_IMPORT
| Colonne de la table cible | Colonne de la table source | Formatage (Constante) | Unique |
|---|---|---|---|
| ACCOUNT_NAME | ACCOUNT_NAME | X | |
| EIN | EIN | ||
| EXT_APPLICATION_ID | RACM | ||
| EXTRA1 | EXTRA1 | ||
| EXTRA2 | deactivate | ||
| FULL_NAME | FULL_NAME | ||
| STATUS | Désactivé |
Provisionnement
Les variables disponibles sont définies dans le schéma.
SYNC_STATUSsera mis à 1 quand le compte matérialisé sera provisionné.- L'attribut du système cible est défini dans le schéma de l'application cible.
Note
La variable action dans le script Groovy ne semble pas toujours être correctement initialisée. Une variable ACTION_PATCH pourrait être créée pour adresser le problème et transmettre les actions à traiter au script Groovy.
Provisionnement de compte
Configurer une primitive ModuleICFAccountProvisionning pour envoyer la matérialisation des comptes vers l'application cible.
- Table source
APPLICATION_ACCOUNT_IMPORT-> Connecteur ICF --> cible
| Colonne RACM | Attribut du système cible | Unique |
|---|---|---|
| ACCOUNT_NAME | LOGIN | X |
| EIN | GIA_ID | |
| EXTRA1 | LANGUAGE | |
| EXTRA2 | ACTION_PATCH | |
| FULL_NAME | FULL_NAME | |
| PASSWORD | PASSWORD | |
| STATUS | STATUS |
Provisionnement de groupe
Configurer une primitive ModuleICFProfileProvisionning pour envoyer la matérialisation des membres vers l'application cible.
- Table source
PROFILE_IMPORT-> Connecteur ICF -->_GROUP_
Note
Nous n'avons pas besoin de matérialiser les groupes car seulement les membres sont gérés par RAC/M.
| Colonne RACM | Attribut du système cible | Unique |
|---|---|---|
| ACCOUNT_NAME | LOGIN | X |
| EXT_PROFILE_ID | LOGIN_ROLE_ID |
Groovy
Un fichier Groovy sera appelé par le connecteur ICF lorsqu'il exécute une action spécifique définie dans la configuration du connecteur ICF.
Tous les mappings seront disponibles dans le script Groovy sous les noms définis dans le schéma Groovy.
Nous pourrons accéder à ces variables et exécuter une requête SQL qui sera envoyée à la base de données cible.
Search Script Filename
Lorsqu'on effectue une lecture pour récupérer les informations d'une table, ce script Groovy est exécuté.
import groovy.sql.Sql
import java.util.logging.Logger
Logger logger = Logger.getLogger("")
logger.info("Entering Search script")
def sql = new Sql(connection)
def result = []
switch (objectClass) {
case "__ACCOUNT__":
query = """
SELECT login_id, login, full_name, language, email from LOGIN
"""
logger.info("Executing query : " + query)
result.add(__UID__: it.USER_ID, __NAME__: it.USER_NAME, LOGIN: it.LOGIN, FULL_NAME: it.FULL_NAME,
LANGUAGE: it.LANGUAGE, EMAIL: it.EMAIL)
break
case "__GROUP__":
query = """
SELECT LOGIN_ROLE_ID, DESCRIPTION,LOGIN_ROLE_ID, DESCRIPTION from LOGIN_ROLE
"""
logger.info("Executing query : " + query)
sql.eachRow(query, { result.add(__UID__: it.ROLE_ID, __NAME__: it.ROLE_NAME, ROLE_ID: it.ROLE_ID, ROLE_NAME: it.ROLE_NAME) })
break
case "ROLE_MEMBERS":
query = """
SELECT login_id ,login, LOGIN_ROLE_ID from LOGIN
"""
logger.info("Executing query : " + query)
sql.eachRow(query, { result.add(__UID__: it.LOGIN_ID, __NAME__: it.LOGIN, LOGIN_ROLE_ID: it.LOGIN_ROLE_ID) })
break
default:
result
}
return resultUpdate Script Filename
When a change needs to be sent from RAC\M to the target, this script is executed.
It will check the action and action_patch variables to execute a specific section of the case (e.g., activate, delete, update, etc.).
In the connector configuration, it would have been possible to point to different files depending on the action performed by the ICF connector.
Note
La fonction hashPassword à la fin de ce script est volontairement laissée à l'état de pseudocode. Le stockage des mots de passe est propre à chaque application cible : implémentez-la avec le schéma exact utilisé par cette application pour valider les mots de passe, en vous appuyant sur une fonction de hachage de mot de passe lente et adaptative (Argon2id, scrypt, bcrypt ou PBKDF2-HMAC-SHA-256) plutôt que sur un simple condensat comme SHA-256 ou SHA-512.
package racm
import org.identityconnectors.common.logging.Log
import org.identityconnectors.framework.common.objects.Uid
import groovy.sql.Sql
import java.security.SecureRandom
import org.identityconnectors.common.security.SecurityUtil
import org.apache.commons.codec.binary.Base64
def log = log as Log
def LOGIN = attributes.get('LOGIN')
def LOGIN_ROLE_ID = attributes.get('LOGIN_ROLE_ID')
def FULL_NAME = attributes.get('FULL_NAME')
def PASSWORD = attributes.get('PASSWORD')
def LANGUAGE = attributes.get('LANGUAGE')
def EMAIL = attributes.get('EMAIL')
def GIA_ID = attributes.get('GIA_ID')
def STATUS = attributes.get('STATUS')
def ACTION_PATCH = attributes.get('ACTION_PATCH')
log.info("----- Update - login - Entering {0} script -----", action)
log.info("Entering {0} LOGIN", LOGIN)
log.info("Entering {0} LOGIN_ROLE_ID", LOGIN_ROLE_ID)
log.info("Entering {0} FULL_NAME", FULL_NAME)
log.info("Entering {0} LANGUAGE", LANGUAGE)
log.info("Entering {0} EMAIL", EMAIL)
log.info("Entering {0} GIA_ID", GIA_ID)
log.info("Entering {0} STATUS", STATUS)
log.info("Entering {0} ACTION_PATCH", ACTION_PATCH)
log.info("Entering {0} object class", objectClass)
def sql = new Sql(connection)
log.info("Connected to database, script update racm login")
if (toString(LOGIN).trim().equals('')) {
log.info("LOGIN ne peut pas être vide")
sql.close()
return new Uid("")
}
switch (objectClass) {
case "__GROUP__":
log.info("Update login group")
if ('ADD_ATTRIBUTE_VALUES'.equalsIgnoreCase(action)) {
log.info("Add group member LOGIN_ROLE")
// Toujours passer les valeurs en paramètres : les attributs d'identité sont
// des entrées non fiables et peuvent légitimement contenir une apostrophe
// (p. ex. O'Brien).
updated = sql.executeUpdate(
"UPDATE LOGIN SET LOGIN_ROLE_ID = ? WHERE LOGIN = ?",
[toString(LOGIN_ROLE_ID), toString(LOGIN)])
} else if ('REMOVE_ATTRIBUTE_VALUES'.equalsIgnoreCase(action)) {
log.info("Remove group member LOGIN_ROLE")
updated = sql.executeUpdate(
"UPDATE LOGIN SET LOGIN_ROLE_ID = 3 WHERE LOGIN = ?",
[toString(LOGIN)])
} else {
throw new Exception("Script not implemented for action: " + action + " LOGIN: " + toString(LOGIN_ROLE_ID).trim())
}
sql.commit()
sql.close()
return new Uid(LOGIN_ROLE_ID[0])
case "__ACCOUNT__":
log.info("Modify account")
if ('TERMINATE'.equalsIgnoreCase(action) || "terminate".equalsIgnoreCase(ACTION_PATCH)) {
log.info("Terminating login")
sql.executeUpdate("DELETE FROM LOGIN WHERE LOGIN = ?", [toString(LOGIN)])
} else if ('ACTIVATE'.equalsIgnoreCase(action) || "activate".equalsIgnoreCase(ACTION_PATCH)) {
log.info("Activate login")
def decryptPassword = SecurityUtil.decrypt(PASSWORD[0])
def salt = generateSalt(16) // Génère un sel aléatoire de 128 bits (16 octets)
def password = hashPassword(salt, decryptPassword)
// Ne journalisez pas cette requête ni ses paramètres : ils contiennent le sel
// et le hash du mot de passe, qui ne doivent pas se retrouver dans les logs.
def count = sql.executeUpdate("""
UPDATE LOGIN SET
PASSWORD_SALT = ?,
PASSWORD = ?,
FULL_NAME = ?,
GIA_ID = ?,
LANGUAGE = ?,
EMAIL = ?
WHERE LOGIN = ?
""", [salt, password, toString(FULL_NAME), toString(GIA_ID),
toString(LANGUAGE), toString(EMAIL), toString(LOGIN)])
if (count == 0) {
sql.executeUpdate("""
INSERT INTO [dbo].[LOGIN]
([LOGIN], [PASSWORD_SALT], [PASSWORD], [LOGIN_ROLE_ID], [FULL_NAME], [GIA_ID], [LANGUAGE], [EMAIL])
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", [toString(LOGIN), salt, password, 3, toString(FULL_NAME),
toString(GIA_ID), toString(LANGUAGE), toString(EMAIL)])
}
} else if ('UPDATE'.equalsIgnoreCase(action) || "update".equalsIgnoreCase(ACTION_PATCH)) {
log.info("Update login")
sql.executeUpdate("""
UPDATE LOGIN SET
FULL_NAME = ?,
GIA_ID = ?,
LANGUAGE = ?,
EMAIL = ?
WHERE LOGIN = ?
""", [toString(FULL_NAME), toString(GIA_ID), toString(LANGUAGE),
toString(EMAIL), toString(LOGIN)])
} else {
log.info("Updated login " + action)
}
sql.commit()
sql.close()
return new Uid(LOGIN[0])
default:
log.info("Script not implemented for {0}", objectClass)
break
}
/**
* Fonction pour générer un sel aléatoire de longueur donnée (ex: 16 octets pour 128 bits)
*/
def generateSalt(int length) {
SecureRandom random = new SecureRandom()
byte[] saltBytes = new byte[length]
random.nextBytes(saltBytes)
return Base64.encodeBase64String(saltBytes) // Encodé en Base64 pour stockage facile
}
/**
* Fonction pour hacher un mot de passe avec un sel donné.
*
* PSEUDOCODE : cet exemple ne fournit volontairement aucune implémentation de
* hachage, car l'algorithme doit correspondre à celui utilisé par l'application
* cible pour valider les mots de passe de la table LOGIN. Implémentez-la avec une
* fonction de hachage de mot de passe lente et adaptative (Argon2id, scrypt,
* bcrypt ou PBKDF2-HMAC-SHA-256) et un facteur de travail ajusté à votre matériel.
* N'utilisez jamais un simple condensat rapide (MD5, SHA-1, ou une seule passe de
* SHA-256/SHA-512) : même salés, ceux-ci se cassent à grande vitesse sur GPU si la
* table LOGIN venait à fuiter.
*/
def hashPassword(String salt, String password) {
// Appelez la bibliothèque de hachage de mots de passe fournie avec
// l'application cible, par exemple :
//
// byte[] hash = Argon2id(password, salt,
// memory: 64 MiB, iterations: 3, parallelism: 1, length: 32 bytes)
// -- ou --
// byte[] hash = PBKDF2_HMAC_SHA256(password, salt,
// iterations: 600000, length: 32 bytes)
//
// return Base64.encodeBase64String(hash) // Encodé en Base64 pour stockage
throw new UnsupportedOperationException(
"Implémentez hashPassword() avec la fonction de hachage adaptative attendue par l'application cible")
}Schema Script Filename
Définition du schéma du connecteur ICF par rapport au schéma de la base de données. Le mapping sera effectué dans chaque implémentation de script Groovy. Ce schéma sera utilisé également dans les interfaces de collecteurs.
package racm
import org.identityconnectors.framework.common.objects.AttributeInfo
import org.identityconnectors.framework.common.objects.AttributeInfoBuilder
import org.identityconnectors.framework.common.objects.ObjectClassInfo
import org.identityconnectors.framework.common.objects.ObjectClassInfoBuilder
import org.identityconnectors.common.security.GuardedString
import java.util.logging.Logger
Logger logger = Logger.getLogger("Initilazing logger")
// Declare the __ACCOUNT__ attributes
accAttrsInfo = new HashSet<AttributeInfo>()
accAttrsInfo.add(AttributeInfoBuilder.build("__UID__", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("__NAME__", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("LOGIN", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("LOGIN_ROLE_ID", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("FULL_NAME", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("LANGUAGE", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("PASSWORD", GuardedString.class))
accAttrsInfo.add(AttributeInfoBuilder.build("LANGUAGE", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("EMAIL", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("GIA_ID", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("STATUS", String.class))
accAttrsInfo.add(AttributeInfoBuilder.build("ACTION_PATCH", String.class))
// Create the __ACCOUNT__ Object class
final ObjectClassInfo ociAccount = new ObjectClassInfoBuilder().setType("__ACCOUNT__").addAllAttributeInfo(accAttrsInfo).build()
builder.defineObjectClass(ociAccount)
// Declare the __GROUP__ attributes
groupAttrsInfo = new HashSet<AttributeInfo>()
groupAttrsInfo.add(AttributeInfoBuilder.build("__UID__", String.class))
groupAttrsInfo.add(AttributeInfoBuilder.build("__NAME__", String.class))
groupAttrsInfo.add(AttributeInfoBuilder.build("LOGIN", String.class))
groupAttrsInfo.add(AttributeInfoBuilder.build("LOGIN_ROLE_ID", String.class))
// Create the __GROUP__ Object class
final ObjectClassInfo ociGroup = new ObjectClassInfoBuilder().setType("__GROUP__").addAllAttributeInfo(groupAttrsInfo).build()
builder.defineObjectClass(ociGroup)
// Declare the __ROLE_MEMBERS__ attributes
memberAttrsInfo = new HashSet<AttributeInfo>()
memberAttrsInfo.add(AttributeInfoBuilder.build("__UID__", String.class))
memberAttrsInfo.add(AttributeInfoBuilder.build("__NAME__", String.class))
memberAttrsInfo.add(AttributeInfoBuilder.build("LOGIN", String.class))
memberAttrsInfo.add(AttributeInfoBuilder.build("LOGIN_ROLE_ID", String.class))
// Create the __ROLE_MEMBERS__ Object class
final ObjectClassInfo ociMember = new ObjectClassInfoBuilder().setType("__ROLE_MEMBERS__").addAllAttributeInfo(memberAttrsInfo).build()
builder.defineObjectClass(ociMember)