Customization
Schema extensions
With schema extensions you can create new or extend existing GraphQL types or create new queries.
Create a schema extension
Whilst the find and search queries already allow a high degree of freedom because with them you are able to access any attribute of an LDAP entry you want, they should only be used for development and test environments. By extending the schema in a well-defined way, you can enhance your possibilities in a safe manner.
You can extend the GraphQL schema via the IdentityHub config. Under the graphql
section in the config, you may provide the schemaExtension
option.
The value can either be configured as plain string or as a path to a file. The path is resolved relative to the config directory.
If you want to use a path, configure it as follows:
[graphql]
schemaExtension="file://path/to/extension/file.graphql"
We recommend to use a separate file. This is way easier to read and your editor can provide you with correct syntax highlighting for graphql files.
Your extensions will be added to the schema on startup of the IdentityHub server.
Adding custom fields
A common use case could be a custom attribute your company is using to store some information on a User
in LDAP. The example below shows how you can add this attribute to the User
type and therefore make it accessible in your queries. To show how to add a schema extension directly in the config instead of a separate file the corresponding syntax is used here.
[graphql]
schemaExtension='''
extend type User {
companyAttribute: String @ldapField(attribute: "companyAttribute")
}
'''
For multiline strings in the config file use the triple '''
.
After restarting the IdentityHub you are now able to request the field companyAttribute
on every User
type and the Viewer
as well.
In the schema extension above we defined this field to return a single value string, using the @ldapField
directive which takes care of extracting the value from the LDAP attribute companyAttribute
and casting it to the desired type.
For multi-value fields adapt your schema extension to return an array of strings. Since the passed parameter attribute
is the same as the alias at the start of the line, you don't need to pass the attribute
parameter.
extend type User {
companyAttribute: [String] @ldapField
}
Filter by custom attributes
In the same way as seen above, you can extend input types. For example you may want to be able to filter your users by your newly added field. The extension below shows how to extend the UserBy
input type and how to use it in a query.
extend input UserBy {
companyAttribute: [String!]
}
We defined the companyAttribute
input field as an array of strings because this automatically provides you with the possibility to create OR
connected filters as seen below.
query SearchUsers {
users(by: { companyAttribute: ["value1", "value2"] }) {
nodes {
id
companyAttribute
}
}
}
This query will create the LDAP filter
(&(objectClass=Person)(|(companyAttribute=value1)(companyAttribute=value2)))
To stay compliant with the already existing API you may want to additionally extend the UserWhere
and add the companyAttribute
to the qAttributes
in the config section ldap.users.qAttributes
.
extend input UserWhere {
companyAttribute: [StringFilterInput!]
}
extend input UserBy {
companyAttributes: [String]
}
Those extensions will give you the ability to perform the following queries
query SearchUsersWithWhere {
users(where: { companyAttribute: { startsWith: "a" } }) {
nodes {
id
companyAttribute
}
}
}
query SearchUsersWithBy {
usersBy(companyAttributes: ["value1", "value2"]) {
nodes {
id
companyAttribute
}
}
}
We highly recommend to define your FilterInputs as arrays (i.e. givenName: [StringMatchFilterInput!]
instead of givenName: StringMatchFilterInput
), to take advantage of implicit combination of filters from arrays with or
.
You can even use aliases for the added attributes by using the ldap.attributeMapping
config option. See the example below on how to add a filter surname
that effectively filters by ldaps sn
attribute.
[ldap]
[ldap.attributeMapping]
surname="sn"
[graphql]
schemaExtension='''
extend inputUserWhere {
surname: [StringMatchFilterInput!]
}
'''
Now you can write a query like:
query SearchUsersWithWhere {
users(where: { surname: { eq: "mustermann" } }) {
nodes {
id
surname
}
}
}
Keep in mind, that you only can extend
a type but not override
fields of that type. If a type you want to extend already has a givenName
property, you cannot re-define that property and you have to use another name for that field.
Create new types and queries
Another common use case may be, that you want to define a whole new LDAP type and with that queries to find those types in your directory. Let's say you store objects called Application
s in your directory, that have a subset of custom properties as well as relations to Group
s. First, we define the new type Application
.
type Application {
id: ID! @ldapField(attribute: "guid") @id
name: String @ldapField(attribute: "appName")
title: String @ldapField(attribute: "appTitle")
availableFor: GroupConnection @ldapField(attribute: "groups") @resolveDNConnection(resolvedType: "Group")
}
We created a new type Application
, that uses the guid
attribute as its ID, has a name and title extracted from the given LDAP attributes and has a pageable Connection of groups connected to it, which are stored in a multi-value attribute groups
, which we first resolve via the @ldapField
directive and the transform to a Connection with the @resolveDNConnection
directive.
To properly work with our new type we may need a query to be able to find them via the IdentityHub. We will add a simple q
filter and basic ldap paging possibilities. To ensure a proper Connection we need to define the corresponding ApplicationConnection
and ApplicationEdge
types. The type Query
is just another GraphQL type that we can extend.
type ApplicationEdge {
cursor: ConnectionCursor!
node: Application
}
type ApplicationConnection {
estimatedSize: Int
edges: [ApplicationEdge!]!
nodes: [Application!]!
pageInfo: PageInfo!
}
extend type Query {
applications(q: String, paging: LDAPPaging) ApplicationConnection @ldapSearch(filter: "(objectClass=application)", scope: sub, base: "ou=applications,o=data", qAttributes: ["appName", "appTitle"])
}
The @ldapSearch
directive creates a search function for you and let's you define base
and scope
, as well as a custom filter
that narrows down the results. by defining a q
parameter and providing the qAttributes
we can now find our applications by their name of title.
query SearchApplications {
applications(q: "test") {
nodes {
id
name
title
}
}
}
This will perform the LDAP query
(&(objectClass=application)(|(appName=test)(appTitle=test)))
under the base ou=applications,o=data
with the search scope sub
.
Conclusion
These examples should give you a short glimpse of the powerful, yet easy ways to use schema extensions.
For further insight, please use the schema explorer of the graphql playground (if configured the IdentityHub to expose it, i.e. http://localhost:4000/-/playground
) to explore the schema after you extended it via config.
For a detailed description of all directives or guidance along your way, please don't hesitate to contact us.