comtrya_lib/actions/user/providers/
macos.rs1use 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 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}