对于 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;
}
希望这对任何人都有帮助...
我在这个 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;
}
希望这对任何人都有帮助...