1. Federation
  2. Gateway schema extensions

Federation

Gateway schema extensions

How to connect federated types

A common use case may be to extend a certain type of a federated service with values resolved by another service. The only place to extend the schema likewise, is the gateway itself. Let's stay with our example. We have one service that is connected to an eDirectory and one connected to an Active Directory. That means we have the types User (eDirectory) and AD_User (Active Directory). We want to extend the User type by a property adUser that resolves the connected user from the Active Directory. As well we want to extend the AD_User type by the property edirUser vice versa.

For our example to work, there must be some way to align both user types via certain properties. In our case the eDirectory service may have its User type with a adObjectSid and the Active Directory service has extended the AD_User type with the property objectSid. Also they both extended the filter inputs to be able to filter by those new properties:

eDirectory schema extension:

extend type User {
  adObjectSid: String @ldapField(attribute: "adObjectSid")
}

extend input UserBy {
  adObjectSid: [String!]
}

extend input UserWhere {
  adObjectSid: [String!]
}

Active Directory schema extension:

extend type AD_User {
  objectSid: String @ldapField(attribute: "objectSid") @buffer(format: "objectSid")
}

extend input AD_UserBy {
  objectSid: [String!]
}

extend input AD_UserWhere {
  objectSid: [String!]
}

Pay attention to the @buffer directive and the passed format objectSid. This format was added tom the @buffer directive in version v9.5.0 as well and returns the string representation of an Active Directory objectSid.

The @graphql directive

The IdentityHub as a gateway has no access to the usual directives (@ldapField, @ldapSearch, etc). It has though one new directive called @graphql. Have a look to this gateway schema extension:

extend type User {
  adUser: AD_User     
    @graphql(query: "($id: [ID!]){user(by: {id: $id}) { adObjectSid }}", variables: {id: "id"})
    @graphql(query: "($objectSid: [String!]){AD_user(by: {objectSid: $objectSid}) {[...]}}", variables: {objectSid: "user.adObjectSid"}, path: "AD_user") 
}

extend type AD_User {
  edirUser: User
    @graphql(query: "($id: [ID!]){AD_user(by: {id: $id}) { objectSid }}", variables: {id: "id"})
    @graphql(query: "($objectSid: [String!]){user(by: {adObjectSid: $objectSid}) {[...]}}", variables: {objectSid: "AD_user.objectSid"}, path: "user") 
}

As you can see this directive is repeatable and can be chained to achieve a certain goal. The parameters are the query, the variables and a path.

At first we have a look at the adUser extension. The first @graphql directive is querying (user(){}) the user itself (the parent object) by using the id of the parent User object. The directive ensures that the id is requested even if it is not in gthe selection set of the prent user. As you can see the selection set of our @graphql query contains the adObjectSid. The result of that first directive would be:

{
  user: {
    adObjectSid: "some SID"
  } 
}

This result is passed as new parent object to the next @graphql directive. Now we can use the user.adObjectSid in our variables to send a query AD_user and filter by the objectSid. The path property defined what the directive should resolve starting from the content of the data property. Since we want to resolve an AD_User we pass the path AD_user which correlates to the query name.

As you can see we used [...] as selection set in the second directive query. This is a placeholder that gets replaced with the really requested selection set. A query utilizing the shown schema extension could be:

query edirUser {
  user(by: {id: "xyz"}) {
    dn
    fullName
    adUser {
      name
      mail
      company
    }
  }
}

In our example [...] would be replaced with the really selected fields name, mail and company.

The second schema extension does the same vice versa.

Every query send via the @graphql directive can be named, i.e.

@graphql(query: "query namedQuery { user(...) { ... } }")

If it is not, it will get a unique name based on the parent type name, the field name and a hash of the query itself.