Finding and Monitoring Identities

One of the most common uses of the Identity Services API is to look for an identity stored in an identity authority. You may also need to observe changes to users and groups that occur outside the scope of your application. For example, if another application removes a user from a group your application is using, you want to be notified of this change. In the Core Services Identity API, the CSIdentityQuery class provides synchronous and asynchronous access to find and monitor identities from an identity authority’s database. In the Collaboration framework, these methods are part of the CBIdentity class.

This chapter explains how to search for identities using both CSIdentityQuery objects and CBIdentity objects.

Find an Identity

You can find an identity using either the Objective-C based Collaboration framework or the Core Services Identity API.

Using the Collaboration Framework

To find a user or group with the Collaboration framework, use one of the CBIdentity class factory methods. There are three methods that allow you to search based on different properties of an identity:

  • If you want to search by full names, short names, or aliases, use the identityWithName:authority: method.

  • If you want to search by UUID, use the identityWithUUIDString:authority: method.

  • If you want to search using a persistent reference, use the identityWithPersistentReference: method. (For more information about persistent references, see “Loading an ACL”).

To complete your search, pass a search term and an identity authority object to either the identityWithName:authority: and the identityWithUUIDString:authority: methods. There are a number of class factory methods in CBIdentityAuthority that allow you to create an identity authority object based on the identity authorities you want to search. Listing 3-1 shows how to search for all identities named “David Ortiz” in a local identity authority.

Listing 3-1  Finding an identity in Objective-C

CBIdentityAuthority *localAuthority =
    [CBIdentityAuthority localIdentityAuthority];
CBIdentity *user =
    [CBIdentity identityWithName:@"David Ortiz" authority:localAuthority];

You can also search specifically for a user identity or a group identity by using the CBUserIdentity and CBGroupIdentity classes, respectively. By default, the CBIdentity class factory methods search for a user identities first, and if none are located then it looks for group identities.

Using the Core Services Identity API

To find a user or group with the Core Services Identity API you need to create a CSIdentityQuery object. A CSIdentityQuery object contains methods to search the identities database. It is important to use the appropriate method to create the identity query object based on how you want to search the database. The following methods are provided for you:

  • If you want to search by full names, short names, or aliases, use the CSIdentityQueryCreateForName method.

  • If you want to search by UUID, use the CSIdentityQueryCreateForUUID method.

  • If you want to search by POSIX ID, use the CSIdentityQueryCreateForPosixID method.

  • If you want to search by reference data (generated by the CSIdentityCreatePersistentReference method), use the CSIdentityQueryCreateForPersistentReference method.

  • If you want to search for the current user’s identity, use the CSIdentityQueryCreateForCurrentUser method.

There are two ways to execute the search, synchronously and asynchronously. It is highly recommended that you run any process that could block as a result of network delays asynchronously.

Search Identities Synchronously

To perform a CSIdentityQuery search synchronously, call the method CSIdentityQueryExecute on your identity query object. The method returns only when it has completed the search. If the query is executed successfully, CSIdentityQueryExecute returns TRUE; otherwise, it returns FALSE. Assuming the query was successful, run the CSIdentityQueryCopyResults method to return an array of identity objects. When you have finished retrieving the identities, make sure to release the CSIdentityQuery object. Listing 3-2 shows an example of this.

Listing 3-2  Finding identities synchronously

CSIdentityQueryRef query;
CFErrorRef error;
CFArrayRef identityArray;
 
// create the identity query based on name
query = CSIdentityQueryCreateForName(kCFAllocatorDefault,
                                     CFSTR("David"),
                                     kCSIdentityQueryStringBeginsWith,
                                     kCSIdentityClassUser,
                                     CSGetDefaultIdentityAuthority());
 
// execute the query
if (CSIdentityQueryExecute(query, kCSIdentityQueryGenerateUpdateEvents, &error))
{
    // retrieve the results of the identity query
    identityArray = CSIdentityQueryCopyResults(query);
 
    // do something with identityArray
 
 
 
}
CFRelease(query);

Search Identities Asynchronously

Performing an identity query asynchronously is similar to performing a query synchronously but differs in an important way. With a synchronous query, you execute the query, wait for it to complete, and then ask for the results. In contrast, with an asynchronous query, you start the query and your callback function will be passed the results as they become available. The process for setting up asynchronous callbacks is similar in theory and in practice to other Core Services callbacks.

