// 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);
}
}
}
}