// 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. namespace WixTestTools { using System; using System.Text; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using Xunit; /// /// Contains methods for User account verification /// public static class UserVerifier { public static class SIDStrings { // Built-In Local Groups public static readonly string BUILTIN_ADMINISTRATORS = "S-1-5-32-544"; public static readonly string BUILTIN_USERS = "S-1-5-32-545"; public static readonly string BUILTIN_GUESTS = "S-1-5-32-546"; public static readonly string BUILTIN_ACCOUNT_OPERATORS = "S-1-5-32-548"; public static readonly string BUILTIN_SERVER_OPERATORS = "S-1-5-32-549"; public static readonly string BUILTIN_PRINT_OPERATORS = "S-1-5-32-550"; public static readonly string BUILTIN_BACKUP_OPERATORS = "S-1-5-32-551"; public static readonly string BUILTIN_REPLICATOR = "S-1-5-32-552"; // Special Groups public static readonly string CREATOR_OWNER = "S-1-3-0"; public static readonly string EVERYONE = "S-1-1-0"; public static readonly string NT_AUTHORITY_NETWORK = "S-1-5-2"; public static readonly string NT_AUTHORITY_INTERACTIVE = "S-1-5-4"; public static readonly string NT_AUTHORITY_SYSTEM = "S-1-5-18"; public static readonly string NT_AUTHORITY_Authenticated_Users = "S-1-5-11"; public static readonly string NT_AUTHORITY_LOCAL_SERVICE = "S-1-5-19"; public static readonly string NT_AUTHORITY_NETWORK_SERVICE = "S-1-5-20"; } /// /// Create a local user on the machine /// /// /// /// Has to be run as an Admin public static void CreateLocalUser(string userName, string password, string comment = "") { DeleteLocalUser(userName); UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine)); newUser.SetPassword(password); newUser.Name = userName; newUser.Description = comment; newUser.UserCannotChangePassword = true; newUser.PasswordNeverExpires = false; newUser.Save(); } /// /// Deletes a local user from the machine /// /// user name to delete /// Has to be run as an Admin public static void DeleteLocalUser(string userName) { UserPrincipal newUser = GetUser(String.Empty, userName); if (null != newUser) { newUser.Delete(); } } /// /// Verifies that a user exisits or not /// /// domain name for the user, empty for local users /// the user name public static bool UserExists(string domainName, string userName) { UserPrincipal user = GetUser(domainName, userName); return null != user; } /// /// Sets the user information for a given user /// /// domain name for the user, empty for local users /// the user name /// user is required to change the password on first login /// password never expires /// account is disabled public static void SetUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled) { UserPrincipal user = GetUser(domainName, userName); Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName)); user.PasswordNeverExpires = passwordNeverExpires; user.Enabled = !disabled; if (passwordExpired) { user.ExpirePasswordNow(); } else { // extend the expiration date to a month user.AccountExpirationDate = DateTime.Now.Add(new TimeSpan(30, 0, 0, 0, 0)); } user.Save(); } /// /// Sets the user comment for a given user /// /// domain name for the user, empty for local users /// the user name /// comment to be set for the user public static void SetUserComment(string domainName, string userName, string comment) { UserPrincipal user = GetUser(domainName, userName); Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName)); var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry; Assert.False(null == directoryEntry); directoryEntry.Properties["Description"].Value = comment; user.Save(); } /// /// Adds the specified user to the specified local group /// /// User to add /// Group to add too public static void AddUserToGroup(string userName, string groupName) { DirectoryEntry localMachine; DirectoryEntry localGroup; localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString()); localGroup = localMachine.Children.Find(groupName, "group"); Assert.False(null == localGroup, String.Format("Group '{0}' was not found.", groupName)); DirectoryEntry user = FindActiveDirectoryUser(userName); localGroup.Invoke("Add", new object[] { user.Path.ToString() }); } /// /// Find the specified user in AD /// /// user name to lookup /// DirectoryEntry of the user private static DirectoryEntry FindActiveDirectoryUser(string UserName) { var mLocalMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString()); var mLocalEntries = mLocalMachine.Children; var theUser = mLocalEntries.Find(UserName); return theUser; } /// /// Verifies the user information for a given user /// /// domain name for the user, empty for local users /// the user name /// user is required to change the password on first login /// password never expires /// account is disabled public static void VerifyUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled) { UserPrincipal user = GetUser(domainName, userName); Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName)); Assert.True(passwordNeverExpires == user.PasswordNeverExpires, String.Format("Password Never Expires for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, user.PasswordNeverExpires, passwordNeverExpires)); Assert.True(disabled != user.Enabled, String.Format("Disappled for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, !user.Enabled, disabled)); DateTime expirationDate = user.AccountExpirationDate.GetValueOrDefault(); bool accountExpired = expirationDate.ToLocalTime().CompareTo(DateTime.Now) <= 0; Assert.True(passwordExpired == accountExpired, String.Format("Password Expired for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, accountExpired, passwordExpired)); } /// /// Verifies the user comment for a given user /// /// domain name for the user, empty for local users /// the user name /// the comment to be verified public static void VerifyUserComment(string domainName, string userName, string comment) { UserPrincipal user = GetUser(domainName, userName); Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName)); var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry; Assert.False(null == directoryEntry); Assert.True(comment == (string)(directoryEntry.Properties["Description"].Value)); } /// /// Verify that a given user is member of a local group /// /// domain name for the user, empty for local users /// the user name /// list of groups to check for membership public static void VerifyUserIsMemberOf(string domainName, string userName, params string[] groupNames) { IsUserMemberOf(domainName, userName, true, groupNames); } /// /// Verify that a givin user is NOT member of a local group /// /// domain name for the user, empty for local users /// the user name /// list of groups to check for membership public static void VerifyUserIsNotMemberOf(string domainName, string userName, params string[] groupNames) { IsUserMemberOf(domainName, userName, false, groupNames); } /// /// /// /// SID to search for /// AccountName public static string GetLocalUserNameFromSID(string sidString) { SecurityIdentifier sid = new SecurityIdentifier(sidString); NTAccount account = (NTAccount)sid.Translate(typeof(NTAccount)); return account.Value; } /// /// Get the SID string for a given user name /// /// /// /// SID string public static string GetSIDFromUserName(string Domain, string UserName) { string retVal = null; string domain = Domain; string name = UserName; if (String.IsNullOrEmpty(domain)) { domain = System.Environment.MachineName; } try { DirectoryEntry de = new DirectoryEntry("WinNT://" + domain + "/" + name); long iBigVal = 5; byte[] bigArr = BitConverter.GetBytes(iBigVal); System.DirectoryServices.PropertyCollection coll = de.Properties; object obVal = coll["objectSid"].Value; if (null != obVal) { retVal = ConvertByteToSidString((byte[])obVal); } } catch (Exception ex) { retVal = String.Empty; Console.Write(ex.Message); } return retVal; } /// /// converts a byte array containing a SID into a string /// /// /// SID string private static string ConvertByteToSidString(byte[] sidBytes) { short sSubAuthorityCount; StringBuilder strSid = new StringBuilder(); strSid.Append("S-"); try { // Add SID revision. strSid.Append(sidBytes[0].ToString()); sSubAuthorityCount = Convert.ToInt16(sidBytes[1]); // Next six bytes are SID authority value. if (sidBytes[2] != 0 || sidBytes[3] != 0) { string strAuth = String.Format("0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}", (short)sidBytes[2], (short)sidBytes[3], (short)sidBytes[4], (short)sidBytes[5], (short)sidBytes[6], (short)sidBytes[7]); strSid.Append("-"); strSid.Append(strAuth); } else { long iVal = (int)(sidBytes[7]) + (int)(sidBytes[6] << 8) + (int)(sidBytes[5] << 16) + (int)(sidBytes[4] << 24); strSid.Append("-"); strSid.Append(iVal.ToString()); } // Get sub authority count... int idxAuth = 0; for (int i = 0; i < sSubAuthorityCount; i++) { idxAuth = 8 + i * 4; uint iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth); strSid.Append("-"); strSid.Append(iSubAuth.ToString()); } } catch (Exception ex) { Console.WriteLine(ex.Message); return ""; } return strSid.ToString(); } /// /// Verify that a given user is member of a local group /// /// domain name for the user, empty for local users /// the user name /// whether the user is expected to be a member of the groups or not /// list of groups to check for membership private static void IsUserMemberOf(string domainName, string userName, bool shouldBeMember, params string[] groupNames) { UserPrincipal user = GetUser(domainName, userName); Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName)); bool missedAGroup = false; string message = String.Empty; foreach (string groupName in groupNames) { try { bool found = user.IsMemberOf(new PrincipalContext(ContextType.Machine), IdentityType.Name, groupName); if (found != shouldBeMember) { missedAGroup = true; message += String.Format("User '{0}/{1}' is {2} a member of local group '{3}'. \r\n", domainName, userName, found ? String.Empty : "NOT", groupName); } } catch (System.DirectoryServices.AccountManagement.PrincipalOperationException) { missedAGroup = true; message += String.Format("Local group '{0}' was not found. \r\n", groupName); } } Assert.False(missedAGroup, message); } /// /// Returns the UserPrincipal object for a given user /// /// Domain name to look under, if Empty the LocalMachine is assumned as the domain /// /// UserPrinicipal Object for the user if found, or null other wise private static UserPrincipal GetUser(string domainName, string userName) { if (String.IsNullOrEmpty(domainName)) { return UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Name, userName); } else { return UserPrincipal.Current;//.FindByIdentity(new PrincipalContext(ContextType.Domain,domainName), IdentityType.Name, userName); } } } }