User groups are easy, right? A user is either a member or they are not.
Once you start thinking about the details and want or need to automate some of the aspects of user and group management on macOS, there is a lot of devil in those details.
User Membership
You can easily list all groups a given user is a member of. The id
command will show all the groups the current user is a member of. id -Gn
will list just the groups. Add a username to the id
command to see the information for a different user. The groups
command does the same as id -Gn
.
You can also run a command to check if a given user is a member of a group:
$ dseditgroup -o checkmember -m user staff
yes user is a member of staff
$ dseditgroup -o checkmember -m user wheel
no user is NOT a member of wheel
Group Membership
So far, so good.
A user is a member of a group when one of these applies:
- the user’s
PrimaryGroupID
attribute matches thePrimaryGroupID
of the group - the user’s UUID is listed in the group’s
GroupMembers
attribute and the user’s shortname is listed in the group’sGroupMembership
- the user is a member of a group nested in the group
Note: you should not attempt to manipulate the
GroupMembers
orGroupMembership
attributes directly. Use thedseditgroup -o edit
command to manage group membership instead.dseditgroup
syntax is weird, but it is a really useful tool. Study itsman
page.
Listing Group Members
Sometimes (mainly for security audits) you need to list all the members of a group. With the above information, it is easy enough to build a script that checks the PrimaryGroupID
, the GroupMembership
attribute and the recursively loops through the NestedGroups
.
This is confused by the fact that PrimaryGroupID
stores the numeric User ID, GroupMembership
uses the shortname and NestedGroups
uses UUIDs. Nevertheless, you can sort through it.
I have written exactly such a script here:
In most cases this script will work fine. But, (and you knew there would be a “but”) macOS has a very nasty wrench to throw in our wheels.
Calculated Groups
There are a few groups on macOS, that have neither GroupMembers
, GroupMembership
, nor NestedGroups
, but still have members. This is because the system calculates membership dynamically. This is similar to Smart Playlists in iTunes, Smart Folders in Finder, or Smart Groups in Jamf Pro.
You can list all calculated groups on macOS with:
$ dscl . list /Groups Comment | grep "calc"
The most interesting calculated groups are everyone
, localaccounts
, and netaccounts
.
These groups can be very useful in certain environments. For example in a DEP setup you could add localaccounts
or everyone
to the _lpadmin
and _developer
groups, before the user has even created their standard account. That way any user created on that Mac will can manage printers and use the developer tools.
However, since these groups are calculated magically, a script cannot list all the members of any of these groups. (My script above will show a warning, when it encounters one of these groups.)
While it would probably not be wise to nest the everybody
group in the admin
group, a malicious user could do that and hide from detection with the above script (or similar methods).
Other Solution
Instead of recursively listing all users, we can loop through all user accounts and check their member status with dseditgroup -checkmember
. This script is actually much simpler and dseditgroup
can deal with calculated groups.
This works well enough when run against all local users.
I strongly recommend against running this for all users in a large directory infrastructure. It’ll be very slow and generate a lot of requests to the directory server. Because of this the script above runs only on the local directory node by default.
Summary
- on macOS users can be assigned to groups thorugh different means
- you can check membership with
dseditgroup -o checkmember
- you can edit group membership with
dseditgroup -o edit
- macOS has a few groups which are dynamically calculated and difficult to process in scripts