对于 Mac 上的所有用户,我如何以编程方式获取(Objective-C 或 Swift)用户所属组的列表?

How can I programmatically obtain (Objective-C or Swift) the list of groups a user is member of, for all the users on the Mac?

我在这个 question 中找到了这个可爱的代码,它可以帮助我获取用户列表。

#import <OpenDirectory/OpenDirectory.h>
ODSession *s = [ODSession defaultSession];
ODNode *root = [ODNode nodeWithSession:s name:@"/Local/Default" error:nil];
ODQuery *q = [ODQuery queryWithNode:root forRecordTypes:kODRecordTypeUsers attribute:nil matchType:0 queryValues:nil returnAttributes:nil maximumResults:0 error:nil];

NSArray *results = [q resultsAllowingPartial:NO error:nil];
for (ODRecord *r in results) {
    NSLog(@"%@", [r recordName]);
}

现在 Terminal/Shell,我可以简单地:

id -G <username or userID>

立即获取用户所属组的列表,例如

>id -G 501 
20 12 61 79 80 81 98 702 701 33 100 204 250 395 398 399 400

但是,继续生成 NSTask 运行“id”,然后收集输出并解析,这看起来很愚蠢,只是因为我不知道如何使用 OpenDirectory API...

所以我挖得更深一点,发现我可以在 ODQuery 的 returnAttributes: 参数中指定一个或多个“属性”...然后我浏览了一长串属性,只找到了在 CFOpenDirectoryConstants.h:

中有意义的一个
/*!
    @const      kODAttributeTypeGroup
    @abstract   List of groups.
    @discussion List of groups.
*/
CF_EXPORT
const ODAttributeType kODAttributeTypeGroup;

但是,当我将它添加到 returnedAttributes 的“standard”或“nativeOnly”或“All”组时,甚至单独指定它时 - 我收到的结果 ODRecords 不包含该属性。

我尝试使用 [r valueForAttribute: kODAttributeTypeGroup]; 获取它,并且当我打印 [r description] 时我可以看到返回的所有属性 - 可惜它不在那里。

我也尝试了其他属性,比如

kODAttributeTypeGroupMembers、kODAttributeTypeGroupMembers、kODAttributeTypeNetGroups、kODAttributeTypeGroupMembership 和其他 - 但它们都更希望出现在 OpenDirectory 的“组”记录中,无论如何它们都不会返回。

所以我的问题是:如何说服 OD 为每个用户检索组?任何提示(或对有用文档的参考)将不胜感激。谢谢!

如果你改变你的循环如下

        for (ODRecord *r in results) {
            NSLog(@"%@", [r recordName]);
            
            // Query groups
            ODQuery * gq = [ODQuery queryWithNode:root
                           forRecordTypes:kODRecordTypeGroups
                            attribute:kODAttributeTypeGroupMembership
                            matchType:kODMatchContains
                          queryValues:r.recordName
                         returnAttributes:nil
                           maximumResults:0
                            error:NULL];
            NSArray * gr = [gq resultsAllowingPartial:NO
                                error:NULL];
            
            for ( ODRecord * g in gr ) {
                
                NSLog(@"\t%@",g.recordName);
                
            }
        }

更新

如果您想要类似于 id 的内容,您需要单独查询每个组。这是一个示例,只是强加到您的结构中。

        for (ODRecord *r in results) {
            NSLog(@"%@", [r recordName]);
            
            // Query groups
            ODQuery * gq = [ODQuery queryWithNode:root
                           forRecordTypes:kODRecordTypeGroups
                            attribute:nil
                            matchType:0
                          queryValues:nil
                         returnAttributes:nil
                           maximumResults:0
                            error:NULL];
            NSArray * gr = [gq resultsAllowingPartial:NO
                                error:NULL];
            
            for ( ODRecord * g in gr ) {
                
                if ( [g isMemberRecord:r
                         error:NULL] ) {

                    NSLog(@"\t%@",g.recordName);

                }
            }
        }

这说明了如何操作。问题在于每次都会检索这些组。你可以例如轻松缓存。

注意这两个查询很重要。在第一个中,我查询组成员资格,在第二个中,我查询组并使用每个组来测试成员资格。似乎第一个更直接,第二个更强,因为它更完整/准确。我认为因为嵌套在组中所以更准确。

虽然我完全接受 skaak 的回答,但我想我会提供他的代码的精炼版本,它更简洁一些,并且避免为每个用户重新获取所有组,而且我会提供另一个版本的答案我的问题的一个更具体的变体(我需要实现的那个)希望这个代码片段能帮助别人。

首先,skaaks 的工作版本回答:

