Create an identity connector

By default, Google Cloud Search only recognizes Google identities stored in Google Cloud Directory (users and groups). Identity connectors are used to sync your enterprise’s identities to Google identities used by Google Cloud Search.

Google provides the following options for developing identity connectors:

  • The Identity Connector SDK. This option is for developers who are programming in the Java programming language. The Identity Connector SDK is a wrapper around the REST API allowing you to quickly create connectors. To create an identity connector using the SDK, refer to Create an identity connector using the Identity Connector SDK.

  • A low-level REST API and API libraries. These options are for developers who might not be programming in Java or whose codebase better accommodates a REST API or a library. To create an identity connector using the REST API, refer to Directory API: User Accounts for information on mapping users and Cloud Identity Documentation for information on mapping groups.

Create an identity connector using the Identity Connector SDK

A typical identity connector performs the following tasks:

  1. Configure the connector.
  2. Retrieve all users from your enterprise identity system and send them to Google for syncing with Google identities.
  3. Retrieve all groups from your enterprise identity system and send them to Google for syncing with Google identities.

Set up dependencies

You must include certain dependencies in your build file to use the SDK. Click on a tab below to view the dependencies for your build environment:

Maven

<dependency>
<groupId>com.google.enterprise.cloudsearch</groupId>
<artifactId>google-cloudsearch-identity-connector-sdk</artifactId>
<version>v1-0.0.3</version>
</dependency>

Gradle

 compile group: 'com.google.enterprise.cloudsearch',
         name: 'google-cloudsearch-identity-connector-sdk',
         version: 'v1-0.0.3'

Create your connector configuration

Every connector has a configuration file containing parameters used by the connector, such as the ID for your repository. Parameters are defined as key-value pairs, such as api.sourceId=1234567890abcdef.

The Google Cloud Search SDK contains several Google-supplied configuration parameters used by all connectors. You must declare the following Google-supplied parameters in your configuration file:

  • For a content connector, you must declare api.sourceId and api.serviceAccountPrivateKeyFile as these parameters identify the location of your repository and private key needed to access the repository.
  • For an identity connector, you must declare api.identitySourceId as this parameter identifies the location of your external identity source. If you are syncing users, you must also declare api.customerId as the unique ID for your enterprise's Google Workspace account.

Unless you want to override the default values of other Google-supplied parameters, you do not need to declare them in your configuration file. For additional information on the Google-supplied configuration parameters, such as how to generate certain IDs and keys, refer to Google-supplied configuration parameters.

You can also define your own repository-specific parameters for use in your configuration file.

Pass the configuration file to the connector

Set the system property config to pass the configuration file to your connector. You can set the property using the -D argument when starting the connector. For example, the following command starts the connector with the MyConfig.properties configuration file:

java -classpath myconnector.jar;... -Dconfig=MyConfig.properties MyConnector

If this argument is missing, the SDK attempts to access a default configuration file named connector-config.properties.

Create a full sync identity connector using a template class

The Identity Connector SDK contains a FullSyncIdentityConnector template class you can use to sync all users and groups from the identity repository with Google identities. This section explains how to use the FullSyncIdentityConnector template to perform a full sync of users and groups from a non-Google identity repository.

This section of the docs refers to code snippets from the IdentityConnecorSample.java sample. This sample reads user and group identities from two CSV files and syncs them with Google identities.

Implement the connector’s entry point

The entry point to a connector is the main() method. This method’s primary task is to create an instance of the Application class and invoke its start() method to run the connector.

Before calling application.start(), use the IdentityApplication.Builder class to instantiate the FullSyncIdentityConnector template. The FullSyncIdentityConnector accepts a Repository object whose methods you will implement. The following code snippet shows how to implement the main() method:

IdentityConnectorSample.java
/**
 * This sample connector uses the Cloud Search SDK template class for a full
 * sync connector. In the full sync case, the repository is responsible
 * for providing a snapshot of the complete identity mappings and
 * group rosters. This is then reconciled against the current set
 * of mappings and groups in Cloud Directory.
 *
 * @param args program command line arguments
 * @throws InterruptedException thrown if an abort is issued during initialization
 */
