craft-0.0.0.1: src/Craft/User.hs
module Craft.User where
import Control.Lens hiding (un)
import Data.List (intercalate)
import qualified Data.Set as S
import Formatting
import qualified Craft.Group as Group
import Craft.Internal
import Craft.Internal.Helpers
import Craft.Internal.UserGroup
type Name = UserName
name :: Lens' User Name
name = userName
gid :: Lens' User GroupID
gid = userGroup . Craft.Internal.gid
data UserOptions =
UserOptions
{ _optName :: String
, _optUID :: Maybe UserID
, _optComment :: String
--, optAllowdupe :: Bool
-- Whether to allow duplicate UIDs. Default: False
, _optGroup :: Maybe GroupName
-- ^ The default group for a user
, _optUserGroup :: Bool
-- ^ Create a user group for this user. Default: True
, _optGroups :: [GroupName]
-- ^ Other groups
, _optHome :: Path Abs Dir
, _optCreateHome :: Bool
-- ^ Create the user's home directory when creating user
, _optPassword :: Maybe String
, _optSalt :: Maybe String
-- ^ The salt to use when creating the user's password
--, password_max_age :: Maybe Int
--, password_min_age :: Maybe Int
--, _optLocked :: Bool
-- Lock the user's account
, _optShell :: Maybe (Path Abs FileP)
-- User's shell
, _optSystem :: Bool
-- Is the user a system user. Default: False
}
deriving (Eq, Show)
makeLenses ''UserOptions
-- Nothing means rely on the system's default behavior
userOptions :: String -> Craft UserOptions
userOptions n = do
dirName <- parseRelDir n
return UserOptions
{ _optName = n
, _optUID = Nothing
, _optComment = n
, _optGroup = Nothing
, _optUserGroup = True
, _optGroups = []
, _optHome = $(mkAbsDir "/home")</>dirName
, _optCreateHome = True
, _optPassword = Nothing
, _optSalt = Nothing
, _optShell = Nothing
, _optSystem = False
--, _optLocked = False
}
systemUserOptions :: String -> Craft UserOptions
systemUserOptions n = do
uo <- userOptions n
return $ uo & optHome .~ $(mkAbsDir "/")
& optShell ?~ $(mkAbsFile "/usr/sbin/nologin")
& optCreateHome .~ False
& optPassword ?~ ""
& optSystem .~ True
-- & optLocked .~ True
userMod :: UserName -> [String] -> Craft ()
userMod (UserName un) args =
exec_ "usermod" $ args ++ [un]
getUID :: UserName -> Craft (Maybe UserID)
getUID (UserName "root") = return . Just $ UserID 0
getUID (UserName un) =
exec "id" ["--user", un] >>= \case
ExecSucc r -> return . Just . UserID . read $ r ^. stdout
ExecFail _ -> return Nothing
setUID :: UserName -> UserID -> Craft ()
setUID un uid' = userMod un ["--uid", show uid']
setShell :: UserName -> Path Abs FileP -> Craft ()
setShell un shell' = userMod un ["--shell", fromAbsFile shell']
setComment :: UserName -> String -> Craft ()
setComment un comment' = userMod un ["--comment", comment']
setGroup :: UserName -> GroupName -> Craft ()
setGroup un gn =
Group.fromName gn >>= \case
Just g -> userMod un $ toArg "--gid" (g ^. Craft.Internal.gid)
Nothing -> $craftError $ formatToString ("setGroup `"%shown%"` `"%shown%"` failed. Group `"%shown%"` not found!") un gn gn
setGroups :: UserName -> [GroupName] -> Craft ()
setGroups _ [] = return ()
setGroups un gns = userMod un ["--groups", intercalate "," $ map show gns]
setHome :: UserName -> Path Abs Dir -> Craft ()
setHome un homepath = userMod un ["--home", fromAbsDir homepath]
ensureUserOpts :: User -> UserOptions -> Craft Bool
ensureUserOpts user UserOptions{..} = do
let checks =
[ maybeOpt _optUID (user^.uid) setUID
, opt _optComment (user^.userComment) setComment
, maybeOpt _optGroup (user^.userGroup . groupName) setGroup
, opt _optHome (user^.userHome) setHome
, maybeOpt _optShell (user^.userShell) setShell
, if sameElems _optGroups (user^.userGroups)
then setGroups username _optGroups >> return True
else return False
--TODO: password, salt, lock
] :: [Craft Bool]
or <$> sequence checks
where
username = UserName _optName
opt :: Eq a => a -> a -> (UserName -> a -> Craft ()) -> Craft Bool
opt expected actual setter
| expected == actual = return False
| otherwise = setter username expected >> return True
maybeOpt :: Eq a => Maybe a -> a -> (UserName -> a -> Craft ()) -> Craft Bool
maybeOpt Nothing _ _ = return False
maybeOpt (Just expected) actual setter = opt expected actual setter
sameElems :: Ord a => [a] -> [a] -> Bool
sameElems xs ys = S.fromList xs == S.fromList ys
createUser :: UserOptions -> Craft ()
createUser UserOptions{..} = do
groupArg <- getGroupArg _optGroup
exec_ "useradd" $ optsToArgs ++ groupArg ++ groupsArg ++ [show _optName]
where
getGroupArg :: Maybe GroupName -> Craft [String]
getGroupArg Nothing = return []
getGroupArg (Just gn) =
Group.fromName gn >>= \case
Just g -> return $ toArg "--gid" $ g ^. Craft.Internal.gid
Nothing -> $craftError $ "Failed to create User `"++_optName++"` " ++ "with group `"++show gn++"`. Group not found!"
groupsArg = toArg "--groups" $ intercalate "," (map show _optGroups)
optsToArgs =
concat
[ toArg "--uid" _optUID
, toArg "--comment" _optComment
, toArg "--home" _optHome
, toArg "--create-home" _optCreateHome
, toArgBool "--user-group" "--no-user-group" _optUserGroup
, toArg "--password" (encrypt _optSalt _optPassword)
, toArg "--shell" _optShell
, toArg "--system" _optSystem
]
lock :: UserName -> Craft ()
lock un = userMod un ["--lock"]
fromName :: Name -> Craft (Maybe User)
fromName (UserName n) = userFromStr n
fromID :: UserID -> Craft (Maybe User)
fromID = userFromID
-- ___ ___ _____ ___ _____ ___
-- | _ \ _ \_ _\ \ / /_\_ _| __|
-- | _/ /| | \ V / _ \| | | _|
-- |_| |_|_\___| \_/_/ \_\_| |___|
-- TODO
encrypt :: Maybe String -> Maybe String -> Maybe String
encrypt _ _ = Nothing