01. Single datasource
Context
In this example, we have a unique Datasource (an Oracle database) that we’ll use to convert typical users, password, groups and group membership data to fill an LDAP server.
Oracle schema
classDiagram direction BT ORA_USERPASSWORDS <-- ORA_USERS ORA_GROUPSMEMBERS <-- ORA_USERS ORA_GROUPSMEMBERS <-- ORA_GROUPS class ORA_USERS{ USER_ID - NUMBER, NOT NULL LOGIN - VARCHAR2 FIRSTNAME - VARCHAR2 LASTNAME - VARCHAR2 EMAIL - VARCHAR2 } class ORA_USERPASSWORDS{ USER_ID - NUMBER, NOT NULL PASSWORD_ENCRYPTED - RAW LDAP_HASHES - VARCHAR2 } class ORA_GROUPS{ GROUP_ID - NUMBER, NOT NULL GROUP_NAME - VARCHAR2 GROUP_DESC - VARCHAR2 } class ORA_GROUPSMEMBERS{ USER_ID - NUMBER, NOT NULL GROUP_ID - NUMBER, NOT NULL }
hermes-server-config
hermes:
cache:
dirpath: /path/to/.hermes/hermes-server/cache
enable_compression: true
backup_count: 1
cli_socket:
path: /path/to/.hermes/hermes-server.sock # Facultative, required to use cli
owner: user_login # Facultative
group: group_name # Facultative
# Facultative, '0600' will be used by default.
# The value MUST be prefixed by a 0 to indicate that it's an octal integer
mode: 0660
logs:
logfile: /path/to/.hermes/hermes-server/logs/hermes-server.log
backup_count: 31 # 1 month
verbosity: info
mail:
server: dummy.example.com
from: Hermes Server <no-reply@example.com>
to:
- user@example.com
plugins:
# Attribute transform plugins (jinja filters)
attributes:
ldapPasswordHash:
settings:
default_hash_types:
- SMD5
- SSHA
- SSHA256
- SSHA512
crypto_RSA_OAEP:
settings:
keys:
decrypt_from_datasource:
hash: SHA256
# WARNING - THIS KEY IS WEAK AND PUBLIC, NEVER USE IT
rsa_key: |-
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEAstltWwDzmtSSHi7lfKqtUIO4dI8aX/EAopNdR/cWXH8CAwEAAQIh
AKfflFjGNOJQwvJX3Io+/juxO+HFd7SRC++zBD9paZqZAhEA5OtjZQUapRrV/aC5
NXFsswIRAMgBtgpz+t0FxyEXdzlcTwUCEHU6WZ8M2xU7xePpH49Ps2MCEQC+78s+
/WvfNtXcRI+gJfyVAhAjcIWzHC5q4wzgL7psbPGy
-----END RSA PRIVATE KEY-----
# SERVER ONLY - Sources used to fetch data. At lease one must be defined
datasources:
datasource_of_example1: # Source name. Use whatever you want. Will be used in datamodel
type: oracle # Source type. A datasource plugin with this name must exist
settings: # Settings of current source
login: HERMES_DUMMY
password: "DuMmY_p4s5w0rD"
port: 1234
server: dummy.example.com
sid: DUMMY
messagebus:
kafka:
settings:
servers:
- dummy.example.com:9093
ssl:
certfile: /path/to/.hermes/dummy.crt
keyfile: /path/to/.hermes/dummy.pem
cafile: /path/to/.hermes/INTERNAL-CA-chain.crt
topic: hermes
hermes-server:
updateInterval: 60 # Interval between two data update, in seconds
# The declaration order of data types is important:
# - add/modify events will be processed in the declaration order
# - remove events will be processed in the reversed declaration order
datamodel:
SRVGroups: # Settings for SRVGroups data type
primarykeyattr: srv_group_id # Attribute name that will be used as primary key
# Facultative template of object string representation that will be used in logs
toString: "<SRVGroups[{{ srv_group_id }}, {{ srv_group_name | default('#UNDEF#') }}]>"
sources: # datasource(s) to use to fetch data. Usually one, but several could be used
datasource_of_example1: # The source name set in hermes.plugins.datasources
# The query to fetch data.
# 'type' is mandatory and indicate to plugin which flavor of query to proceed
# Possible 'type' values are 'add', 'delete', 'fetch' and 'modify'
# 'query' is the query to send
# 'vars' is a dict with vars to use (and sanitize !) in query
#
# According to source type, 'query' and 'vars' may be facultative.
# A Jinja template can be inserted in 'query' and 'vars' values to avoid wildcards
# and manually typing the attribute list, or to filter the query using a cached value.
#
# Jinja vars available are [REMOTE_ATTRIBUTES, CACHED_VALUES].
# See documentation for details:
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.fetch
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_GROUPS
attrsmapping:
srv_group_id: GROUP_ID
srv_group_name: GROUP_NAME
srv_group_desc: GROUP_DESC
SRVUsers: # Settings for SRVUsers data type
primarykeyattr: srv_user_id # Attribute name that will be used as primary key
# Facultative template of object string representation that will be used in logs
toString: "<SRVUsers[{{ srv_user_id }}, {{ srv_login | default('#UNDEF#') }}]>"
sources: # datasource(s) to use to fetch data. Usually one, but several could be used
datasource_of_example1: # The source name set in hermes.plugins.datasources
# The query to fetch data.
# 'type' is mandatory and indicate to plugin which flavor of query to proceed
# Possible 'type' values are 'add', 'delete', 'fetch' and 'modify'
# 'query' is the query to send
# 'vars' is a dict with vars to use (and sanitize !) in query
#
# According to source type, 'query' and 'vars' may be facultative.
# A Jinja template can be inserted in 'query' and 'vars' values to avoid wildcards
# and manually typing the attribute list, or to filter the query using a cached value.
#
# Jinja vars available are [REMOTE_ATTRIBUTES, CACHED_VALUES].
# See documentation for details:
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.fetch
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_USERS
attrsmapping:
srv_user_id: USER_ID
srv_login: LOGIN
# Ensure first letter of each names is uppercase, and other are lowercase
srv_firstname: "{{ FIRSTNAME | title}}"
srv_lastname: "{{ LASTNAME | title}}"
srv_mail: MAIL
SRVUserPasswords: # Settings for SRVUserPasswords data type
primarykeyattr: srv_user_id # Attribute name that will be used as primary key
# Integrity constraints between datamodel type, in Jinja.
# WARNING: could be very slow, keep it as simple as possible, and focused upon
# primary keys
# Jinja vars available are '_SELF': the current object, and every types declared
# For each "typename" declared, two vars are available:
# - typename_pkeys: a set with every primary keys
# - typename: a list of dict containing each entries
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.integrity_constraints
integrity_constraints:
- "{{ _SELF.srv_user_id in SRVUsers_pkeys }}"
sources: # datasource(s) to use to fetch data. Usually one, but several could be used
datasource_of_example1: # The source name set in hermes.plugins.datasources
# The query to fetch data.
# 'type' is mandatory and indicate to plugin which flavor of query to proceed
# Possible 'type' values are 'add', 'delete', 'fetch' and 'modify'
# 'query' is the query to send
# 'vars' is a dict with vars to use (and sanitize !) in query
#
# According to source type, 'query' and 'vars' may be facultative.
# A Jinja template can be inserted in 'query' and 'vars' values to avoid wildcards
# and manually typing the attribute list, or to filter the query using a cached value.
#
# Jinja vars available are [REMOTE_ATTRIBUTES, CACHED_VALUES].
# See documentation for details:
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.fetch
fetch:
type: fetch
query: >-
SELECT p.{{ REMOTE_ATTRIBUTES | join(', p.') }}
FROM ORA_USERPASSWORDS p
# For each entry successfully processed, we'll remove PASSWORD_ENCRYPTED
# and store the freshly computed LDAP_HASHES.
#
# Facultative. The query to run each time an item of current data have been processed
# without errors.
# 'type' is mandatory and indicate to plugin which flavor of query to proceed
# Possible 'type' values are 'add', 'delete', 'fetch' and 'modify'
# 'query' is the query to send
# 'vars' is a dict with vars to use (and sanitize !) in query
#
# According to source type, 'query' and 'vars' may be facultative.
# A Jinja template can be inserted in 'query' and 'vars' values to avoid wildcards
# and manually typing the attribute list, or to filter the query using a cached value.
#
# Jinja vars available are [REMOTE_ATTRIBUTES, ITEM_CACHED_VALUES, ITEM_FETCHED_VALUES].
# See documentation for details:
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.commit_one
commit_one:
type: modify
query: >-
UPDATE ORA_USERPASSWORDS
SET
PASSWORD_ENCRYPTED = NULL,
LDAP_HASHES = :ldap_hashes
WHERE USER_ID = :user_id
vars:
user_id: "{{ ITEM_FETCHED_VALUES.srv_user_id }}"
ldap_hashes: "{{ ';'.join(ITEM_FETCHED_VALUES.srv_password_ldap) }}"
attrsmapping:
srv_user_id: USER_ID
# Decipher PASSWORD_ENCRYPTED value to generate the LDAP hashes.
srv_password_ldap: >-
{{
(
PASSWORD_ENCRYPTED
| crypto_RSA_OAEP('decrypt_from_datasource')
| ldapPasswordHash
)
| default(None if LDAP_HASHES is None else LDAP_HASHES.split(';'))
}}
SRVGroupsMembers:
# Attribute names that will be used as primary key: here is is a tuple
primarykeyattr: [srv_group_id, srv_user_id]
# Foreign keys declaration between data types
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.foreignkeys
foreignkeys:
srv_group_id:
from_objtype: SRVGroups
from_attr: srv_group_id
srv_user_id:
from_objtype: SRVUsers
from_attr: srv_user_id
# Integrity constraints between datamodel type, in Jinja.
# WARNING: could be very slow, keep it as simple as possible, and focused upon
# primary keys
# Jinja vars available are '_SELF': the current object, and every types declared
# For each "typename" declared, two vars are available:
# - typename_pkeys: a set with every primary keys
# - typename: a list of dict containing each entries
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.integrity_constraints
integrity_constraints:
- "{{ _SELF.srv_user_id in SRVUsers_pkeys and _SELF.srv_group_id in SRVGroups_pkeys }}"
sources: # datasource(s) to use to fetch data. Usually one, but several could be used
datasource_of_example1: # The source name set in hermes.plugins.datasources
# The query to fetch data.
# 'type' is mandatory and indicate to plugin which flavor of query to proceed
# Possible 'type' values are 'add', 'delete', 'fetch' and 'modify'
# 'query' is the query to send
# 'vars' is a dict with vars to use (and sanitize !) in query
#
# According to source type, 'query' and 'vars' may be facultative.
# A Jinja template can be inserted in 'query' and 'vars' values to avoid wildcards
# and manually typing the attribute list, or to filter the query using a cached value.
#
# Jinja vars available are [REMOTE_ATTRIBUTES, CACHED_VALUES].
# See documentation for details:
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.fetch
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_GROUPSMEMBERS
attrsmapping:
srv_user_id: USER_ID
srv_group_id: GROUP_ID
hermes:
cache:
dirpath: /path/to/.hermes/hermes-server/cache
cli_socket:
path: /path/to/.hermes/hermes-server.sock
logs:
logfile: /path/to/.hermes/hermes-server/logs/hermes-server.log
verbosity: info
mail:
server: dummy.example.com
from: Hermes Server <no-reply@example.com>
to:
- user@example.com
plugins:
attributes:
ldapPasswordHash:
settings:
default_hash_types:
- SMD5
- SSHA
- SSHA256
- SSHA512
crypto_RSA_OAEP:
settings:
keys:
decrypt_from_datasource:
hash: SHA256
# WARNING - THIS KEY IS WEAK AND PUBLIC, NEVER USE IT
rsa_key: |-
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEAstltWwDzmtSSHi7lfKqtUIO4dI8aX/EAopNdR/cWXH8CAwEAAQIh
AKfflFjGNOJQwvJX3Io+/juxO+HFd7SRC++zBD9paZqZAhEA5OtjZQUapRrV/aC5
NXFsswIRAMgBtgpz+t0FxyEXdzlcTwUCEHU6WZ8M2xU7xePpH49Ps2MCEQC+78s+
/WvfNtXcRI+gJfyVAhAjcIWzHC5q4wzgL7psbPGy
-----END RSA PRIVATE KEY-----
datasources:
datasource_of_example1:
type: oracle
settings:
login: HERMES_DUMMY
password: "DuMmY_p4s5w0rD"
port: 1234
server: dummy.example.com
sid: DUMMY
messagebus:
kafka:
settings:
servers:
- dummy.example.com:9093
ssl:
certfile: /path/to/.hermes/dummy.crt
keyfile: /path/to/.hermes/dummy.pem
cafile: /path/to/.hermes/INTERNAL-CA-chain.crt
topic: hermes
hermes-server:
# The declaration order of data types is important:
# - add/modify events will be processed in the declaration order
# - remove events will be processed in the reversed declaration order
datamodel:
SRVGroups:
primarykeyattr: srv_group_id
toString: "<SRVGroups[{{ srv_group_id }}, {{ srv_group_name | default('#UNDEF#') }}]>"
sources:
datasource_of_example1:
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_GROUPS
attrsmapping:
srv_group_id: GROUP_ID
srv_group_name: GROUP_NAME
srv_group_desc: GROUP_DESC
SRVUsers:
primarykeyattr: srv_user_id
toString: "<SRVUsers[{{ srv_user_id }}, {{ srv_login | default('#UNDEF#') }}]>"
sources:
datasource_of_example1:
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_USERS
attrsmapping:
srv_user_id: USER_ID
srv_login: LOGIN
# Ensure first letter of each names is uppercase, and other are lowercase
srv_firstname: "{{ FIRSTNAME | title}}"
srv_lastname: "{{ LASTNAME | title}}"
srv_mail: MAIL
SRVUserPasswords:
primarykeyattr: srv_user_id
# Integrity constraints between datamodel type, in Jinja.
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.integrity_constraints
integrity_constraints:
- "{{ _SELF.srv_user_id in SRVUsers_pkeys }}"
sources:
datasource_of_example1:
fetch:
type: fetch
query: >-
SELECT p.{{ REMOTE_ATTRIBUTES | join(', p.') }}
FROM ORA_USERPASSWORDS p
# For each entry successfully processed, we'll remove PASSWORD_ENCRYPTED
# and store the freshly computed LDAP_HASHES.
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.sources.datasource-name.commit_one
commit_one:
type: modify
query: >-
UPDATE ORA_USERPASSWORDS
SET
PASSWORD_ENCRYPTED = NULL,
LDAP_HASHES = :ldap_hashes
WHERE USER_ID = :user_id
vars:
user_id: "{{ ITEM_FETCHED_VALUES.srv_user_id }}"
ldap_hashes: "{{ ';'.join(ITEM_FETCHED_VALUES.srv_password_ldap) }}"
attrsmapping:
srv_user_id: USER_ID
# Decipher PASSWORD_ENCRYPTED value to generate the LDAP hashes.
srv_password_ldap: >-
{{
(
PASSWORD_ENCRYPTED
| crypto_RSA_OAEP('decrypt_from_datasource')
| ldapPasswordHash
)
| default(None if LDAP_HASHES is None else LDAP_HASHES.split(';'))
}}
SRVGroupsMembers:
# The primary key is a tuple
primarykeyattr: [srv_group_id, srv_user_id]
foreignkeys:
srv_group_id:
from_objtype: SRVGroups
from_attr: srv_group_id
srv_user_id:
from_objtype: SRVUsers
from_attr: srv_user_id
# Integrity constraints between datamodel type, in Jinja.
# https://hermes.insa-strasbourg.fr/en/setup/configuration/hermes-server/#hermes-server.datamodel.data-type-name.integrity_constraints
integrity_constraints:
- "{{ _SELF.srv_user_id in SRVUsers_pkeys and _SELF.srv_group_id in SRVGroups_pkeys }}"
sources:
datasource_of_example1:
fetch:
type: fetch
query: >-
SELECT {{ REMOTE_ATTRIBUTES | join(', ') }}
FROM ORA_GROUPSMEMBERS
attrsmapping:
srv_user_id: USER_ID
srv_group_id: GROUP_ID
hermes-client-usersgroups_ldap-config
hermes:
cache:
dirpath: /path/to/.hermes/hermes-client-usersgroups_ldap/cache
cli_socket:
path: /path/to/.hermes/hermes-client-usersgroups_ldap.sock
logs:
logfile: /path/to/.hermes/hermes-client-usersgroups_ldap/logs/hermes-client-usersgroups_ldap.log
verbosity: info
mail:
server: dummy.example.com
from: hermes-client-usersgroups_ldap <no-reply@example.com>
to:
- user@example.com
plugins:
messagebus:
kafka:
settings:
servers:
- dummy.example.com:9093
ssl:
certfile: /path/to/.hermes/dummy.crt
keyfile: /path/to/.hermes/dummy.pem
cafile: /path/to/.hermes/INTERNAL-CA-chain.crt
topic: hermes
group_id: hermes-grp
hermes-client-usersgroups_ldap:
uri: ldaps://ldap.example.com:636
binddn: cn=account,dc=example,dc=com
bindpassword: s3cReT_p4s5w0rD
basedn: dc=example,dc=com
users_ou: ou=users,dc=example,dc=com
groups_ou: ou=groups,dc=example,dc=com
# MANDATORY: Name of DN attribute for Users, UserPasswords and Groups
# You have to set up values for the three, even if you don't use some of the types
dnAttributes:
Users: uid
UserPasswords: uid
Groups: cn
propagateUserDNChangeOnGroupMember: true
groupsObjectclass: groupOfNames
# It is possible to set a default value for some attributes for Users,
# UserPasswords and Groups. The default value will be set on added and modified
# events if the local attribute has no value
defaultValues:
# Hack to allow creation of an empty group, because of the "MUST member" in schema
Groups:
member: ""
# The local attributes listed here won't be stored in LDAP for Users,
# UserPasswords and Groups
attributesToIgnore:
Users:
- user_pkey
UserPasswords:
- user_pkey
Groups:
- group_pkey
hermes-client:
# Autoremediation policy to use in error queue for events concerning a same object
# - "disabled" : no autoremediation, events are stacked as is (default)
# - "conservative" :
# - merge an added event with a following modified event
# - merge two successive modified events
# - "maximum" :
# - merge an added event with a following modified event
# - merge two successive modified events
# - delete both events when an added event is followed by a removed event
# - merge a removed event followed by an added event in a modified event
# - delete a modified event when it is followed by a removed event
autoremediation: conservative
datamodel:
Users:
hermesType: SRVUsers
# Facultative template of object string representation that will be used in logs
toString: "<Users[{{ user_pkey }}, {{ uid | default('#UNDEF#') }}]>"
attrsmapping:
user_pkey: srv_user_id
uid: srv_login
givenname: srv_firstname
sn: srv_lastname
mail: srv_mail
# Compose the displayname with two other attributes
displayname: "{{ srv_firstname ~ ' ' ~ srv_lastname }}"
#
# Static values
# Defining them here instead of in default values will allow changes
# propagation on each entry
#
objectclass: "{{ ['person', 'inetOrgPerson', 'eduPerson'] }}"
UserPasswords:
hermesType: SRVUserPasswords
attrsmapping:
user_pkey: srv_user_id
userPassword: srv_password_ldap
Groups:
hermesType: SRVGroups
toString: "<Groups[{{ group_pkey }}, {{ cn | default('#UNDEF#') }}]>"
attrsmapping:
group_pkey: srv_group_id
cn: srv_group_name
description: srv_group_desc
#
# Static values
# Defining them here instead of in default values will allow changes
# propagation on each entry
#
objectclass: "{{ ['groupOfNames'] }}"
GroupsMembers:
hermesType: SRVGroupsMembers
attrsmapping:
# 'user_pkey' and 'group_pkey' keys can't be renamed
user_pkey: srv_user_id
group_pkey: srv_group_id
Attributes flow
flowchart LR subgraph Oracle direction LR ORA_GROUPS ORA_USERS ORA_USERPASSWORDS ORA_GROUPSMEMBERS end subgraph ORA_GROUPS direction LR ORA_GROUPS_GROUP_ID["GROUP_ID"] ORA_GROUPS_GROUP_NAME["GROUP_NAME"] ORA_GROUPS_GROUP_DESC["GROUP_DESC"] end subgraph ORA_USERS direction LR ORA_USERS_USER_ID["USER_ID"] ORA_USERS_LOGIN["LOGIN"] ORA_USERS_FIRSTNAME["FIRSTNAME"] ORA_USERS_LASTNAME["LASTNAME"] ORA_USERS_EMAIL["EMAIL"] end subgraph ORA_USERPASSWORDS direction LR ORA_USERPASSWORDS_USER_ID["USER_ID"] ORA_USERPASSWORDS_PASSWORD_ENCRYPTED["PASSWORD_ENCRYPTED"] ORA_USERPASSWORDS_LDAP_HASHES["LDAP_HASHES"] end subgraph ORA_GROUPSMEMBERS direction LR ORA_GROUPSMEMBERS_USER_ID["USER_ID"] ORA_GROUPSMEMBERS_GROUP_ID["GROUP_ID"] end subgraph hermes-server direction LR SRVGroups SRVUsers SRVUserPasswords SRVGroupsMembers end subgraph SRVGroups direction LR SRVGroups_srv_group_id["srv_group_id"] SRVGroups_srv_group_name["srv_group_name"] SRVGroups_srv_group_desc["srv_group_desc"] end ORA_GROUPS_GROUP_ID --> SRVGroups_srv_group_id ORA_GROUPS_GROUP_NAME --> SRVGroups_srv_group_name ORA_GROUPS_GROUP_DESC --> SRVGroups_srv_group_desc subgraph SRVUsers direction LR SRVUsers_srv_user_id["srv_user_id"] SRVUsers_srv_login["srv_login"] SRVUsers_srv_firstname["srv_firstname"] SRVUsers_srv_lastname["srv_lastname"] SRVUsers_srv_mail["srv_mail"] end ORA_USERS_USER_ID --> SRVUsers_srv_user_id ORA_USERS_LOGIN --> SRVUsers_srv_login ORA_USERS_FIRSTNAME -->|'title' Jinja filter| SRVUsers_srv_firstname ORA_USERS_LASTNAME -->|'title' Jinja filter| SRVUsers_srv_lastname ORA_USERS_EMAIL --> SRVUsers_srv_mail subgraph SRVUserPasswords direction LR SRVUserPasswords_srv_user_id["srv_user_id"] SRVUserPasswords_srv_password_ldap["srv_password_ldap"] end ORA_USERPASSWORDS_USER_ID --> SRVUserPasswords_srv_user_id ORA_USERPASSWORDS_PASSWORD_ENCRYPTED -->|"'crypto_RSA_OAEP | ldapPasswordHash' Jinja filter"| SRVUserPasswords_srv_password_ldap ORA_USERPASSWORDS_LDAP_HASHES <-->|LDAP_HASHED is filled by, or provide its value| SRVUserPasswords_srv_password_ldap subgraph SRVGroupsMembers direction LR SRVGroupsMembers_srv_user_id["srv_user_id"] SRVGroupsMembers_srv_group_id["srv_group_id"] end ORA_GROUPSMEMBERS_USER_ID --> SRVGroupsMembers_srv_user_id ORA_GROUPSMEMBERS_GROUP_ID --> SRVGroupsMembers_srv_group_id subgraph hermes-client-usersgroups_ldap direction LR ClientGroups ClientUsers ClientUserPasswords ClientGroupsMembers end subgraph ClientGroups direction LR ClientGroups_group_pkey["group_pkey"] ClientGroups_cn["cn"] ClientGroups_description["description"] ClientGroups_objectclass["objectclass"] end SRVGroups_srv_group_id --> ClientGroups_group_pkey SRVGroups_srv_group_name --> ClientGroups_cn SRVGroups_srv_group_desc --> ClientGroups_description subgraph ClientUsers direction LR ClientUsers_user_pkey["user_pkey"] ClientUsers_uid["uid"] ClientUsers_givenname["givenname"] ClientUsers_sn["sn"] ClientUsers_mail["mail"] ClientUsers_displayname["displayname"] ClientUsers_objectclass["objectclass"] end SRVUsers_srv_user_id --> ClientUsers_user_pkey SRVUsers_srv_login --> ClientUsers_uid SRVUsers_srv_firstname --> ClientUsers_givenname SRVUsers_srv_firstname --> ClientUsers_displayname SRVUsers_srv_lastname --> ClientUsers_displayname SRVUsers_srv_lastname --> ClientUsers_sn SRVUsers_srv_mail --> ClientUsers_mail subgraph ClientUserPasswords direction LR ClientUserPasswords_user_pkey["user_pkey"] ClientUserPasswords_userPassword["userPassword"] end SRVUserPasswords_srv_user_id --> ClientUserPasswords_user_pkey SRVUserPasswords_srv_password_ldap --> ClientUserPasswords_userPassword subgraph ClientGroupsMembers direction LR ClientGroupsMembers_user_pkey["user_pkey"] ClientGroupsMembers_group_pkey["group_pkey"] end SRVGroupsMembers_srv_user_id --> ClientGroupsMembers_user_pkey SRVGroupsMembers_srv_group_id --> ClientGroupsMembers_group_pkey subgraph LDAP direction LR LDAPGroups LDAPUsers end subgraph LDAPGroups direction LR LDAPGroups_cn["cn"] LDAPGroups_description["description"] LDAPGroups_objectclass["objectclass"] LDAPGroups_member["member"] end ClientGroups_cn --> LDAPGroups_cn ClientGroups_description --> LDAPGroups_description ClientGroups_objectclass --> LDAPGroups_objectclass ClientGroupsMembers_user_pkey -->|converted to user DN| LDAPGroups_member ClientGroupsMembers_group_pkey -->|converted to group DN| LDAPGroups_member subgraph LDAPUsers direction LR LDAPUsers_uid["uid"] LDAPUsers_givenname["givenname"] LDAPUsers_displayname["displayname"] LDAPUsers_displayname["displayname"] LDAPUsers_sn["sn"] LDAPUsers_mail["mail"] LDAPUsers_objectclass["objectclass"] LDAPUsers_userPassword["userPassword"] end ClientUsers_uid --> LDAPUsers_uid ClientUsers_givenname --> LDAPUsers_givenname ClientUsers_displayname --> LDAPUsers_displayname ClientUsers_sn --> LDAPUsers_sn ClientUsers_mail --> LDAPUsers_mail ClientUsers_objectclass --> LDAPUsers_objectclass ClientUserPasswords_userPassword --> LDAPUsers_userPassword classDef global fill:#fafafa,stroke-dasharray: 5 5 class Oracle,hermes-server,hermes-client-usersgroups_ldap,LDAP global