Contacts Provider LDAP deprecated
This article describes how to setup and configure a contact provider plugin connecting to a directory server via LDAP (Lightweight Directory Access Protocol). This integration option supersedes the previously available LDAP Contact Storage.
Overview
In enterprise installations, there is often a central directory service that hosts the user data or other addressbook related entries. The Open-Xchange Server can be configured to access such data available from LDAP directories and integrate it in terms of address book folders in the groupware. For end-users and from a client's perspective, there's basically no difference to other contact folders, so that they can access the contents from the LDAP directory in a transparent way, e.g. when looking up participants for an appointment or choosing recipients for an e-mail message.
Internally, one or more LDAP search filters will represent read-only address books in the public folders tree. Entries matching these filters are then converted to OX contacts and distribution lists. All operations in these folders are passed through to the directory server directly, i.e. there is no synchronization to the local database storage in the background.
Requirements
- Open-Xchange Server v7.10.6 and above (
open-xchange-core,open-xchange-contacts-provider-ldap) - An LDAP compatible directory server (e.g. OpenLDAP, Microsoft Active Directory)
Configuration
The following steps provide a walkthrough to configure access to the LDAP directory. All configuration files and properties are reloadable, so that no additional server restart is required after the plugin's package was installed.
Required Bundles
If not yet done, enable the following additional package on the server: open-xchange-contacts-provider-ldap. Packages can be enabled in your chart's values.yaml:
core-mw:
packages:
status:
open-xchange-contacts-provider-ldap: enabled
LDAP Client Configuration
All connection-related settings are made through the common LDAP client configuration. Therefore, a section within ldap-client-config.yml needs to be defined first. See LDAP Client Configuration for further details.
Configure Mappings
Besides the LDAP client configuration, another prerequisite are the attribute mappings to define how LDAP entries can be converted to groupware contacts and distribution lists. After installation, mapping templates for typical OpenLDAP and Active Directory Servers can be found in our core repository. It's recommended to copy over this template file to /opt/open-xchange/etc/contacts-provider-ldap-mappings.yml and adjust the mappings as needed. Each configured set of mappings can be used for an LDAP contact provider (as defined later through separate file contacts-provider-ldap.yml), by using the corresponding identifier used in this .yml file.
Generally, contact properties are set based on an entry's value of the mapped LDAP attribute name. Empty mappings are ignored. It's possible to define a second LDAP attribute name for a property that is used as fall-back if the first one is empty in an LDAP result, e.g. to define multiple attributes for a display name, or to have multiple mappings for contacts and distribution lists. Therefore, the second attribute name can be appended as comma-separated value.
For the data-types, each LDAP attribute value is converted/parsed to the type necessary on the server (Strings, Numbers, Booleans). Dates are assumed to be in UTC and parsed using the pattern yyyyMMddHHmmss. Binary properties may be indicated by appending ;binary to the LDAP attribute name, and as special variant, Microsoft GUIDs can be decorated with the ;guid flag for proper conversion. Boolean properties may also be set based on a comparison with the LDAP attribute value, which is defined by the syntax [LDAP_ATTRIBUTE_NAME]=[EXPECTED_VALUE], e.g. to set the mark_as_distribution_list property based on a specific objectClass value. Alternatively, a Boolean value may also be assigned based on the the existence of any attribute value using *.
The mapping for the objectid property is mandatory, and must refer to an attribute that uniquely identifies each entry on the LDAP server - a contacts provider that references a set of mappings without it is rejected during initialization with a corresponding configuration error. Recommended choices are the operational attribute entryUUID for OpenLDAP and objectGUID;guid for Active Directory, as both are guaranteed to be unique and remain stable even when entries are renamed or moved. Alternatively, entryDN respectively distinguishedName may be used if DN-based identifiers are preferred. Note that changing the object id mapping of an existing provider also changes the identifiers of the served contacts, so that clients synchronizing affected address books via CardDAV will perform a one-time full re-synchronization.
Define Contacts Provider
In order to make LDAP address books available for users, the contacts provider needs to be defined and registered in the system. Therefore, a new section in contacts-provider-ldap.yml needs to be inserted, whose key becomes the identifier of the contacts provider. When starting from scratch, it is recommended to copy the template file, which can be found in our core repository.
In the general section, besides a name, values for ldapClientId and mappings need to be provided. The ldapClientId references the corresponding section in ldap-client-config.yml, while the mappings point to the set of mappings defined at contacts-provider-ldap-mappings.yml.
Then, in the folders section, the configuration of the available address book folders can be made. For this purpose, three different modes are available. Also, it is possible to configure how the folders of the contacts provider behave regarding appearance in the folder tree and contact picker dialogs, as well as towards synchronization. See the examples in the template file for further details.
Static
In static folder mode, a fixed list of folder definitions is used, each one with its own contact filter and name (the names must be unique). Additionally, a commonContactFilter needs to be defined, which is used for operations that are not bound to a specific folder, like lookups across all visible folders. The filter's search scopes relative to the LDAP client's 'baseDN' can be configured as one (only immediate subordinates) or sub (base entry itself and any subordinate entries to any depth), and all default to sub unless specified otherwise.
Dynamic Attributes
With mode dynamicAttributes, the distinct values of one attribute are discovered periodically and each value becomes a folder. The list of values is fetched by querying all entries that match the contactFilterTemplate (with the wildcard * as value) within the contactSearchScope (one/sub); the folders are then derived from all distinct values found in attributeName, using the value as name. The refreshInterval (e.g. 1h, 30m, 1D) determines how often this list - and any folder display names looked up via LDAP (see below) - is refreshed. The optional sortOrder (ascending/descending) sorts the resulting folders lexicographically.
Folder display names
By default, the distinguishing attribute value is used as the folder's display name. Optionally, a more readable name can be derived in up to two further steps:
folderNamePattern: A regular expression applied to the attribute value. If it matches, the name is taken from the first capturing group (or the whole match if the expression defines no group); otherwise the raw value is used unchanged.folderNameFilterTemplate(together withfolderNameSearchScopeandfolderNameAttributeName): The name resulting from the previous step is re-injected via the[value]placeholder into a second LDAP query, and the folder name is taken fromfolderNameAttributeNameof the first matching entry. Looked-up names are cached and refreshed along with therefreshInterval.
Per-user filters
Besides the distinguishing [value] placeholder, the contactFilterTemplate may contain user- or session-specific placeholders that are resolved dynamically from the requesting user's session before the query is passed to LDAP. This makes it possible to model different views on the same directory - for example, letting a user only see the departments they are a member of (typically in combination with a multi-valued LDAP attribute). The following placeholders are available:
[user.mail]: The user's primary email address[user.loginInfo]: Login info associated with the user[user.imapLogin]: The provisioned IMAP login of the user[user.id]: The identifier of the user[context.id]: The context identifier[context.name]: The context name[session.login]: The full login string used during login[session.oidc.<claim>]: An arbitrary claim from the session's OpenID Connect ID token[session.<parameterName>]: The value of a generic session parameter
When at least one such placeholder is present, folder discovery is performed per user (using the resolved values), so the visible folder list varies from user to user. For operations that span multiple folders (such as a search across all visible folders), the individual folder filters are combined with a logical OR to form a per-user commonContactFilter. If a placeholder cannot be resolved for the current session (e.g. a missing OpenID Connect claim or session parameter), it is substituted with an empty value, which generally results in no matches for that user.
These placeholders gate folder visibility only: they are evaluated while discovering which distinct attribute values (and thus folders) a user may see. When the contents of a visible folder are listed or searched, the placeholders are replaced by wildcards, so all entries carrying that folder's distinguishing value are returned, regardless of the user's individual values. The restriction therefore applies at folder granularity rather than per individual contact - both with and without the local cache. Use per-user placeholders to partition the directory into per-user sets of folders, not to hide individual contacts within an otherwise shared folder.
Per-user filters are also independent of per-user authentication: they only vary the search filter while still using the provider's shared bind, so they can be combined with the local cache. See the Caching section below for details.
When no per-user placeholders are used (and a shared bind is configured), folder discovery is global and shared across all users. With per-user authentication (an individual LDAP bind per user) the discovery is necessarily performed per user and the cache cannot be used, so this mode is not recommended in that setup.
For a complete, worked configuration of a per-user filter, see Per-user filters: teachers and schools in the Examples section.
Fixed Attributes
With mode fixedAttributes, all entries matching a filter and having an attribute set to one of the defined values do form a folder. Works similar to dynamic attributes, but with a static list of possible values, and can also be used with individual / per-user authentication. All items defined in the attributeValues array are used as folder (with the value as name). When listing the contents of a specific folder, this folder's specific attribute value is inserted in the configured contactFilterTemplate, using the contactSearchScope (one/sub).
If the useCache option is enabled, data for the cache is retrieved periodically using the filter template with wildcard * as value (implicitly serving as global commonContactFilter). The individual folder view on the contacts is applied dynamically upon usage within the application.
Enable Contacts Provider
After all necessary things are configured for the contacts provider, it can be enabled for users using its identifier (as used as key of the provider definition in contact-provider-ldap.yml). Therefore, the property com.openexchange.contacts.ldap.accounts can be used, which accepts a comma-separated list of contacts provider identifiers. This property can be defined in the most flexible way through the Config Cascade, so that different sets of users can be granted access to providers as needed.
Upgrading from Contact Storage LDAP
The previously available plugin open-xchange-contact-storage-ldap will continue to work temporary in v7.10.6 but is removed with v8.0.0. Therefore, it is recommended to upgrade to the new contacts provider plugin.
Compared with the legacy contact storage plugin, the new provider does integrate in a native way where the addressbook folders are directly provided by the plugin itself, i.e. there is no longer a regular database folder underneath where just the actual contents are delivered by the storage plugin. Therefore, it is no longer bound to a certain database folder that is configured. Furthermore, a defined storage is also not bound to a specific context anymore, so that the same contacts provider configuration can be shared across multiple contexts of the installation (assignable to users through the config cascade). Therefore, these kind of configuration settings are no longer required. Also, the previously required ID mapping is no longer needed, as textual identifiers can be used as-is in the meantime.
Besides that, the new plugin requires mostly the same configuration settings, however, the format was adjusted from plain .properties files to structured .yml files. So, transferring a previous configuration to the new format involves the following steps:
- Configure Connection Settings
The connection-related settings (like LDAP server URI or Base DN), as well as the authentication configuration need to be done in the common LDAP client fileldap-client-config.yml. - Define Mappings
The previously used mappings (in.propertiesformat) need to be transferred into the corresponding definitions in the configuration filecontacts-provider-ldap-mappings.yml. - Prepare Provider Configuration
A new provider configuration can then be inserted in the filecontacts-provider-ldap.yml(or copied over from the corresponding template). There, themappingsandldapClientIdconfigurations need to be linked. Then, all other required settings can be taken over (like cache configuration etc.). - Setup Folder(s)
Now, one or more address book folder(s) can be specified. A previously configured filter fromcom.openexchange.contact.storage.ldap.searchfiltercan be directly transferred to a single folder definition instaticmode. Alternatively, one can configure additional folders in the provider, too. - Enable Contacts Provider
As last step, the contacts provider can be actually enabled for a specific group of users, by specifying the provider's identifier (the key used in the YAML file) in the config-cascade enabled propertycom.openexchange.contacts.provider.ldap. - Remove Legacy Folder
The previous storage integration worked on top of an ordinary public folder that is stored in the database. After the legacy configuration fromopen-xchange-contact-storage-ldapis no longer active, a corresponding folder remnant can be removed manually by logging in as context admin into App Suite and deleting it from there if it is no longer needed.
Misc
Extended Folder Settings
The following extended settings are available to control the appearance of the address book folders in App Suite. For each of the settings, a default value can be specified, and additionally a protected flag that prevents changing the actual value through the client APIs. The settings can be defined in the folders section of the configuration file contacts-provider-ldap.yml, see the provided template for further details.
usedForSync
Configures if the addressbook folders can be synchronized to external clients via CardDAV or not. If set to false, the folders are only available in the web client. If set to true, folders can be activated for synchronization. Should only be enabled if attribute mappings for the changing_date and uid contact properties are available, and the LDAP server supports the special "LDAP Show Deleted Control" to query tombstone entries via isDeleted=TRUE.
usedInPicker
Defines whether addressbook folders will be available in the contact picker dialog of App Suite. If enabled, contacts from this provider can be looked up through this dialog, otherwise they are hidden.
shownInTree
Defines whether addressbook folders will be shown as 'subscribed' folders in the tree or not. If enabled, the folders will appear in the contacts module of App Suite as regular, subscribed folder. Otherwise, they're treated as hidden, unsubscribed folders.
Read-only
The LDAP contact storage works in read-only mode, meaning that any attempts to create new, delete, or modify existing contacts in the folder are rejected.
Incremental Synchronization
Different external Clients accessing the server via CardDAV have the requirement that the server is able to report all changes in a contact folder since the last synchronization. This includes updates to existing contacts, as well as deleted and newly created contacts. In order to use folders backed by the LDAP contact storage with such clients, the LDAP directory needs to be able to deliver these information, especially the so-called tombstones for deleted contacts, i.e. the knowledge about deleted directory entries, which is currently only available in Active Directory and must be explicitly turned on via isDeletedSupport in the provider configuration.
Furthermore, a valid attribute mapping for "last modified" must be provided.
Caching
To speed-up access to the LDAP directory, some contact properties can be held in a local cache. If enabled, a certain set of contact properties is kept in memory, and all client operations accessing the contacts will use the cached data preferably to speed up access. As a trade-off, a corresponding amount of memory is consumed locally by the middleware process on each node, and potentially stale data is used until the next refresh.
The cache is a single, provider-wide snapshot that is shared by all users. It is populated using the configured filter(s) with the wildcard * substituted for the distinguishing attribute value and for any user-/session-specific placeholders, i.e. from a global perspective across all folders. This has two consequences:
- The cache cannot be used together with per-user authentication (an individual LDAP bind per user). In that case it is automatically bypassed and every request is served directly from LDAP.
- It can be used together with per-user filters (see Dynamic Attributes), as these keep using the provider's shared bind. The user's resolved filter still selects which of the globally discovered folders are visible to them; within a visible folder all cached contacts are returned. (As noted under Dynamic Attributes, per-user filters restrict folder visibility rather than individual contacts in any case - this is not specific to the cache.)
To enable caching just add a cache element to your contact provider configuration which contains a useCache field set to true. Additionally you can also configure the fields to cache by adding the cachedFields field with a comma separated list of contact fields; if omitted, a sensible default set of fields is cached.
You can also configure the cache expire time by configuring the com.openexchange.contacts.ldap.cache.expire property. This way the cache will be regularly refreshed with new data and it will also be removed from memory in case it is not needed.
Internal Users / Global Addressbook
Using the corresponding mappings for the contact properties internal_userid and contextid, it is also possible to make contact data from internal users provisioned to the system available in LDAP address books, and let them be recognized as such by App Suite. The mapping can either be performed directly when pointing to properties yielding the numerical identifiers, or by using attributes that hold the corresponding login information (username / contextname), which are then resolved dynamically by the middleware, which can be indicated by the ;logininfo flag in the property mappings. Please see the explanations in file contacts-provider-ldap-mappings-template.yml, which can be found in our core repository for further details.
Direct Mapping of User- / Context-ID:
Given that the App Suite context identifier is stored within an Integer attribute named oxContextId, and the App Suite user identifier in an attribute named oxUserId in the directory server, an appropriate mapping would look like the following:
internal_userid : oxUserId
contextid : oxContextId
Indirect Mapping of User- / Contextname :
If no numerical identifiers are available as LDAP attributes, but the context name as provisioned in App Suite is stored within an attribute named oxContextName, and the provisioned user name in an attribute named oxUserName in the directory server, the mapping would look like:
internal_userid : oxUserName;logininfo
contextid : oxContextName;logininfo
Please note that it is required to have mappings for both the internal user- as well as the context identifiers, as the same LDAP contacts provider definition can be used throughout multiple contexts of the system. Based on the context information, user contacts are then exposed as internal user contacts or normal/external contacts dynamically based on the context of the actually requesting user.
If the contact information from all provisioned users is stored in the directory server and made available through the LDAP contacts provider plugin, the special context-internal global address book can also be disabled to avoid ambiguities and redundant data appearing from multiple sources. This can be achieved by setting the corresponding module permission globaladdressbookdisabled.
Note: In order to disable the global address book for non-PIM users, a rather historic permission check needs to be disabled by setting com.openexchange.admin.bypassAccessCombinationChecks to true.
Also, the configuration switch ENABLE_INTERNAL_USER_EDIT should be set to FALSE in such scenarios, which will effectively hide the corresponding dialog in App Suite that would otherwise access the contact data bits that have been provisioned to the system.
Examples
Per-user filters: teachers and schools
This example shows how a per-user filter in dynamicAttributes mode can expose a different set of folders to each user, based on information about the requesting user that is itself stored in the LDAP directory. The scenario: each teacher should see one address book folder per school they are assigned to, listing all staff of that school. Teachers assigned to multiple schools see multiple folders.
Directory structure
Each teacher is a person entry with a multi-valued memberOf attribute referencing the schools they are assigned to. Schools are group entries below ou=schools, each carrying a friendly displayName.
dc=example,dc=edu
├── ou=people
│ ├── uid=aberg cn: Anna Berg mail: a.berg@example.edu
│ │ memberOf: cn=lincoln-high,ou=schools,dc=example,dc=edu
│ │ memberOf: cn=washington-elem,ou=schools,dc=example,dc=edu (teaches at 2)
│ ├── uid=bcole cn: Ben Cole mail: b.cole@example.edu
│ │ memberOf: cn=lincoln-high,ou=schools,dc=example,dc=edu
│ ├── uid=cdean cn: Clara Dean mail: c.dean@example.edu
│ │ memberOf: cn=jefferson-mid,ou=schools,dc=example,dc=edu
│ │ memberOf: cn=washington-elem,ou=schools,dc=example,dc=edu (teaches at 2)
│ └── uid=dfrost cn: David Frost mail: d.frost@example.edu
│ memberOf: cn=jefferson-mid,ou=schools,dc=example,dc=edu
└── ou=schools
├── cn=lincoln-high displayName: Lincoln High School (members: aberg, bcole)
├── cn=washington-elem displayName: Washington Elementary (members: aberg, cdean)
└── cn=jefferson-mid displayName: Jefferson Middle School (members: cdean, dfrost)
Provider configuration
The relevant part of the folders section of the provider definition (in contacts-provider-ldap.yml):
folders:
mode: dynamicAttributes
dynamicAttributes:
attributeName: memberOf
# "[value]" is the distinguishing school; "[user.mail]" is the requesting teacher's own entry
contactFilterTemplate: (&(objectClass=person)(memberOf=[value])(mail=[user.mail]))
contactSearchScope: sub
refreshInterval: 1h
# Folder name: turn the DN into the school code ("cn=lincoln-high,..." -> "lincoln-high")...
folderNamePattern: ^cn=([^,]+),.*$
# ...then (optional) look up the friendly name from the school entry itself:
folderNameFilterTemplate: (&(objectClass=groupOfNames)(cn=[value]))
folderNameSearchScope: sub
folderNameAttributeName: displayName
Resolution for a concrete user
Assume Anna Berg (mail=a.berg@example.edu) signs in. She is assigned to Lincoln High and Washington Elementary.
Step 1 - folder discovery (which folders appear). Anna's session values are substituted into the template, and the distinguishing [value] becomes a wildcard:
(&(objectClass=person)(memberOf=*)(mail=a.berg@example.edu))
This matches only Anna's own entry, and the distinct values of its memberOf attribute become her folders: cn=lincoln-high,... and cn=washington-elem,.... After applying folderNamePattern and the optional name lookup, she gets the folders Lincoln High School and Washington Elementary. She does not get a Jefferson Middle folder.
Step 2 - listing a folder, e.g. Lincoln High School. Now the per-user placeholder is replaced by a wildcard and the folder's distinguishing value is hard-coded:
(&(objectClass=person)(memberOf=cn=lincoln-high,ou=schools,dc=example,dc=edu)(mail=*))
This returns all staff of Lincoln High - Anna Berg and Ben Cole. The mail= constraint that selected Anna during discovery is intentionally dropped here, so the folder shows the whole school, not just Anna.
Resulting visibility
| Teacher (assigned schools) | Folders they see | Folder contents |
|---|---|---|
| Anna Berg (Lincoln High, Washington Elem.) | Lincoln High School, Washington Elementary | Lincoln → Anna, Ben · Washington → Anna, Clara |
| Ben Cole (Lincoln High) | Lincoln High School | Lincoln → Anna, Ben |
| Clara Dean (Jefferson Mid., Washington Elem.) | Jefferson Middle School, Washington Elementary | Jefferson → Clara, David · Washington → Anna, Clara |
| David Frost (Jefferson Mid.) | Jefferson Middle School | Jefferson → Clara, David |
Anna and Clara - the two teachers assigned to multiple schools - each get two school address lists, while Ben and David get only their one. The membership gate decides which school folders a teacher sees; within a visible folder they see the whole staff list of that school (i.e. the restriction applies at folder granularity, not per individual contact).
Notes
memberOfmust be present on the teacher entries (native in Active Directory; in OpenLDAP enable thememberofoverlay). If only the school entries carrymember, the filter has to be restructured accordingly.- The identity attribute used to select the requesting user (
mailhere) must uniquely match that user's own entry, otherwise discovery would read more than just their assignments. - With the local cache enabled, the per-teacher folder visibility still applies, but each folder's contents are taken from the shared, provider-wide snapshot. The cache is bypassed entirely if the provider uses per-user authentication (an individual LDAP bind per user). See the Caching section.
- If the school assignments are carried in the login token rather than the directory, gate on a claim instead, e.g. discover against
[session.oidc.schoolId]in place of(mail=[user.mail]).
Internal users: replacing the context global address list
This example shows how contacts served from LDAP can be recognized as the internal users that App Suite has provisioned, by resolving their user and context identifiers indirectly from login information (see also Internal Users / Global Addressbook). The scenario is a public-sector deployment where several municipalities (the same applies to ministries or agencies) are each run as a separate App Suite context, while their staff live in one shared directory. Recognizing the directory entries as internal users lets the LDAP directory become the authoritative address list for each tenant, so the built-in per-context global address book can be disabled to avoid duplicate entries.
Directory structure
Each civil servant has a person entry that, besides the usual contact attributes, carries their App Suite login name and the name of their municipality's context in dedicated attributes (here oxUserName and oxContextName). In this example two municipalities - riverton and lakeside - share one directory:
dc=example,dc=gov
└── ou=people
├── uid=smarek cn: Sofia Marek mail: sofia.marek@riverton.example.gov
│ oxUserName: smarek oxContextName: riverton department: Citizen Services
├── uid=droth cn: Daniel Roth mail: daniel.roth@riverton.example.gov
│ oxUserName: droth oxContextName: riverton department: Public Works
└── uid=hfalk cn: Henrik Falk mail: henrik.falk@lakeside.example.gov
oxUserName: hfalk oxContextName: lakeside department: Finance
Mappings
In contacts-provider-ldap-mappings.yml, the internal_userid and contextid properties are mapped to the login-info attributes using the ;logininfo flag (instead of attributes holding the numeric identifiers):
internal_userid : oxUserName;logininfo
contextid : oxContextName;logininfo
displayname : cn
givenname : givenName
surname : sn
email1 : mail
department : department
Both an internal_userid and a contextid mapping are required - the context resolution is what lets a single directory (and one shared set of mappings) serve several municipalities while still matching each entry to the correct tenant. The attribute values must match the provisioning: oxContextName has to correspond to one of the context's login mappings, and oxUserName to the user's login name within that context.
Provider configuration
Each municipality gets its own provider definition whose filter restricts the directory to that municipality's entries, and is enabled for the matching context through the config cascade. For riverton:
riverton: # provider key - enable in Riverton's context via the config cascade
name: Riverton City Directory
ldapClientId: gov_directory
mappings: gov
folders:
mode: static
static:
commonContactFilter: (&(objectClass=inetOrgPerson)(oxContextName=riverton))
commonContactSearchScope: sub
folders:
- name: City Directory
contactFilter: (&(objectClass=inetOrgPerson)(oxContextName=riverton))
contactSearchScope: sub
Define an analogous lakeside provider with (oxContextName=lakeside), and enable each one only in its own municipality's context (see Enable Contacts Provider). The filter carries a fixed municipality value here because static mode does not substitute placeholders; see the Variant below for an auto-scoping alternative.
Resolution for a concrete contact
Assume Sofia Marek, a civil servant in the riverton context (provisioned with internal context id 2051), browses the address list and the Daniel Roth entry is read:
- The
;logininfomappings put the attribute values into the temporary contact propertiesloginContextInfo=rivertonandloginUserInfo=drothinstead of setting numeric ids directly. rivertonis resolved against the provisioned context login mappings → context id2051.drothis resolved within that context → e.g. internal user id12.- As both resolve, the contact is linked to internal user
12in context2051- App Suite then treats it as that user's contact (e.g. for de-duplication against the global address book, user details, and so on).
If either value cannot be resolved, the entry is shown as a plain external contact. The internal-user link is also only applied when the resolved context matches the requesting user's own context: a lakeside entry seen by a riverton user would appear as a regular contact, never as a riverton internal user. This is what makes one shared directory and mapping set safe to use across all the municipalities' contexts.
Disabling the built-in global address book
Once all provisioned users are available through this provider, the context-internal global address book can be disabled to avoid the data appearing twice, by setting the globaladdressbookdisabled module permission. See Internal Users / Global Addressbook for the related switches (com.openexchange.admin.bypassAccessCombinationChecks and ENABLE_INTERNAL_USER_EDIT).
Variant: structure into departments
To present a municipality's staff grouped by department instead of as one flat list, switch the folder mode to dynamicAttributes and derive a folder per department (still scoped to the municipality):
folders:
mode: dynamicAttributes
dynamicAttributes:
attributeName: department
contactFilterTemplate: (&(objectClass=inetOrgPerson)(oxContextName=riverton)(department=[value]))
contactSearchScope: sub
refreshInterval: 1h
This yields folders such as Citizen Services and Public Works for Riverton. Because dynamicAttributes mode does substitute per-user placeholders, the literal riverton can be replaced with [context.name] so that a single shared provider auto-scopes to each requesting user's own municipality (see Per-user filters: teachers and schools). Either way, the user/context resolution is driven by the mappings and works identically regardless of the folder mode, so the departmental folders still expose their members as internal users.