To search for an identity asynchronously requires two main steps: setting up a callback function and adding the identity query object to a run loop. First, set up your callback function. The callback function must be a void function and must accept five arguments:

  • CSIdentityQueryRef query, the identity query object

  • CSIdentityQueryEvent event, the event that caused the callback function to be run

  • CFArrayRef identities, the results of the query as an array identities

  • CFErrorRef error, the error that occurred as a result of the identity query, if applicable

  • void *info, any data placed in the CSIdentityQueryClientContext, to be sent to the callback function

A callback function might look like Listing 3-3.

Listing 3-3  Identity query callback function

void myIdentityQueryCallback (CSIdentityQueryRef query,
                              CSIdentityQueryEvent event,
                              CFArrayRef identities,
                              CFErrorRef error,
                              void *info) {
 
    // See what event triggered the callback
    switch ( event ) {
 
        case kCSIdentityQueryEventResultsAdded:
        // An identity was added to the list of results
        break;
 
        case kCSIdentityQueryEventSearchPhaseFinished:
        // The query was completed
        break;
    }
}

To add the identity query object to a run loop, first create the object. Then create a CSIdentityQueryClientContext structure. In the CSIdentityQueryClientContext structure, define the name of the callback function to be run. With the CSIdentityQueryClientContext structure set up, call the CSIdentityQueryExecuteAsynchronously method to add the query to a run loop.

CSIdentityQueryExecuteAsynchronously requires five arguments: the identity query object to be executed, the execution options (from CSIdentityQueryFlags), a pointer to your CSIdentityQueryClientContext structure, the run loop on which to schedule callbacks, and the run loop mode. If the query is added to the run loop, the function returns TRUE and any errors as a result of the query are sent to your callback function. See Listing 3-4 to see how this looks in code.

Listing 3-4  Adding an identity query object to a run loop

CSIdentityQueryRef query;
CSIdentityQueryClientContext queryclient =
    {0, NULL, NULL, NULL, NULL, myIdentityQueryCallback};
 
// create the identity query based on name
query = CSIdentityQueryCreateForName(kCFAllocatorDefault,
                                     CFSTR("David"),
                                     kCSIdentityQueryStringBeginsWith,
                                     kCSIdentityClassUser,
                                     CSGetDefaultIdentityAuthority());
 
// add the identity query object to the current run loop
if (!CSIdentityQueryExecuteAsynchronously(query,
                                          kCSIdentityQueryGenerateUpdateEvents,
                                          &queryclient,
                                          CFRunLoopGetCurrent(),
                                          kCFRunLoopCommonModes))
{
    // query was not added to the run loop
}

When an event is triggered based on your query, your callback function is run. The event that causes your callback function to run is passed to the callback function along with an array of identities. If identities from the query are added, removed, or modified, the array contains only those identities that have been affected. If the search is completed, then the array is NULL. In this case, use CSIdentityQueryCopyResults to get the full list of identities.

After the query has completed, you need to remove the identity query object from the run loop. To do this, call the CSIdentityQueryStop method and pass it the identity query object. Then, release the identity query object. This should look like the code in Listing 3-5.

Listing 3-5  Invalidating an identity query object

CSIdentityQueryStop(query);
CFRelease(query);

Continually Monitor Identities

Monitoring an identity is very similar to searching for one. If you are searching for an identity asynchronously, then all you need to do is to not call CSIdentityQueryStop when the query is completed. As long as your identity query object is registered on the run loop, it continues to notify you when the contents of your query change. So if you are searching for all Identities with the name “Chris”, and a new user is created with the name “Chris Jones” after your original search finished, your callback function will be notified of this new user. When you are done monitoring the identities, make sure to call CSIdentityQueryStop.

To monitor identities synchronously, you need to poll an identity query object. Each identity query object can only be executed once, so after running CSIdentityQueryExecute and checking the results with CSIdentityQueryCopyResults, you will need to create an identical identity query object to execute again. Each time you run CSIdentityQueryCopyResults it will return an array with the full results of your query, not just what has changed. This is another reason why it is recommended that you search for and monitor identities asynchronously, rather than synchronously.