comtrya_lib/actions/user/providers/
macos.rs

1use super::UserProvider;
2use crate::actions::user::{add_group::UserAddGroup, UserVariant};
3use crate::atoms::command::Exec;
4use crate::contexts::Contexts;
5use crate::steps::Step;
6use crate::utilities;
7use serde::{Deserialize, Serialize};
8use tracing::warn;
9use which::which;
10
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct MacOSUserProvider {}
13
14impl UserProvider for MacOSUserProvider {
15    fn add_user(&self, user: &UserVariant, contexts: &Contexts) -> anyhow::Result<Vec<Step>> {
16        let mut args: Vec<String> = vec![];
17        let cli = match which("dscl") {
18            Ok(c) => c,
19            Err(_) => {
20                warn!(message = "Could not find proper user add tool");
21                return Ok(vec![]);
22            }
23        };
24
25        // is a user name isn't provided, cant create a new user
26        if user.username.is_empty() {
27            warn!("Unable to create user without a username");
28            return Ok(vec![]);
29        }
30
31        let mut username = String::from("/Users/");
32        username.push_str(user.username.clone().as_str());
33
34        args.push(String::from("."));
35        args.push(String::from("-create"));
36        args.push(username);
37
38        if !user.shell.is_empty() {
39            args.push(String::from("UserShell"));
40            args.push(user.shell.clone());
41        }
42
43        if !user.fullname.is_empty() {
44            args.push(String::from("RealName"));
45            args.push(String::from("\"{user.fullename}\""));
46        }
47
48        let privilege_provider =
49            utilities::get_privilege_provider(&contexts).unwrap_or_else(|| "sudo".to_string());
50
51        let mut steps: Vec<Step> = vec![Step {
52            atom: Box::new(Exec {
53                command: cli.display().to_string(),
54                arguments: vec![].into_iter().chain(args.clone()).collect(),
55                privileged: true,
56                privilege_provider: privilege_provider.clone(),
57                ..Default::default()
58            }),
59            initializers: vec![],
60            finalizers: vec![],
61        }];
62
63        if !user.group.is_empty() {
64            let user_group = UserAddGroup {
65                username: user.username.clone(),
66                group: user.group.clone(),
67                provider: user.provider.clone(),
68            };
69            for group in self.add_to_group(&user_group, &contexts)? {
70                steps.push(group);
71            }
72        }
73
74        Ok(steps)
75    }
76
77    fn add_to_group(&self, user: &UserAddGroup, contexts: &Contexts) -> anyhow::Result<Vec<Step>> {
78        let cli = match which("dscl") {
79            Ok(c) => c,
80            Err(_) => {
81                warn!(message = "Could not find the dscl tool");
82                return Ok(vec![]);
83            }
84        };
85
86        if user.group.is_empty() {
87            warn!(message = "No groups listed to add user to");
88            return Ok(vec![]);
89        }
90
91        if user.username.is_empty() {
92            warn!(message = "No user specified to add to group(s)");
93            return Ok(vec![]);
94        }
95
96        let privilege_provider =
97            utilities::get_privilege_provider(&contexts).unwrap_or_else(|| "sudo".to_string());
98
99        let mut steps: Vec<Step> = vec![];
100
101        for group in user.group.iter() {
102            let mut group_string: String = String::from("/Groups/");
103            group_string.push_str(&group.clone());
104
105            let args: Vec<String> = vec![
106                ".".to_string(),
107                "append".to_string(),
108                group_string.clone(),
109                "GroupMembership".to_string(),
110                user.username.clone(),
111            ];
112
113            steps.push(Step {
114                atom: Box::new(Exec {
115                    command: cli.display().to_string().clone(),
116                    arguments: args.into_iter().collect(),
117                    privileged: true,
118                    privilege_provider: privilege_provider.clone(),
119                    ..Default::default()
120                }),
121                initializers: vec![],
122                finalizers: vec![],
123            });
124        }
125
126        Ok(steps)
127    }
128}
129
130#[cfg(target_os = "macos")]
131#[cfg(test)]
132mod test {
133    use crate::actions::user::providers::{MacOSUserProvider, UserProvider};
134    use crate::actions::user::{add_group::UserAddGroup, UserVariant};
135    use crate::contexts::Contexts;
136
137    #[test]
138    fn test_add_user() {
139        let user_provider = MacOSUserProvider {};
140        let contexts = Contexts::default();
141        let steps = user_provider.add_user(
142            &UserVariant {
143                username: String::from("test"),
144                shell: String::from("sh"),
145                home_dir: String::from("/home/test"),
146                fullname: String::from("Test User"),
147                group: vec![],
148                ..Default::default()
149            },
150            &contexts,
151        );
152
153        assert_eq!(steps.unwrap().len(), 1);
154    }
155
156    #[test]
157    fn test_add_user_no_username() {
158        let user_provider = MacOSUserProvider {};
159        let contexts = Contexts::default();
160        let steps = user_provider.add_user(
161            &UserVariant {
162                username: String::from(""),
163                shell: String::from("sh"),
164                home_dir: String::from("/home/test"),
165                fullname: String::from("Test User"),
166                group: vec![],
167                ..Default::default()
168            },
169            &contexts,
170        );
171
172        assert_eq!(steps.unwrap().len(), 0);
173    }
174
175    #[test]
176    fn test_add_to_group() {
177        let user_provider = MacOSUserProvider {};
178        let contexts = Contexts::default();
179        let steps = user_provider.add_to_group(
180            &UserAddGroup {
181                username: String::from("test"),
182                group: vec![String::from("testgroup"), String::from("wheel")],
183                ..Default::default()
184            },
185            &contexts,
186        );
187
188        assert_eq!(steps.unwrap().len(), 2);
189    }
190
191    #[test]
192    fn test_create_user_add_to_group() {
193        let user_provider = MacOSUserProvider {};
194        let contexts = Contexts::default();
195        let steps = user_provider.add_user(
196            &UserVariant {
197                username: String::from("test"),
198                shell: String::from(""),
199                home_dir: String::from(""),
200                fullname: String::from(""),
201                group: vec![String::from("testgroup")],
202                ..Default::default()
203            },
204            &contexts,
205        );
206
207        assert_eq!(steps.unwrap().len(), 2);
208    }
209}