|
| 1 | +{-# OPTIONS_GHC -Wno-missing-signatures #-} |
1 | 2 | -- | |
2 | 3 | -- Module : Streamly.Coreutils.Id |
3 | 4 | -- Copyright : (c) 2022 Composewell Technologies |
@@ -32,6 +33,14 @@ module Streamly.Coreutils.Id |
32 | 33 | -- * Login name of the current process user |
33 | 34 | , loginName |
34 | 35 |
|
| 36 | + -- * Lookups by user id |
| 37 | + , userNameFromId |
| 38 | + , groupNameFromId |
| 39 | + , primaryGroupIdFromUserId |
| 40 | + , primaryGroupNameFromUserId |
| 41 | + , groupIdsFromUserId |
| 42 | + , groupNamesFromUserId |
| 43 | + |
35 | 44 | {- |
36 | 45 | -- * Numeric ids of the current process |
37 | 46 | , realUserId |
@@ -108,32 +117,108 @@ import qualified System.Posix.User as Posix |
108 | 117 | -- router's return type and are kept as standalone functions rather than |
109 | 118 | -- forced into a polymorphic or awkwardly-typed router. |
110 | 119 | -- |
| 120 | + |
111 | 121 | ------------------------------------------------------------------------------ |
112 | | --- Internal helpers |
| 122 | +-- Lookups by user / group id |
113 | 123 | ------------------------------------------------------------------------------ |
114 | 124 |
|
115 | | --- Look up the user name for a uid, returning Nothing if no entry exists |
116 | | --- rather than throwing. See "Maybe for DB lookups" in the module header. |
117 | | --- |
118 | | --- Note: this is a user-DB lookup and logically belongs in a future |
119 | | --- Streamly.Coreutils.UserDB module. It is kept internal here so the |
120 | | --- migration is not a breaking change for callers. |
| 125 | +-- These functions are user/group-DB lookups. They logically belong in a |
| 126 | +-- future Streamly.Coreutils.UserDB module and will likely move there; a |
| 127 | +-- re-export from here may be kept for backwards compatibility when that |
| 128 | +-- happens. |
| 129 | + |
| 130 | +-- | Look up the user name for a uid. Returns 'Nothing' if no matching |
| 131 | +-- user-db entry exists. |
121 | 132 | userNameFromId :: Int -> IO (Maybe String) |
122 | 133 | userNameFromId i = do |
123 | 134 | r <- try (Posix.getUserEntryForID (fromIntegral i)) |
124 | 135 | return $ case r of |
125 | 136 | Left (_ :: SomeException) -> Nothing |
126 | 137 | Right ue -> Just (Posix.userName ue) |
127 | 138 |
|
128 | | --- Look up the group name for a gid, returning Nothing if no entry exists |
129 | | --- rather than throwing. Same migration note as 'userNameFromId'. |
| 139 | +-- | Look up the group name for a gid. Returns 'Nothing' if no matching |
| 140 | +-- group-db entry exists. |
130 | 141 | groupNameFromId :: Int -> IO (Maybe String) |
131 | 142 | groupNameFromId i = do |
132 | 143 | r <- try (Posix.getGroupEntryForID (fromIntegral i)) |
133 | 144 | return $ case r of |
134 | 145 | Left (_ :: SomeException) -> Nothing |
135 | 146 | Right ge -> Just (Posix.groupName ge) |
136 | 147 |
|
| 148 | +-- Internal: look up the full passwd entry for a uid, returning Nothing on |
| 149 | +-- miss rather than throwing. |
| 150 | +userEntryFromId i = do |
| 151 | + r <- try (Posix.getUserEntryForID (fromIntegral (i :: Int))) |
| 152 | + return $ case r of |
| 153 | + Left (_ :: SomeException) -> Nothing |
| 154 | + Right ue -> Just ue |
| 155 | + |
| 156 | +-- | Primary group id of the user with the given uid. This is @pw_gid@ |
| 157 | +-- from the user's passwd entry. Corresponds to @id -g \<uid\>@. |
| 158 | +-- |
| 159 | +-- Returns 'Nothing' if no passwd entry exists for the uid. |
| 160 | +primaryGroupIdFromUserId :: Int -> IO (Maybe Int) |
| 161 | +primaryGroupIdFromUserId i = |
| 162 | + fmap (fromIntegral . Posix.userGroupID) <$> userEntryFromId i |
| 163 | + |
| 164 | +-- | Primary group name of the user with the given uid. Corresponds to |
| 165 | +-- @id -gn \<uid\>@. |
| 166 | +-- |
| 167 | +-- Returns 'Nothing' if no passwd entry exists for the uid, or if the |
| 168 | +-- primary gid has no matching group-db entry. |
| 169 | +primaryGroupNameFromUserId :: Int -> IO (Maybe String) |
| 170 | +primaryGroupNameFromUserId i = do |
| 171 | + mGid <- primaryGroupIdFromUserId i |
| 172 | + case mGid of |
| 173 | + Nothing -> return Nothing |
| 174 | + Just gid -> groupNameFromId gid |
| 175 | + |
| 176 | +-- | All group ids the user with the given uid belongs to: the primary |
| 177 | +-- group from the passwd entry, plus every group in the group database |
| 178 | +-- whose member list contains the user name. Deduplicated. Corresponds |
| 179 | +-- to @id -G \<uid\>@. |
| 180 | +-- |
| 181 | +-- Returns 'Nothing' if no passwd entry exists for the uid. |
| 182 | +-- |
| 183 | +-- Note: group membership lookup walks 'Posix.getAllGroupEntries', which |
| 184 | +-- reflects only @/etc/group@. Groups provided exclusively via NSS |
| 185 | +-- backends (LDAP, SSSD, etc.) will not be seen. A future version could |
| 186 | +-- FFI to @getgrouplist(3)@ for full NSS coverage. |
| 187 | +-- |
| 188 | +-- Note: in contrast to 'groupIds' (current process, which cannot fail), |
| 189 | +-- this returns 'Maybe' to distinguish a non-existent user from a user |
| 190 | +-- whose only group is the primary. |
| 191 | +groupIdsFromUserId :: Int -> IO (Maybe [Int]) |
| 192 | +groupIdsFromUserId i = do |
| 193 | + mUe <- userEntryFromId i |
| 194 | + case mUe of |
| 195 | + Nothing -> return Nothing |
| 196 | + Just ue -> do |
| 197 | + let uname = Posix.userName ue |
| 198 | + primary = fromIntegral (Posix.userGroupID ue) |
| 199 | + allGroups <- Posix.getAllGroupEntries |
| 200 | + let supp = [ fromIntegral (Posix.groupID g) |
| 201 | + | g <- allGroups |
| 202 | + , uname `elem` Posix.groupMembers g |
| 203 | + ] |
| 204 | + return $ Just (nub (primary : supp)) |
| 205 | + |
| 206 | +-- | Names of all groups the user with the given uid belongs to, in the |
| 207 | +-- same order as 'groupIdsFromUserId'. Corresponds to @id -Gn \<uid\>@. |
| 208 | +-- |
| 209 | +-- Returns 'Nothing' if no passwd entry exists for the uid. Entries for |
| 210 | +-- which no group-db record exists are silently dropped. |
| 211 | +-- |
| 212 | +-- See 'groupIdsFromUserId' for the note on NSS coverage. |
| 213 | +groupNamesFromUserId :: Int -> IO (Maybe [String]) |
| 214 | +groupNamesFromUserId i = do |
| 215 | + mGids <- groupIdsFromUserId i |
| 216 | + case mGids of |
| 217 | + Nothing -> return Nothing |
| 218 | + Just gids -> do |
| 219 | + mNames <- mapM groupNameFromId gids |
| 220 | + return $ Just [ n | Just n <- mNames ] |
| 221 | + |
137 | 222 | ------------------------------------------------------------------------------ |
138 | 223 | -- Current process: numeric ids |
139 | 224 | ------------------------------------------------------------------------------ |
|
0 commit comments