-(BOOL)groupsOfAllUsers {
    NSError *error = nil;
    ODSession *s = [ODSession defaultSession];
    ODNode *root = [ODNode nodeWithSession:s name:@"/Local/Default" error:nil];
    ODQuery *q = nil;

    // fetch all user records.
    NSArray *attributesToFetch = @[kODAttributeTypeUniqueID, kODAttributeTypeRecordName];
    q = [ODQuery queryWithNode:root forRecordTypes:kODRecordTypeUsers attribute:kODAttributeTypeRecordName matchType:0 queryValues:nil returnAttributes:attributesToFetch maximumResults:0 error:&error];   //kODAttributeTypeStandardOnly
    if (error!= nil)
        return NO;
    
    NSArray *userRecords = [q resultsAllowingPartial:NO error:&error];
    if (userRecords==nil || error!= nil)
        return NO;
    
    // fetch all groups
    attributesToFetch = @[kODAttributeTypeUniqueID, kODAttributeTypePrimaryGroupID];
    q = [ODQuery queryWithNode:root forRecordTypes:kODRecordTypeGroups
                     attribute:nil matchType:nil queryValues:nil
              returnAttributes:attributesToFetch maximumResults:0 error:&error];
    if (q == nil || error!= nil)
        return NO;

    NSArray *groupRecords = [q resultsAllowingPartial:NO error:&error];
    if (groupRecords == nil || error!= nil)
        return NO;
    
    NSMutableDictionary *userGroupIDs = [[NSMutableDictionary alloc] initWithCapacity:userRecords.count];
    NSMutableDictionary *userGroupNames = [[NSMutableDictionary alloc] initWithCapacity:userRecords.count];
    
    for (ODRecord *userRecord in userRecords) {
        NSMutableSet *gids = [[NSMutableSet alloc] initWithCapacity:groupRecords.count];
        NSMutableSet *groupNames = [[NSMutableSet alloc] initWithCapacity:groupRecords.count];
        for (ODRecord *groupRecord in groupRecords) {
            BOOL isMember = [groupRecord isMemberRecord:userRecord error:&error];
            if (error != nil)
                return NO;
            if (isMember) {
                [groupNames addObject:[groupRecord recordName]];
                id gid = [[groupRecord valuesForAttribute:kODAttributeTypePrimaryGroupID error:&error] firstObject];
                if (nil == gid || error!= nil)
                    return NO;
                [gids addObject:@([gid intValue])];
            }
        }
        id uid = [[userRecord valuesForAttribute:kODAttributeTypeUniqueID error:&error] firstObject];
        if (nil == uid || error!= nil)
            return NO;
        [userGroupIDs setObject:[gids copy] forKey:@([uid intValue])];
        [userGroupNames setObject:[groupNames copy] forKey:[userRecord recordName]];
    }
    return YES;
}

但是,正如 skaaks 的回答(以及我的许多评论)所指出的,isMemberRecord: 方法考虑了递归组(包含组的组)以及非主要组成员资格,它不适合我的需要。我想要的是找到哪些用户参与一组预定义的组(比如 {"staff","admin"},例如,排除主要组为 'root' 的“root”用户 (uid 0),尽管它也是 'staff' 的成员。所以 - 我的另一个变体(接收一组组名)看起来像这样,并且运行得更快:

-(BOOL)usersInGroupNames:(NSArray *)inGroupNames { // e.g.  @[@"staff", @"admin"]
    
    // stuff needed for all our OD work...
    NSError *error = nil;
    ODSession *s = [ODSession defaultSession];
    ODNode *root = [ODNode nodeWithSession:s name:@"/Local/Default" error:nil];
    ODQuery *q = nil; NSArray *results = nil;
    
    // First fetch desired groups, their names and group-ids
    NSArray *attributesToFetch = @[kODAttributeTypePrimaryGroupID, kODAttributeTypeRecordName];
    q = [ODQuery queryWithNode:root forRecordTypes:kODRecordTypeGroups
                     attribute:kODAttributeTypeRecordName matchType:kODMatchEqualTo queryValues: inGroupNames
              returnAttributes:attributesToFetch maximumResults:0 error:&error];
    if (q == nil || error!= nil)
        return NO;
    
    if (nil == (results = [q resultsAllowingPartial:NO error:&error]) || error!= nil)
        return NO;

    NSArray *groupIDs = [results valueForKeyPath: [NSString stringWithFormat:@"@unionOfArrays.%@", kODAttributeTypePrimaryGroupID]];
    
    // now fetch users whose primary group IDs are in the list
    attributesToFetch = @[kODAttributeTypeUniqueID, kODAttributeTypeRecordName, kODAttributeTypePrimaryGroupID];
    q = [ODQuery queryWithNode:root forRecordTypes:kODRecordTypeUsers
                     attribute:kODAttributeTypePrimaryGroupID matchType:kODMatchEqualTo queryValues:groupIDs
              returnAttributes:attributesToFetch maximumResults:0 error:&error];
    if (q == nil || error!= nil)
        return NO;
    
    if (nil == (results = [q resultsAllowingPartial:NO error:&error]) || error!= nil)
        return NO;
    
    NSArray *userIDs = [results valueForKeyPath: [NSString stringWithFormat:@"@unionOfArrays.%@.intValue", kODAttributeTypeUniqueID]];
    NSLog (@"userIDs:%@", userIDs);
    return YES;
}

希望这对任何人都有帮助...