main
cs 383 lines 16.8 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixTestTools
4 {
5 using System;
6 using System.Text;
7 using System.DirectoryServices;
8 using System.DirectoryServices.AccountManagement;
9 using System.Security.Principal;
10 using Xunit;
11
12 /// <summary>
13 /// Contains methods for User account verification
14 /// </summary>
15 public static class UserVerifier
16 {
17 public static class SIDStrings
18 {
19 // Built-In Local Groups
20 public static readonly string BUILTIN_ADMINISTRATORS = "S-1-5-32-544";
21 public static readonly string BUILTIN_USERS = "S-1-5-32-545";
22 public static readonly string BUILTIN_GUESTS = "S-1-5-32-546";
23 public static readonly string BUILTIN_ACCOUNT_OPERATORS = "S-1-5-32-548";
24 public static readonly string BUILTIN_SERVER_OPERATORS = "S-1-5-32-549";
25 public static readonly string BUILTIN_PRINT_OPERATORS = "S-1-5-32-550";
26 public static readonly string BUILTIN_BACKUP_OPERATORS = "S-1-5-32-551";
27 public static readonly string BUILTIN_REPLICATOR = "S-1-5-32-552";
28
29 // Special Groups
30 public static readonly string CREATOR_OWNER = "S-1-3-0";
31 public static readonly string EVERYONE = "S-1-1-0";
32 public static readonly string NT_AUTHORITY_NETWORK = "S-1-5-2";
33 public static readonly string NT_AUTHORITY_INTERACTIVE = "S-1-5-4";
34 public static readonly string NT_AUTHORITY_SYSTEM = "S-1-5-18";
35 public static readonly string NT_AUTHORITY_Authenticated_Users = "S-1-5-11";
36 public static readonly string NT_AUTHORITY_LOCAL_SERVICE = "S-1-5-19";
37 public static readonly string NT_AUTHORITY_NETWORK_SERVICE = "S-1-5-20";
38 }
39
40 /// <summary>
41 /// Create a local user on the machine
42 /// </summary>
43 /// <param name="userName"></param>
44 /// <param name="password"></param>
45 /// <remarks>Has to be run as an Admin</remarks>
46 public static void CreateLocalUser(string userName, string password, string comment = "")
47 {
48 DeleteLocalUser(userName);
49 UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine));
50 newUser.SetPassword(password);
51 newUser.Name = userName;
52 newUser.Description = comment;
53 newUser.UserCannotChangePassword = true;
54 newUser.PasswordNeverExpires = false;
55 newUser.Save();
56 }
57
58 /// <summary>
59 /// Deletes a local user from the machine
60 /// </summary>
61 /// <param name="userName">user name to delete</param>
62 /// <remarks>Has to be run as an Admin</remarks>
63 public static void DeleteLocalUser(string userName)
64 {
65 UserPrincipal newUser = GetUser(String.Empty, userName);
66 if (null != newUser)
67 {
68 newUser.Delete();
69 }
70 }
71
72 /// <summary>
73 /// Verifies that a user exisits or not
74 /// </summary>
75 /// <param name="domainName">domain name for the user, empty for local users</param>
76 /// <param name="userName">the user name</param>
77 public static bool UserExists(string domainName, string userName)
78 {
79 UserPrincipal user = GetUser(domainName, userName);
80
81 return null != user;
82 }
83
84 /// <summary>
85 /// Sets the user information for a given user
86 /// </summary>
87 /// <param name="domainName">domain name for the user, empty for local users</param>
88 /// <param name="userName">the user name</param>
89 /// <param name="passwordExpired">user is required to change the password on first login</param>
90 /// <param name="passwordNeverExpires">password never expires</param>
91 /// <param name="disabled">account is disabled</param>
92 public static void SetUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled)
93 {
94 UserPrincipal user = GetUser(domainName, userName);
95
96 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
97 user.PasswordNeverExpires = passwordNeverExpires;
98 user.Enabled = !disabled;
99 if (passwordExpired)
100 {
101 user.ExpirePasswordNow();
102 }
103 else
104 {
105 // extend the expiration date to a month
106 user.AccountExpirationDate = DateTime.Now.Add(new TimeSpan(30, 0, 0, 0, 0));
107 }
108 user.Save();
109 }
110
111 /// <summary>
112 /// Sets the user comment for a given user
113 /// </summary>
114 /// <param name="domainName">domain name for the user, empty for local users</param>
115 /// <param name="userName">the user name</param>
116 /// <param name="comment">comment to be set for the user</param>
117 public static void SetUserComment(string domainName, string userName, string comment)
118 {
119 UserPrincipal user = GetUser(domainName, userName);
120
121 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
122
123 var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
124 Assert.False(null == directoryEntry);
125 directoryEntry.Properties["Description"].Value = comment;
126 user.Save();
127 }
128
129 /// <summary>
130 /// Adds the specified user to the specified local group
131 /// </summary>
132 /// <param name="userName">User to add</param>
133 /// <param name="groupName">Group to add too</param>
134 public static void AddUserToGroup(string userName, string groupName)
135 {
136 DirectoryEntry localMachine;
137 DirectoryEntry localGroup;
138
139 localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
140 localGroup = localMachine.Children.Find(groupName, "group");
141 Assert.False(null == localGroup, String.Format("Group '{0}' was not found.", groupName));
142 DirectoryEntry user = FindActiveDirectoryUser(userName);
143 localGroup.Invoke("Add", new object[] { user.Path.ToString() });
144 }
145
146 /// <summary>
147 /// Find the specified user in AD
148 /// </summary>
149 /// <param name="UserName">user name to lookup</param>
150 /// <returns>DirectoryEntry of the user</returns>
151 private static DirectoryEntry FindActiveDirectoryUser(string UserName)
152 {
153 var mLocalMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
154 var mLocalEntries = mLocalMachine.Children;
155
156 var theUser = mLocalEntries.Find(UserName);
157 return theUser;
158 }
159
160 /// <summary>
161 /// Verifies the user information for a given user
162 /// </summary>
163 /// <param name="domainName">domain name for the user, empty for local users</param>
164 /// <param name="userName">the user name</param>
165 /// <param name="passwordExpired">user is required to change the password on first login</param>
166 /// <param name="passwordNeverExpires">password never expires</param>
167 /// <param name="disabled">account is disabled</param>
168 public static void VerifyUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled)
169 {
170 UserPrincipal user = GetUser(domainName, userName);
171
172 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
173
174 Assert.True(passwordNeverExpires == user.PasswordNeverExpires, String.Format("Password Never Expires for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, user.PasswordNeverExpires, passwordNeverExpires));
175 Assert.True(disabled != user.Enabled, String.Format("Disappled for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, !user.Enabled, disabled));
176
177 DateTime expirationDate = user.AccountExpirationDate.GetValueOrDefault();
178 bool accountExpired = expirationDate.ToLocalTime().CompareTo(DateTime.Now) <= 0;
179 Assert.True(passwordExpired == accountExpired, String.Format("Password Expired for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, accountExpired, passwordExpired));
180 }
181
182 /// <summary>
183 /// Verifies the user comment for a given user
184 /// </summary>
185 /// <param name="domainName">domain name for the user, empty for local users</param>
186 /// <param name="userName">the user name</param>
187 /// <param name="comment">the comment to be verified</param>
188 public static void VerifyUserComment(string domainName, string userName, string comment)
189 {
190 UserPrincipal user = GetUser(domainName, userName);
191
192 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
193
194 var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
195 Assert.False(null == directoryEntry);
196 Assert.True(comment == (string)(directoryEntry.Properties["Description"].Value));
197 }
198
199 /// <summary>
200 /// Verify that a given user is member of a local group
201 /// </summary>
202 /// <param name="domainName">domain name for the user, empty for local users</param>
203 /// <param name="userName">the user name</param>
204 /// <param name="groupNames">list of groups to check for membership</param>
205 public static void VerifyUserIsMemberOf(string domainName, string userName, params string[] groupNames)
206 {
207 IsUserMemberOf(domainName, userName, true, groupNames);
208 }
209
210 /// <summary>
211 /// Verify that a givin user is NOT member of a local group
212 /// </summary>
213 /// <param name="domainName">domain name for the user, empty for local users</param>
214 /// <param name="userName">the user name</param>
215 /// <param name="groupNames">list of groups to check for membership</param>
216 public static void VerifyUserIsNotMemberOf(string domainName, string userName, params string[] groupNames)
217 {
218 IsUserMemberOf(domainName, userName, false, groupNames);
219 }
220
221 /// <summary>
222 ///
223 /// </summary>
224 /// <param name="SID">SID to search for</param>
225 /// <returns>AccountName</returns>
226 public static string GetLocalUserNameFromSID(string sidString)
227 {
228 SecurityIdentifier sid = new SecurityIdentifier(sidString);
229 NTAccount account = (NTAccount)sid.Translate(typeof(NTAccount));
230 return account.Value;
231 }
232
233 /// <summary>
234 /// Get the SID string for a given user name
235 /// </summary>
236 /// <param name="Domain"></param>
237 /// <param name="UserName"></param>
238 /// <returns>SID string</returns>
239 public static string GetSIDFromUserName(string Domain, string UserName)
240 {
241 string retVal = null;
242 string domain = Domain;
243 string name = UserName;
244
245 if (String.IsNullOrEmpty(domain))
246 {
247 domain = System.Environment.MachineName;
248 }
249
250 try
251 {
252 DirectoryEntry de = new DirectoryEntry("WinNT://" + domain + "/" + name);
253
254 long iBigVal = 5;
255 byte[] bigArr = BitConverter.GetBytes(iBigVal);
256 System.DirectoryServices.PropertyCollection coll = de.Properties;
257 object obVal = coll["objectSid"].Value;
258 if (null != obVal)
259 {
260 retVal = ConvertByteToSidString((byte[])obVal);
261 }
262 }
263 catch (Exception ex)
264 {
265 retVal = String.Empty;
266 Console.Write(ex.Message);
267 }
268
269 return retVal;
270 }
271
272 /// <summary>
273 /// converts a byte array containing a SID into a string
274 /// </summary>
275 /// <param name="sidBytes"></param>
276 /// <returns>SID string</returns>
277 private static string ConvertByteToSidString(byte[] sidBytes)
278 {
279 short sSubAuthorityCount;
280 StringBuilder strSid = new StringBuilder();
281 strSid.Append("S-");
282 try
283 {
284 // Add SID revision.
285 strSid.Append(sidBytes[0].ToString());
286
287 sSubAuthorityCount = Convert.ToInt16(sidBytes[1]);
288
289 // Next six bytes are SID authority value.
290 if (sidBytes[2] != 0 || sidBytes[3] != 0)
291 {
292 string strAuth = String.Format("0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}",
293 (short)sidBytes[2],
294 (short)sidBytes[3],
295 (short)sidBytes[4],
296 (short)sidBytes[5],
297 (short)sidBytes[6],
298 (short)sidBytes[7]);
299 strSid.Append("-");
300 strSid.Append(strAuth);
301 }
302 else
303 {
304 long iVal = (int)(sidBytes[7]) +
305 (int)(sidBytes[6] << 8) +
306 (int)(sidBytes[5] << 16) +
307 (int)(sidBytes[4] << 24);
308 strSid.Append("-");
309 strSid.Append(iVal.ToString());
310 }
311
312 // Get sub authority count...
313 int idxAuth = 0;
314 for (int i = 0; i < sSubAuthorityCount; i++)
315 {
316 idxAuth = 8 + i * 4;
317 uint iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);
318 strSid.Append("-");
319 strSid.Append(iSubAuth.ToString());
320 }
321 }
322 catch (Exception ex)
323 {
324 Console.WriteLine(ex.Message);
325 return "";
326 }
327 return strSid.ToString();
328 }
329
330 /// <summary>
331 /// Verify that a given user is member of a local group
332 /// </summary>
333 /// <param name="domainName">domain name for the user, empty for local users</param>
334 /// <param name="userName">the user name</param>
335 /// <param name="shouldBeMember">whether the user is expected to be a member of the groups or not</param>
336 /// <param name="groupNames">list of groups to check for membership</param>
337 private static void IsUserMemberOf(string domainName, string userName, bool shouldBeMember, params string[] groupNames)
338 {
339 UserPrincipal user = GetUser(domainName, userName);
340 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
341
342 bool missedAGroup = false;
343 string message = String.Empty;
344 foreach (string groupName in groupNames)
345 {
346 try
347 {
348 bool found = user.IsMemberOf(new PrincipalContext(ContextType.Machine), IdentityType.Name, groupName);
349 if (found != shouldBeMember)
350 {
351 missedAGroup = true;
352 message += String.Format("User '{0}/{1}' is {2} a member of local group '{3}'. \r\n", domainName, userName, found ? String.Empty : "NOT", groupName);
353 }
354 }
355 catch (System.DirectoryServices.AccountManagement.PrincipalOperationException)
356 {
357 missedAGroup = true;
358 message += String.Format("Local group '{0}' was not found. \r\n", groupName);
359 }
360 }
361 Assert.False(missedAGroup, message);
362 }
363
364 /// <summary>
365 /// Returns the UserPrincipal object for a given user
366 /// </summary>
367 /// <param name="domainName">Domain name to look under, if Empty the LocalMachine is assumned as the domain</param>
368 /// <param name="userName"></param>
369 /// <returns>UserPrinicipal Object for the user if found, or null other wise</returns>
370 private static UserPrincipal GetUser(string domainName, string userName)
371 {
372 if (String.IsNullOrEmpty(domainName))
373 {
374 return UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Name, userName);
375 }
376 else
377 {
378 return UserPrincipal.Current;//.FindByIdentity(new PrincipalContext(ContextType.Domain,domainName), IdentityType.Name, userName);
379 }
380 }
381 }
382 }
383