public static void main(String[] args) throws InterruptedException {
  Repository repository = new CsvRepository();
  IdentityConnector connector = new FullSyncIdentityConnector(repository);
  IdentityApplication application = new IdentityApplication.Builder(connector, args).build();
  application.start();
}

Behind the scenes, the SDK calls the initConfig() method after your connector’s main() method calls Application.build. The initConfig() method performs the following tasks:

  1. Calls the Configuation.isInitialized() method to ensure that the Configuration hasn’t been initialized.
  2. Initializes a Configuration object with the Google-supplied key-value pairs. Each key-value pair is stored in a ConfigValue object within the Configuration object.

Implement the Repository interface

The sole purpose of the Repository object is to perform the syncing of repository identities to Google identities. When using a template, you need only override certain methods within the Repository interface to create an identity connector. For the FullTraversalConnector , you will likely override the following methods:

  • The init() method. To perform any identity repository set-up and initialization, override the `init() method.

  • The listUsers() method. To sync all users in the identity repository with Google users, override the listUsers() method.

  • The listGroups() method. To sync all groups in the identity repository with Google Groups, override the listGroups() method.

  • (optional) The close() method. If you need to perform repository cleanup, override the close() method. This method is called once during shutdown of the connector.

Get custom configuration parameters

As part of handling your connector’s configuration, you will need to get any custom parameters from the Configuration object. This task is usually performed in a Repository class's init() method.

The Configuration class has several methods for getting different data types from a configuration. Each method returns a ConfigValue object. You will then use the ConfigValue object’s get() method to retrieve the actual value. The following snippet shows how to retrieve userMappingCsvPath and groupMappingCsvPath value from a Configuration object:

IdentityConnectorSample.java
/**
 * Initializes the repository once the SDK is initialized.
 *
 * @param context Injected context, contains convenienve methods
 *                for building users & groups
 * @throws IOException if unable to initialize.
 */
@Override
public void init(RepositoryContext context) throws IOException {
  log.info("Initializing repository");
  this.context = context;
  userMappingCsvPath = Configuration.getString(
      "sample.usersFile", "users.csv").get().trim();
  groupMappingCsvPath = Configuration.getString(
      "sample.groupsFile", "groups.csv").get().trim();
}

To get and parse a parameter containing several values, use one of the Configuration class's type parsers to parse the data into discrete chunks. The following snippet, from the tutorial connector, uses the getMultiValue method to get a list of GitHub repository names:

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
    "github.repos",
    Collections.emptyList(),
    Configuration.STRING_PARSER);

Get the mapping for all users

Override listUsers() to retrieve the mapping for all users from your identity repository. The listUsers() method accepts a checkpoint representing the last identity to be synced. The checkpoint can be used to resume syncing should the process be interrupted. For each user in your repository, you will perform these steps in the listUsers() method:

  1. Get a mapping consisting of the Google identity and associated external identity.
  2. Package the pair into an iterator returned by the listUsers() method.

Get a user mapping

The following code snippet demonstrates how to retrieve the identity mappings stored in a CSV file:

IdentityConnectorSample.java
/**
 * Retrieves all user identity mappings for the identity source. For the
 * full sync connector, the repository must provide a complete snapshot
 * of the mappings. This is reconciled against the current mappings
 * in Cloud Directory. All identity mappings returned here are
 * set in Cloud Directory. Any previously mapped users that are omitted
 * are unmapped.
 *
 * The connector does not create new users. All users are assumed to
 * exist in Cloud Directory.
 *
 * @param checkpoint Saved state if paging over large result sets. Not used
 *                   for this sample.
 * @return Iterator of user identity mappings
 * @throws IOException if unable to read user identity mappings
 */
@Override
public CheckpointCloseableIterable<IdentityUser> listUsers(byte[] checkpoint)
    throws IOException {
  List<IdentityUser> users = new ArrayList<>();
  try (Reader in = new FileReader(userMappingCsvPath)) {
    // Read user mappings from CSV file
    CSVParser parser = CSVFormat.RFC4180
        .withIgnoreSurroundingSpaces()
        .withIgnoreEmptyLines()
        .withCommentMarker('#')
        .parse(in);
    for (CSVRecord record : parser.getRecords()) {
      // Each record is in form: "primary_email", "external_id"
      String primaryEmailAddress = record.get(0);
      String externalId = record.get(1);
      if (primaryEmailAddress.isEmpty() || externalId.isEmpty()) {
        // Skip any malformed mappings
        continue;
      }
      log.info(() -> String.format("Adding user %s/%s",
          primaryEmailAddress, externalId));

      // Add the identity mapping
      IdentityUser user = context.buildIdentityUser(
          primaryEmailAddress, externalId);
      users.add(user);
    }
  }
  // ...
}

Package a user mapping into an iterator

The listUsers() method returns an Iterator, specifically a CheckpointCloseableIterable, of IdentityUser objects. You can use the CheckpointClosableIterableImpl.Builder class to construct and return an iterator. The following code snippet shows how to package each mapping into list build the iterator from that list:

IdentityConnectorSample.java
CheckpointCloseableIterable<IdentityUser> iterator =
  new CheckpointCloseableIterableImpl.Builder<IdentityUser>(users)
      .setHasMore(false)
      .setCheckpoint((byte[])null)
      .build();

Get a group

Override listGroups() to retrieve all groups and their members from your identity repository. The listGroups() method accepts a checkpoint representing the last identity to be synced. The checkpoint can be used to resume syncing should the process be interrupted. For each user in your repository, you will perform these steps in the listGroups() method:

  1. Get the group and its members.
  2. Package each group and members into an iterator returned by the listGroups() method.

Get the group identity

The following code snippet demonstrates how to retrieve the groups and members stored in a CSV file:

IdentityConnectorSample.java
/**
 * Retrieves all group rosters for the identity source. For the
 * full sync connector, the repository must provide a complete snapshot
 * of the rosters. This is reconciled against the current rosters
 * in Cloud Directory. All groups and members  returned here are
 * set in Cloud Directory. Any previously created groups or members
 * that are omitted are removed.
 *
 * @param checkpoint Saved state if paging over large result sets. Not used
 *                   for this sample.
 * @return Iterator of group rosters
 * @throws IOException if unable to read groups
 */    @Override
public CheckpointCloseableIterable<IdentityGroup> listGroups(byte[] checkpoint)
    throws IOException {
  List<IdentityGroup> groups = new ArrayList<>();
  try (Reader in = new FileReader(groupMappingCsvPath)) {
    // Read group rosters from CSV
    CSVParser parser = CSVFormat.RFC4180
        .withIgnoreSurroundingSpaces()
        .withIgnoreEmptyLines()
        .withCommentMarker('#')
        .parse(in);
    for (CSVRecord record : parser.getRecords()) {
      // Each record is in form: "group_id", "member"[, ..., "memberN"]
      String groupName = record.get(0);
      log.info(() -> String.format("Adding group %s", groupName));
      // Parse the remaining columns as group memberships
      Supplier<Set<Membership>> members = new MembershipsSupplier(record);
      IdentityGroup group = context.buildIdentityGroup(groupName, members);
      groups.add(group);
    }
  }
  // ...

}

Package the group and members into an iterator

The listGroups() method returns an Iterator, specifically a CheckpointCloseableIterable, of IdentityGroup objects. You can use the CheckpointClosableIterableImpl.Builder class to construct and return an iterator. The following code snippet shows how to package each group and members into a list and build the iterator from that list:

IdentityConnectorSample.java
CheckpointCloseableIterable<IdentityGroup> iterator =
   new CheckpointCloseableIterableImpl.Builder<IdentityGroup>(groups)
      .setHasMore(false)
      .setCheckpoint((byte[])null)
      .build();

Next Steps

Here are a few next steps you might take: