Mac • 1:08:08
Integrating Snow Leopard's Open Directory framework allows your application to provide authentication, authorization, and access controls. Learn how to implement common features such as record lookup, search and authentication, best practices for doing it right the first time, and critical debugging techniques and tools.
Speaker: Nicole Jacque
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to Adding Authentication, Authorization, and Access Controls With the Open Directory Framework, or as I like to call the session, the session with the three long A words in the title. Barely -- barely fit on the slides here. We had to do some special, you know, messing around with the fonts to make it work.
So -- but also, this session is also known as DirectoryService Development in Snow Leopard, which is a little bit shorter title. I'm Nicole Jacque, and I am an engineer with the Directory Server group at Apple, but one thing I want you to realize is that even though we have the title of this API, the Open Directory Framework, even though this sounds like the name of our server product, and I'm from the server group, this API is not just about server. This is about directory services with OS X in general.
Client and server. So we're going to be talking about -- a little bit about both today. But we're going start with an overview of the DirectoryServices. How many people here are new to DirectoryServices in OS X. Anybody new to the platform, anybody new to -- OK, got a whole bunch of old timers here. That's great.
So we're going to start by talking about the Open Directory Framework, after we do our overview. Then we're going to talk about a related API, which is the membership API, and then we're going it talk about things that you can do to test and debug your DirectoryService application, some tools that you can use, and finally, we're going to wrap up with some tips for how you can make sure that your application does things right the first time, so that you don't have to, you know, put an application out there and have people say, Oh, what, it doesn't work with this. So we're going to cover a bunch of common issues that developers run into.
So let's just start with a brief overview of DirectoryServices. The main job of DirectoryServices in OS X is, it's a translator between some store of data that has records in it. And this could be records for things like users or groups or computers. Really just any kind of type of data that you might want to store and use in some way. And then it has to translate this to the operating system layer.
So OS X wants to know about users in groups, but it doesn't really know what to do with this data store, because it could be something like flat files, it could be some server out somewhere. So DirectoryService's job is to make sense of all of this. And it does this with a bunch of different plug-ins. So we have a plug-in for open directory.
That's Apple's own directory server. But we also talk to Active Directory, which is very popular. We talk to the UNIX flat files, we talk to NIS, we talk to E directory. We have a generic LDAP 3G plug-in that can pretty much talk to any LDAP server out there.
And what this looks like is, you have OS X layer at the top and the operating system calls into directory services with some different APIs, you can use the limb system APIs, you can use the DirectoryService APIs, which is the traditional API that people have been using for a long time to talk to DirectoryServices. And now we have the new Open Directory Framework.
And then DirectoryService itself is a daemon that runs -- it kind of does all of the translation work. And then it has a bunch of plug-ins that talk to all of the various data stores, whether they're a flat file on your local system or some server out on the network. And one thing to point out here is if there's not a plug-in for the directory server, the directory data that you want to use, you can actually write a plug-in to DirectoryServices to talk to that.
So this is extensible. Who uses DirectoryServices? Well, everybody does. Everybody, you either log in to your machine or you may have it set to automatically log in for you, but every time you do that, you're talking to DirectoryServices. But then lots and lots of different applications and services that are running on your machine are talking to DirectoryService all the time.
Every time that you access your file in your File System, it's got to figure out who has access to that file and to get information about users and groups and so on, it has to talk to DirectoryServices. Things that run on a server, things like the wiki server, or iCal server.
All of those types of applications all use DirectoryServices. So whether you know it or not, you've probably used DirectoryService all the time. But more generally, there's several different categories of developers that tend to use DirectoryServices, and one is just people that are developing some service, maybe something that, you know, runs under the covers or command line utility, something that, you know, may be a file server, something like that. Then there's also people that write applications, things with nice, pretty GUIs on them. And for example you might want to be able to have people select users to have access to something, or read data out of DirectoryServices. So those people use DirectoryServices.
But one of the really exciting areas that people can, you know, areas where people can use DirectoryServices is people that are like in house developers or system Admins. Because a lot of times you have all this data already in your directory server somewhere, and you'd like to make use of it in some other way.
And rather than having to have a totally separate duplicate of all of this data, all the user records and you know, pass words and stuff, you can actually just tie right in to DirectoryServices. So the big news for Snow Leopard is we've always had this thing called the DirectoryService API. And this is how you use any -- if you've done any programming with DirectoryServices, this is probably how you've done it.
How many people have used DirectoryService API? OK, good group of you. So the big news for Snow Leopard is it's deprecated. And please -- please don't cry or anything like that, I know you're -- you're all probably very, very saddened to see it go, we're very sad ourselves. Ahem, I'm trying to stop from dancing up here. And if we're going to -- we're going deprecate this -- and by the way it is still there, you can still use it. You'll get warnings that it's deprecated.
But it's not gone. Just deprecated. We want to encourage you, instead, though, to take a look at new API, which is the Open Directory Framework. So what is the Open Directory Framework. Well, it's a high level API, and we have two flavors of it. We have Cocoa and CoreFoundation.
So whether you use Cocoa or CoreFoundation, we have an API for you. And you can use this to read, write, authenticate, do anything that you would do with DirectoryServices to any DirectoryService node. So the fact that it says open directory in the title does not mean you're limited to using things with our open directory server.
This is anything in DirectoryServices. It's also thread safe, and one of the important things about this API was this is our chance to really do it right. And so we try to make it very simple, very easy to use, to have data structures that make sense. So one of the things that you'll notice if you're familiar with the DirectoryService API, there's no more of this having to create a buffer, and you know, see if it's big enough, you know, maybe make it bigger, and see if it's big enough, and maybe make it bigger, see if it's big enough.
I actually recently had the opportunity to show some people that had never done any DirectoryService programming before, one person had some backwards compatibility requirements. They had to still learn the DirectoryService API. And one had to learn the Open Directory Framework, because they didn't need any of the other stuff, and they wanted to use the new API.
Open Directory Framework, they caught on right away, they said, Hey, this all makes sense, this is really easy, once they figured out what a node was and all this stuff. Piece of cake. Got to tell you, teaching somebody the DirectoryService API after you're used to the Open Directory Framework -- it was so painful. I still have nightmares about that.
So -- you know, they're like, Well, what's this buffer thing, why am I -- what's this data structure? So anyway, those of you who have used the DirectoryService API will really appreciate the simplicity of the Open Directory Framework, and those of you that haven't used the DirectoryService API, well, you're just lucky.
So we're going to start talking about the API itself, and we're going to talk today mostly about the Cocoa version of it. Most of this is all -- it's mirrored by the CoreFoundation version, but we had to kind of pick one way or the other to talk about it, to make it less confusing, so we're going to go with the Cocoa version. So there's four main classes that you have to work with. And we have the ODSession. So there's four main classes that you have to work with.
And we have the ODSession, the ODNode, the ODRecord, and the ODQuery. And so to start, let's talk about what an ODSession is. An ODSession is really just a conversation between your process and DirectoryServices. So whatever DirectoryServices is talking to out there, you don't really know at this point yet. You just want to talk to DirectoryS. And generally speaking, you don't have to do too much configuration for this. You just want the default session.
So that's pretty easy to get. You just say ODSession, default session. And then once you have your session you get a bunch of information. You can find out, for example, what nodes are available. So you can find out what is DirectoryService configured to talk to. Is it talking to Active Directory, is it talking to an LDAP server, is it talking to just local nodes. You can also find out what nodes might be actually up or down. So you've got the list of all the nodes that are configured. Now you can see which ones are not actually working at the moment. So maybe the server is down or the network is down.
You can find out that information from the session. Now once you have the session, it's time to work down another layer. And that is to start talking to an actual node. So the ODNode is a representation of a data store somewhere. So this could be, you know, flat files, it could be some directory server. It could also be a collection of data stores.
So it could be multiple nodes kind of conglomerated together. We have a couple of special ones. For example, the search node, which we're going to talk a lot about. This is just really a node that is made up of all the nodes that the user has said, I want to use these nodes to authenticate against. And there's also a node called Active Directory all domains.
This is really just a node that contains all the different domains that are in Active Directory that a user is configured to. Because an Active Directory forest can have lots of domains. And you might not want to have to talk to each individual domain in the forest, you can talk to Active Directory, all domains. When you're talking about nodes, one way you can pick a node is you can specify it by its actual name.
And this usually looks something like the plug-in name slash the actual configured, you know, server or type of that plug-in. So you know, if it's the LDAP plug-in, it's LDAP V 3, slash, the name of your server, Active Directory, all domains. So you can do this. You can say node with session and the name, and just specify the actual name here.
The other thing that you can do is you could actually specify the node by the node type. And we have a bunch of different types. There's really just one that you really use a lot. And that is the authentication, KOD type of node authentication. This is the same thing we're talking about and referring to this as the search node a lot. But there's also a few other ones.
There's contacts, which is records that are used by Address Book. There's network, there's local nodes, which is nodes that are local to the computer, and there's the configuration node, which is information that DirectoryServices uses for its own configuration. So if you want to specify the node by its type, you just -- node with session type, an then specify the particular one that you want to use. And in this case, KOD node type, authentication, which is the search node.
So first thing, of course, you get your session. Once you have a session, you can get the node. In this case, we're specifying the search node. And you get some information about the node, you can find out what the node's name is. You can also find out some more information, for example, you can find out is the node read only.
Some nodes you can write to, some nodes you can't. Might also want to find out how is the machine bound to it. Is it a trusted authentication bind, is it anonymous, you can find that kind of information out. And you can also find out what record types are supported by that node. Because not all directory servers out there support all types of records.
And so now that we've already mentioned records, that brings us to the third class, which is ODRecord. So a record is just some data stored in the node. And it could be a user, it could be a group. But it's basically a record, which has a name, and then it has lots of different pieces of information called attributes in it. And so for example, you might be familiar with Work Group Manager, where you actually -- you know, you have all kinds of different pieces of information that can be part of a record, and all kinds of different records that you can work with. Here's -- here's Steve's.
Sort of. Anybody thought that was his real home phone number, don't -- so you've got all these records. And there's lots and lots of different record types out there. I've got a few of the real popular ones here, like users and groups, computers, configuration records, accounts, people -- those are what we call our contact records. But there's a whole long list of these, and if you check out the documentation in the constant section, you know, I think it goes on for, like, pages. So take a look, if there's a record you're looking for, I'm sure we have a record type for it.
So once you have the record name, it's pretty easy to get the record. So you know, we set up our session and we get the node. We get the record, and in this case, we already know that the user's name -- in this case as off user, so we're just saying give us a record type of user, and we've got the name as off user. We can specify what attributes we want for that -- that user.
We can get all of them, we can get some certain subset of them. And then you can also do things like read a particular value of one of the attributes in that record. And the nice thing here you'll notice, for those of you who use the DS API, we're just dealing with things like arrays. You can also get a dictionary of all of these values. So it's really easy to work with, and you get these data structures back that make sense.
And once you have a record there's some interesting things you can do with records. One of them is, well, if you're going to be authenticating users you want to be able to verify that you've got their passwords correct, so they know if he gets access or not. And if you're working with passwords, well the next thing users, well, you know, users, they're not happy with just authenticating, they want to change their password too. So we give you some really easy methods for doing that. So in this case, we set up the session in the node, you can verify the password. That's verify password.
And you can also change the password. Once you've dealt with authenticating users and stuff like that, another common thing you want to do is be able to actually edit records and DirectoryServices. And to edit a record there's really -- there's three steps that you need to go through.
The first thing you need to do is, you need to supply some credentials to DirectoryServices to say who you are, who is this person that wants to actually edit something in the directory. Because of course the directory doesn't want to let just anybody edit the data. You know, if you let, you know, just any old user edit your Active Directory, that probably isn't going to fly.
So what you need to do is specify the credentials for what user, or maybe it could be a computer account potentially, too, is actually going to be making the change in the directory. After you do that, you can specify what actual changes you want to make. And then finally, you're going to actually synchronize the record with the directory. And this is where the actual change takes place. Until you do the synchronize, nothing happens.
So just a little example here. First we're going to set the credentials. So we've got some Admin user that we're going to use. And that Admin user is going to edit some data in some other record. In this case, we're adding some plist information to a user record.
Now even though we said what we're going to do to the record, nothing actually happens until we actually do the synchronization. So when we do synchronize and return error, that actually does the write. Another type of editing that people need to do is -- has to do with group membership.
And this is both checking membership in a group, because that can be pretty complex, right? Because you can have users in a group, you can have a group with nested groups, we could have many, many layers of nesting of groups. And then you want to be able to edit the membership in that group, and again, that can be very complicated because if you got all this nest and you've got to figure out, OK, how do I get that user in that group.
And we actually store our group information in one way, and different other directory servers out there, they might store it a different way. For example, in open directory and our DirectoryServices, we store group information with both the user name and the user's GUI, so there's actually two attributes.
So rather than having you try to sort this out on your own , we're going to give you some easy methods toward handling, checking group membership, and editing group membership. So once you have your user record and your group record, this is really easy. Say we want to add a user to a group. Well, we just do add member record, and we add the user record to the group record.
Now because this is a change, this is an editing of a record, you again have to do the synchronize and return error, or nothing happens. But now we can test and see if this user is now in fact in the group. And so that is the is-member record method.
And that's going to return true or false. And then again we could also remove a user from a group. And you could actually remove groups from groups or add groups to groups, and so on. But again, keep in mind, you make the change, you have to do the synchronize afterwards, or nothing's going to happen.
So we've been talking a lot about records so far, and I've been kind of cheating, right? Because I always knew what the record name was before hand. And a lot of times you don't know, you don't just get passed a record name. You have to find out what is the actual record name.
What is this record that I want to deal with. And to get this information you want to be able to query DirectoryServices. And that's what the fourth data type is for, the ODQuery. So you could -- you could have some sort of complex query where you say, you know, find all the users with a phone number that begins with 555 and you want their name and you can specify what information you want back. So query like this in code looks something like this. So we have our session in node. And then we set up the query. And the query you can specify a lot of different parameters here.
So you're going to specify what node you want to query, and keep in mind when you're doing the querying, it's probably more likely that you want to query the search node as opposed to any particular node, because you don't necessarily know what node any kind -- any particular record is in. So for queries, generally speaking, stick to the search node. OK, then you want to specify what type of record are you searching for. Looking for users, looking for groups, computer records. And then you want to specify what attributes you want to search on.
Could be the record name, could be any other attribute. And what type of matching you want. So if you want something that's equal, if you want it to be case sensitive, case insensitive. You can specify what you're actually trying to match now. So you actually want to search for users with the name John. Or you want to search for users who have a Home Directory on this particular server. And then finally you want to specify what you want to get back.
So you want to specify what attributes do you want. You might not be interested in the entire record, you might just want one or two attributes, or you might want everything. And you also want to specify what results -- how many results do you want to get. You might only care if there's one result.
You don't care, there might be one result or a thousand results, it's all the same to you. So you might say, Well, I just want one. You could say you want all of the results back, which would be zero. Or you could specify, you know, some other specific number. And you could also get -- provide a -- get back an error. And you'll notice in all my slides I have been not necessarily passing an error in.
Can also pass nil here. You set up the query, but the query does not actually run until you tell it to actually go do something. So that method is results allowing partial. And results allowing partial is what you would do to run a synchronous query. So in other words, you basically -- you have two options here.
If you say results allowing partial, No, it's going to do the query, and it's going to block until it gets all the results that it's going to get, and return them to you. OK, keep in mind, this is going to be sitting here blocking. And depending on what your application is doing, that might not be real convenient.
So you might want to consider doing an asynchronous query. The other option here is you can say results allowing partial, Yes. This is going to return results as they come in, but you're going to have to sit there and poll for them. And you know what, polling is no fun. So really, you just never want to say results allowing partial, Yes. My main point for even having this slide in here is to say don't do it, OK? We're going to actually have an example of an asynchronous query.
And that's what you should do instead of results allowing partial, Yes. Now if you have a query that's already set up, you can also have this query be called synchronize. And this means that it's going to dump any existing results and restart the query. And now that we've talked about all of these different fun things you can do with DirectoryServices, I think it's time for a demo. Fun with records. This first example is actually something that is -- how many people here would consider -- maybe you're from the IT track, you're an assist Admin -- oh, all right, great.
It's great to see you people here. All right, so this is something that you guys in particular are going to find interesting. This is the sort of thing you might want to do. What we're going to do is I have an Open Directory Master running here on my laptop, because you know, that's normal, right? Having -- everybody runs an LDAP server on their laptop.
And I've got a whole bunch of records in here. You know, and I've got some information for them, I've got some, like, for example, what department they're in, and I don't have any groups though, and I would kind of like to have some groups based on what department they're in. I'd like to have a group for the engineering department, maybe one more the marketing department, and you know, it would be a real pain to have to search around and find them, and drag them into a group a stuff like that.
So instead what we're going to do is we're actually going to write some code. And what we're going to do is write code to basically do a search, find all the users in a particular department, create a group, put those users in the group, and there we go. So, can everybody see -- is the font size big enough, or should I zoom in? How's that? All right.
So first thing that we're going to do is, we need to get a session. So pretty easy. We don't really care too much about any parameters for the session, we just want the default one. And for our searching, again, we just want to use the search node. The search node is whatever the user has specified they want to use for searches for information, for like user records and so on. So we're going to go ahead and we're going it use the search node, which is the ODType authentication node.
And now let's actually go ahead and do a query on the search node. So again, we're supposed to find the search node. And we're going to look for record types that are users. And we're going to search on an -- and this is a command line program, so we've got a bunch of arguments up here that I already have set up. So we're going to search for something that the user passes in.
So it's going to be the actual name of the department. And in this case, I've chosen -- we're going to make it -- it's going to have to be an equal match. So we're going to have to actually give the exact name of the department. And then -- I'm sorry -- actually the attribute that we're searching on is going to be passed in.
And that is going to be in this case the actual name of the -- the attribute that they want to search. So this is pretty flexible tool. They can pass in the actual full name of any attribute. In this case, we're going to be passing in something like DS Attribute Standard Department.
And then the search value is also being passed in. So that is the actual name of the department. And for the return attributes, we're just going to say, OK, just return them all, that's fine. And we want all the results. And I'm not going to check for errors here, but you should.
This is one of those do as I say, not as I do moments. OK? And then -- because this is just a simple little utility. We're going to go ahead and do a synchronous query here. So we're going to say results allowing partial, No. So we're going to wait for all the results. We're just going to block.
And we are just going to print out how many results we get here. OK, now we're going to actually deal with the group itself. So for that, we actually need to know, well, What node do we want to create this group in? So we're going to let the user actually pass in what node they want to create the -- the actual group that we're creating in.
So it could be in the LDAP node, it could be in the local node, wherever they want to put it. OK, so we -- for that we just need to specify the session. We use the same session, and the node is being passed in. So now let's go ahead and set the credentials. So the user is going to have to provide some information, some Admin credentials, a user name and password for an Admin user that has the authority to write to the directory node.
So in this case, we're saying we've got a user whose name is whatever the Admin name they pass in, and the password. And then we're going to go ahead and actually create this group in the directory. So we're just going to say create record with record type. It's a group.
And we're going to pass in a name for the group. And we don't care about the attributes, and again we're not checking for errors. But you should. And then we're going to go ahead and we're going to set some information on the group. I figure in addition to actually setting the group membership we should -- we should put a comment in there so we know we have this fabulous little command line utility that actually created the group for us.
So we're going to say that it was created by Supergrouper, and we're going to say what the search was that was done to create the group. And then we're also going to say what GID value we want to create for the group. Because if we don't specify the group ID, it's going to put in a value of 99, which -- you know, I mean, that works. But it's going to do that every time.
You're going to end up -- every time you create a group it's going to have the same GID, and they're supposed to be unique, that's probably not a good thing. OK, so we've been saying we want to make some changes, but that isn't actually going to do anything until we say synchronize and return error. And now we're going to do the actual heavy lifting of this little application. OK, we don't want an update in the middle of the demo.
That kind of got lost there. All right, create group. Oops, that's the wrong one. Let's -- so we're going to enumerate through all of the results that we got back from the query up here. So up here we got these results. Just going to enumerate through them. Going to get the user record for each of these results, and we're going to say add that member to the group. So add member record. And then again, this is a change to the directory, so we need to actually say synchronize.
So we're going it say synchronize and return error. All right, and that's all there is to it. So let's actually go ahead and let's build this. OK, and this is a command line utility, actually, so we're going to have to pull up a terminal window here. This zoom is -- I think I made my terminal type big enough here.
So let's go ahead and let's go in here -- oops. OK, so just to show you here, you know, nothing up my sleeve. We're going to go ahead and let's create it in the local node. We searched the LDAP node, so let's demonstrate we can do this with whatever node we want to do. We're going to put our group in the local node. And again, this is just another illustration that you can have users from another node in a group that is in a totally different node, it's totally legal. So let's see here, yup, there's no groups.
So let's go ahead and we are going to -- we're going to search on the department field, we're going to look for engineering users, and we're going create a group called engineering department. With a GID of 2001, and we're going to put it in the local node. Here's my supersecret Admin credentials. And if we come back here, voila. There's the comment that we put in there. And there's all of our fabulous users.
[ Applause ]
And if you take a look here you'll see that, yes indeed, it did the right thing when it added the group membership. We have both all of the GUIDs and all of the user's short user names. So that was all taken care of for you. All right. So that seems like enough demo for now.
It's like I did all this talking about synchronous queries, but I already hinted that there must this asynchronous way of doing queries. And one reason that it can be really handy to do this is if you have an application like, say, Work Group Manager here, where you might be getting a really long list of, say, all of the records of a particular tyoe. You don't want to have to sit there and block while you're waiting for all these results. You know, users, they're not necessarily really patient, they don't really like that.
So you need a way to do asynchronous queries. And luckily, we've provided you with one. So what you need to do is implement the ODQuery delegate protocol. And this is actually really simple. All you have to do to support this is, you have to provide a method called found results. And it's going to basically take these results, which is in the form of an array.
And then you do whatever it is you want to do with them, OK? So there's really nothing real fancy going on here, you're just providing a place for DirectoryServices to hand you the results. OK? To actually do the query, you have your session in your node, set up your query the same way that you would set up a synchronous query. So you're going to specify the same pieces of information.
And you're going to retain it. And then what you want to do is set your delegate, which could be yourself, could be whatever else -- wherever else you're implemented the ODQuery delegate, and then you're going to go ahead and schedule it in your run loop, OK? And then DirectoryServices will do the query, and it will send your information to whatever you've set as your delegate.
Again, we have the synchronized method that could be called, and again, this means that you're going to want to discard any results that you have gotten, because there's going to be a whole new set of results coming in, there's something new happening. So you can tell that this has been called, because your delegate is going to be called with a results of nil, and you're going to have an error code KOD error query synchronize, with a domain of ODFramework error domain, OK? So this is just a single to you that, Hey, you know what, we're restarting the query. So whatever results you've already gotten back, just throw them away.
Because you're going to get new results here. And speaking of error codes, we have a whole new set of error codes for you. They're not your old 14-0 something, something, something, errors. So take a look at the documentation. They're pretty similar to the same times of errors, but again, you'll see different error codes popping up.
And everybody like the little bomb icon? I would to ask specific specially for that, so I hope you're impressed.
[ Applause ]
Thank you. And we have documentation for you. This is brand new shiny documentation that they just finished in the last week or so. So check it out, we have documentation for both the Cocoa and CoreFoundation versions of the framework.
I want to point out a couple of gotchas, things that developers tend to hit pretty frequently when they first start using the ODFramework. One of them, if you're using the Cocoa version of the framework, make sure you actually check and see which things are auto released and which things aren't, because sometimes people forget that something maybe is auto released, and they go ahead and release it themselves, and bad things happen. So make sure that you keep track of what you actually need to worry about releasing.
And then another reminder is just keep in mind if you're trying to figure out what node to use, unless you're actually writing data you probably just want to use the search node. Search node you can't use for writing data, because it could be a whole list of different nodes. But if you're doing anything with searching, well, you want to use the search node, which is probably why we call it the search node.
And then finally, just a reminder, don't do synchronous queries that are going to block, unless you realize that that's what's going to happen and that's what you want. Because you could have a query that's going to take a long time, you're going to have a user maybe getting impatient, and really, it may be better to just do an asynchronous query using the run loop.
All right, so now since I keep pushing this asynchronous query idea, we should probably do a demo so you can see what that looks like. So what we have here -- and I'm actually going to go ahead and cheat and build and run it first, so you can see what it is we're making here. This is just a simple little browser of OD users. So I can start typing, and it will start sorting through the users. And as the results come in, it will display them.
OK. So the great thing about this application is when we look at the code here, the vast majority of all the code that it took to write all of this, it's all Cocoa bindings. There's like, I don't know, maybe like three or four lines of actual ODFramework code in here. So the first thing is in the header you can see, Oh, yup, we're going to implement ODQuery delegate.
And we're going to actually go ahead and here's the method that we need for the ODQuery delegate, which is just saying, Hey, what results did you get from the query? So it's just accepting results from DirectoryServices. And we've only got one other method in the whole class, and that is the actual search. So basically we've got two methods. One does the search, and one gets the results back.
All right, so let's take a look at our search here. And this should look pretty familiar. Again, most of this stuff is actually Cocoa binding stuff. So we've got some, you know, Cocoa binding stuff for the UI. But basically the first thing we're going to do is, we're just going to check and see is there already a query set up, because you know, people, they might be doing all kinds of different things, they might do one query and then do another. So we're going to look, see if there's a query.
And if there is, we're just going to kind of reset some stuff in the bindings, we're going to take it out of the run loop, we're going to release it, and we're just going it reset everything back to nil, OK? So that's what we're doing here, remove from run loop, and the release.
OK, so that's just cleaning up in case we had something else going on. Now we actually do the actual query itself. And again, when you set up the query, this looks the same whether you're doing synchronous or asynchronous, you're just going to be specifying, you know, what node are you querying.
You're going to specify what record times you're looking at, what attribute you're searching on, in this case, we're searching the record name. What kind of a match you're looking for. So in this case we're saying just contains, right? Because we wanted to find basically build results or sort results down, as we typed we'd have it sort of eliminating results. And then the search value, this is what the user is actually typing in the little search box in the UI. And then what attributes we want to get back.
And you'll see in the browser, we're actually going to go ahead and for each record, let you sort of drill down and see all the attributes in a record, and then all the values of the attributes. And we want all the results, so we're going it say zero for maximum -- all the results. And again, I'm not checking for errors here, but please do. All right, so we do the retain, and since we just implemented this all in one little class here, we're going to say that we ourselves are the delegate. And then we schedule it in the run loop.
And we're just going to use the default run loop mode here. And then we have some more Cocoa binding stuff. This is all Cocoa binding stuff, this is all Cocoa binding stuff, OK? So really, I think we have like, what, one, two, three -- we have like four lines of actual ODFramework code here.
And then now we're going to look at the method that actually gets the results back from DirectoryService. So really, here, almost all of this is Cocoa binding stuff, right? Because we're getting an array, and then we're going to do whatever we want to do with it. So Cocoa binding stuff is going on here, we're just setting up the UI, and really the only ODFramework stuff here is we're going to actually get the details out of the actual records that we were returned, OK? So that we can display them. And we're going to enumerate through them, so we can get all of the different attributes. So we're going to do an enumeration here. See all of this.
And we have some more Cocoa binding stuff. We're just reading all of the attribute values out and we're going to display them. So let's just take a look at what this looks like again. We've got -- Steve Wozniak, oh, we've got a bunch of different attributes. Let's go ahead and make this a little wider, OK. And Steve has a -- he's in the dance department. Didn't know that.
[ Laughter ]
We can also find Clara -- oops, maybe they don't have that user. Oh, wait -- oh. Tough decision here.
Look at Clara's and -- let's see here. Clara also has a department, she's in developer relations. OK, so basically that was just a really simple UI example of using the asynchronous queries, and like you saw, you know, they were really like, four or five lines of code total that actually had to do with DirectoryServices. Most of the work is just whatever you want to do with these results. So the main thing here is ODFramework is there to do what you want, and then get out of the way and you do whatever you want to do with the data.
So -- so we've covered the Open Directory Framework. The -- I want to talk just real briefly about another related API, because there are times that when using the Open Directory Framework there's a couple of specific cases you may fall into where we have this other API that may do something you want really easily.
And there's also some cases where you may not even need to use the Open Directory Framework, OK? And the membership API handles really two types of work. One is anything having to do with resolving group membership. And the other one is what we call identity conversions, which is converting things like a user's name into their UID, and then also maybe into their GUID and SID, and so on.
So for group membership, you know, the reason that we have this API is because, you know, once upon a time this was really easy, right? You just had a list of all of the user names that were in a group, a flat file, it was really easy. You didn't have that many users in a particular group, and you just had users in the group.
So you could just parse the file. But nowadays things are pretty complex. So if you have a group that has thousands and thousands of users, and any particular user might have thousands of group memberships. You can also nest a group within a group within a group within a group -- and so on. And you can also have things like computers in your group as well as users. And the other thing as we saw in our demo earlier, you can have groups that are actually spanning different nodes.
So you could put users from the LDAP node in your local node, or you could put users from Active Directory into a group in your open directory. So this is all pretty complex stuff. And if you had to figure all this stuff out on your own it would be pretty tedious, and it would just be a lot of work. So we've done this for you. Because you might have a group that looks like this, where you've got multiple layers of nesting, you've got some computers in there, and you don't want to have to sort through all of this.
So this is what the membership API does for you. But also you know what, users are a little more complex too, because once upon a time all you had to worry about was, a user had a user name and they had a unique ID. Pretty simple. But you know, things got really complex, we got these really big directory systems, plus we decided well, these unique IDs really aren't unique enough. So we added this thing called the GUID, or the UUID.
And this is a 128 bit GUID, so that also has to somehow be associated back with this same user name. Because you don't refer to a user as EFB9 -- yeah, that doesn't work very well. So if you have that UUID, you maybe need to know who is this actual user. But then we also have -- you know, we have this other platform that's out there called Windows a few people use it, you may have heard of it. And their unique identifier is the SID.
So this is yet another unique identifier that a user might have that we need to be able to say well, these with SID is the same user that has this GUID. OK? And then we also have Kerberos out there, which is really popular for authentication methods, and here at Apple we use it very heavily in DirectoryServices.
So users also have a Kerberos identity, which is the Kerberos principle. And in fact, they might have multiple ones. So you need a way to sort all of these particular user identifiers to be basically mean the same user. And sometimes you may get one of these values and need to translate it into another one. So that is what the membership API does for you.
So this is the membership.h is the header. And that it will do for you is it's going to hide the group structure. You don't need to worry about is there a whole bunch of nesting going on in here, is there users that are in some other node. You just say, Hey, is this user in this group? Around you're going to get back an answer. It also, on the performance side, provides caching, so every time you look up group information, you don't have to keep, you know, have DirectoryServices keep going out to the server and check all the users in the group.
And you know, all this extra overhead. So we have caching here which helps with performance. And then we also have the membership API doing all of this translation between, for example, your UID and your GUID, and your SID. So there's only a few calls in the whole API. And one of these is member check membership.
So what you're going to do is you're going to pass in the user GUID, the group GUID, and then someplace to get the answer back in, so is- member. And so if it actually -- if in fact the user is in the group, then we're going to say, Zero. So yes, they are a member. One means they are not a member.
And you can also get a separate error value that will be zero if everything's fine. But you could also get EIO, you know, depending on if things have gone wrong with DirectoryServices. Also, by the way, instead of passing a user in with the group, you could pass in a group and find out if a group is inside a group.
Then like I said, the other thing besides checking membership that you can do here is check and see, you know, I've got this UID number, but what I really need is I need the GUID. So you can pass in the UID number, and we will give you back the user's GUID.
You can also, for example, pass in the GUID, get the SID. And there's a really useful member -- membership API call here, which is member identifier to UUID. And so what you can do is basically just specify what type of identifier you're passing in. Like, for example, the Kerberos identity. And then you have to tell us what it is and how long it is.
And then we will give you back the UUID or the GUID. OK, so basically this gives you the ability to very quickly and easily translate back and forth between all of these values. And you may say, Well, you know, I could probably do all of this on my own using the Open Directory Framework, right? And actually, the Open Directory Framework, when it does its group membership checking, it's using the membership API.
So if you're using the is-member method in the Open Directory Framework, it is in fact using the membership API, you don't need to worry about separately using the membership API for that. But you may have cases where, for example, you have a UID or a UUID, and you need to get one of the other identity values, well, the Open Directory Framework, you've got to do a query, you've got to look at the identity, you know, the actual values and the attributes, and then there's a whole lot of calls that you could actually do in just one.
And also for example, you might be doing something -- you don't care about, for example, any of the other values in the user record, just want to know, Hey, I've got a user name, got a group. Is this user in the group? That's all you want to know. And for that you don't need to go through all of the extra stuff that you would do in the Open Directory Framework.
But the main reason you want to use the membership API is just -- this will give you all of that modern user and group functionality, so you don't have to worry about the fact that, you know, if you tried to parse group information yourself, you might miss the fact that there's a nested group.
And so membership API just takes care of all of that for you, and gives you performance benefit. So really, it's just the easiest way of handling all of these types of tasks. OK, so we've covered the APIs, now we're going to step back a little bit, because besides just actually writing some code, you've got to know, well, How do I actually, you know, configure DirectoryServices and get information about what's going on? So for starters, something that changed in Snow Leopard -- how many people were familiar with direct utility? Yeah. So that was an application that existed in Applications Utilities.
That was how you always set up, for example, talking to a particular directory server. Well, directory search is now so important and so central to OS X that we made it part of System Preferences, and we added it to the accounts pane. So we go to the accounts pane log in options, you'll see that you can now do network account server, and if you say Join, you can specify a server that you want to connect to. And as you type we will figure out, Hey, it's an Active Directory server, or, Hey, it's an open directory server, and provide you with the appropriate information that you might need to provide in order to bind to it.
Now some people have some more complex things that they need to do. So we have a couple other options that you can do here. You can use DS config LDAP and DS config AD. These are command line tools for setting up Active Directory and LDAP connections. These are really handy if you need to script something. We also have the full blown directory utility. It is still around, it just sort of moved. It now lives in System Library Core Services. But you can also access it through a button from System Preferences.
Another thing that you might need to check or configure is -- we kept talking about -- we have this search node thing that you might be using. And I said this is something that the user determines what nodes are in the search nodes of the -- so what nodes, what servers do they actually want used for authentication? And this could be all of the servers they have configured, or it could be some subset of them. So this is actually the search policy tab in Directory Utility. And then you want to go to the subtab of authentication. So this is where you can actually specify which of the nodes you have configured are actually going to be part of the search node.
And this is something that if you're troubleshooting you might want to check, just to make sure that all the nodes that you think are there, are in fact there. OK, there's a few tools I just want to cover briefly. Things that are, you know, useful, as you're programming, to get some information about DirectoryServices, or to, for example, put some test information in there so that you can test your code against it. So the first one is Work Group Manager.
And Work Group Manager, in addition to the regular modes that it has, some people don't realize it has this inspector mode, which lets you look at all of the different records in the node, and then see all of the different attributes and edit them. So some of the things that you normally -- users don't have access to edit or read, you can actually get to from the inspector mode, and this can be really, you know, useful, when you are programming or troubleshooting to actually verify or to actually set a particular value for testing purposes.
So this gives you a nice UI way of doing it. One utility that I actually use a lot, because sometimes you need a quick sanity check, right? OK, my code didn't work, am I actually connected to the server So the ID command is just a real quick way of saying OK, ID, user name.
And see, Hey, are we actually able to resolve the user? If it can't, then maybe there's something wrong with the server. This is also a quick way to get information about what groups a user is in. DSCL is the DirectoryService Command Line utility. It's kind of like the Swiss army knife of DirectoryService utilities.
You can do kind of just about everything in it. The main things that you can do is you can do searches, and look up records, and get back a list of all the attributes. You can test authentication, so you can make sure that you are actually able to authenticate to a particular node as a particular user.
And you can also edit any of this data. So if you do a search you actually get, for example, all of the information in a particular record. So this is just a very easy command line way to get access to kind of all parts and all records in a node. This also by the way has both an interactive mode and a non-interactive mode. So it's handy for whether you just want to sit there and check something yourself manually, or if you want to script things.
Another handy utility is the dsmemberutil. If you're doing a lot of stuff with groups, and maybe you're not getting the results you expect, could be you have some stale membership cache. And you want to, you know, just flush that out, start clean, so you can use dsmemberutil flush cache to flush that.
And start fresh. But you can also use it to check your group membership. See if a particular user is in a particular group. And you can also do all of those identity look ups that we talked about with membership API, you can do this from the command line, and this very handy, for example, when you're doing a quick sanity check. Also related to group things, we have dseditgroup. And this basically lets you edit and check membership of a groups from the command line. So you can edit a group, you can add users, add a group, move users, remove a group.
And you can also check membership with dseditgroup. So finally, we've done all this, you've set up all of your nodes, you've written some code, and you've put some test data in your DirectoryServices, and now you know, things aren't quite working the way you expect, so you might want to see what DirectoryServices is doing. So you can turn on DirectoryService debug logging.
To do this, send the USR1 signal to DirectoryService. This is like an on/off switch. So if you send it once, it turns it on. If you send it again, it turns it off. If DirectoryServices restarts or crashes somewhere in there, then it's going to be off. So sometimes you actually want DirectoryServices to just have the debug logging on all the time. So to do that, you need to set a flag file, which is DS log debug at start. And this is in Library Preferences DirectoryService.
This will turn on DirectoryService debugging right away, as soon as DirectoryService starts up. You can also -- if you only want this to happen the first time that DirectoryServices starts up, this would be dot DS log debug at start once. Now if you need more or less information in your debug log, because DirectoryService debugging can be pretty verbose. So you might want to adjust it up or down. You have seven levels of logging, from zero to seven. And for this, this is the DirectoryService debug plist in Library Preferences DirectoryService.
And you want to set the key debug logging priority level. If you're using the default command to do this, make sure that you specify this is an integer, because if it goes in as some other data type, bad things can happen when DirectoryServices tries to read it. And then finally you've done all this, you've turned all your logging on. The logging goes to library logs, DirectoryService, DirectoryService..debug.log. These log files will actually roll, so it's OK to go ahead, turn it on, leave it on for an extended period of time.
You don't have to worry about them taking over the hard drive of your machine. And then actual debug log kind of looks something like this. Which I don't know, looks totally clear to me. But you know, there's a lot of stuff going on in DirectoryServices at any given time. Because there might be lots of other stuff calling into DirectoryServices besides you.
So it can be actually very useful to be able to narrow that down to just your process, what is happening with just your process. So you can do this by basically starting up DirectoryServices in a terminal window, with the Apple debug flag, and then open another terminal window, set an environment variable called DS debug 1, and go ahead and run your process. And then you'll see in the other terminal window, you'll see just the debugging information that is relevant to your process.
OK, wrapping up, I just want to give you a few quick tips for things that you want to look out for as you're writing your code. So first thing you want to do is make sure you're playing nice with everybody, because there's lots of different directory servers out there, want to make sure you don't do something that excludes, for example, people that are using Active Directory. Because that's pretty popular.
And in fact, Active Directory is so darn popular, but you know, they do things a little differently. So they have a few different naming conventions, they have some different off methods than we usually use, they can have these really complex multi-domain forests, and their group membership can be pretty gnarly. The good thing is that the 80 [phonetic] plug-in can sort of shield you from most of this, sorts most of this out.
But a lot of these dos and don'ts that I'm going to be giving you have to do with things that the 80 [phonetic] plug-in particular is frequently a reason for these dos and don'ts. So let's cover some of these. So the first thing is like I said, when in doubt, if you're doing a search, use the search node.
It really -- it's going to save you time anyway, because you don't want to sit and figure out which nodes are available. Another thing, make sure you're using common off methods, because not all nodes use the same types of authentication. Don't try to create non-standard attributes or store information in attributes that maybe don't exist in all directory servers.
Be careful when you try to pull all records of some record type, or all records in a server, could this could be millions and millions of records which you may not actually want, or you may think you're to going to get the millions and millions of records, but the server has a search policy that maybe limits you to the first thousand, like Active Directory.
So you may not get the results you expect. Another thing you want to do is keep in mind that if you do something that's going to disrupt DirectoryService, like take a network interface up and down, that's going to cause DirectoryService to have to reconfigure itself, plug-ins may reconfigure themselves, and that may take them a certain amount of time to do that. So if you can avoid it, don't do anything that's going to disrupt DirectoryServices. And keep in mind, you can always check to see if nodes are available.
OK, another thing is we have all these very complex Active Directory domains and forests out there. Don't make any assumptions about how many Kerberos realms or DNS realms, or that they match, because you could have lots of domains and lots of Kerberos realms out there, and you could have a disjoint domain where the DNS host name does not necessarily watch the Kerberos realm.
One thing that bites a lot of people, especially with Active Directory; people have this idea that certain records follow certain conventions, like for example, no spaces in short names or no back slashes. Well, actually it's pretty popular in Active Directory to have spaces in short names, and when we give you, for example, a list of groups from Active Directory, we put the domain name and a back slash, and then the group name. So they all have back slashes.
So be aware of that when you do your parsing to keep that in mind. And then finally, use the Open Directory Framework or the membership API to figure out membership, don't try to figure that out on your own. And if you're trying to convert user identities, use that membership API, because it's really just going to make things a lot easier for you, plus you're going to get the benefit performance-wise of caching. OK, for more information, you can contact Mark Malone. 1