<commit_before>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Web.Security;
using IronFoundry.Container.Win32;
using NLog;

namespace IronFoundry.Container.Utilities
{
    // BR: Move this to IronFoundry.Container
    public class LocalPrincipalManager : IUserManager
    {
        const int NERR_Success = 0;
        [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int NetUserDel(string serverName, string userName);

        private const uint COM_EXCEPT_UNKNOWN_DIRECTORY_OBJECT = 0x80005004;
        // TODO: Determine if adding the user to IIS_USRS is really a requirement for the
        // IISHost.  If it is, then pass an array of groups for the user instead of having this hardcoded.
        //private const string IIS_IUSRS_NAME = "IIS_IUSRS";

        private readonly string directoryPath = String.Format("WinNT://{0}", Environment.MachineName);
        private readonly Logger log = LogManager.GetCurrentClassLogger();
        private readonly IDesktopPermissionManager permissionManager;
        private readonly IEnumerable<string> wardenUserGroups;

        public LocalPrincipalManager(IDesktopPermissionManager permissionManager, params string[] userGroupNames)
        {
            this.permissionManager = permissionManager;
            this.wardenUserGroups = userGroupNames ?? new String[0];
        }

        public LocalPrincipalManager(params string [] userGroupNames)
            : this(new DesktopPermissionManager(), userGroupNames)
        {
        }

        public void DeleteUser(string userName)
        {
            // Don't need to cleanup desktop permissions as they are managed by the group.

            // Using NetUserDel as the DirectoryService APIs were painfully slow (~20-30 seconds to delete a single user).  
            var result = NetUserDel(null, userName);

            if (result != NERR_Success)
            {
                log.Error("Failed to delete user with the error code: {0}", result);
            }
        }

        public NetworkCredential CreateUser(string userName)
        {
            var data = InnerCreateUser(userName);

            foreach (string userGroupName in wardenUserGroups)
            {
                permissionManager.AddDesktopPermission(userGroupName);
            }

            return new NetworkCredential(data.UserName, data.Password);
        }

        public string FindUser(string userName)
        {
            string rvUserName = null;

            using (var localDirectory = new DirectoryEntry(directoryPath))
            {
                DirectoryEntries users = localDirectory.Children;
                try
                {
                    using (DirectoryEntry user = users.Find(userName))
                    {
                        if (user != null)
                        {
                            rvUserName = user.Name;
                        }
                    }
                }
                catch (COMException ex)
                {
                    // Exception indicates the requested item could not be found, in which case we should return the default value
                    if ((uint) ex.ErrorCode != COM_EXCEPT_UNKNOWN_DIRECTORY_OBJECT)
                    {
                        throw;
                    }
                }
            }

            return rvUserName;
        }

        private LocalPrincipalData InnerCreateUser(string userName)
        {
            string rvUserName = null;
            string rvPassword = null;
            LocalPrincipalData rv = null;

            using (var context = new PrincipalContext(ContextType.Machine))
            {
                bool userSaved = false;
                ushort tries = 0;
                UserPrincipal user = null;

                do
                {
                    try
                    {
                        rvPassword = Membership.GeneratePassword(8, 2).ToLowerInvariant() + Membership.GeneratePassword(8, 2).ToUpperInvariant();
                        user = new UserPrincipal(context, userName, rvPassword, true);
                        user.DisplayName = "Warden User " + userName;
                        user.Save();
                        userSaved = true;
                    }
                    catch (PasswordException ex)
                    {
                        log.DebugException(ex);
                    }

                    ++tries;
                } while (userSaved == false && tries < 5);

                if (userSaved)
                {
                    rvUserName = user.SamAccountName;
                    //AddUserToGroup(context, IIS_IUSRS_NAME, user);

                    foreach(string userGroupName in this.wardenUserGroups)
                    {
                        AddUserToGroup(context, userGroupName, user);
                    }

                    rv = new LocalPrincipalData(rvUserName, rvPassword);
                }
            }

            return rv;
        }

        private void AddUserToGroup(PrincipalContext context, string groupName, UserPrincipal user)
        {
            var groupQuery = new GroupPrincipal(context, groupName);
            var searcher = new PrincipalSearcher(groupQuery);
            var group = InvokeSearcherWithRetries(searcher);


            if (group == null)
            {
                throw new ArgumentException(string.Format("The specified group '{0}' does not exist.", groupName), "groupName");
            }

            // The iisUserGroups.Members.Add attempts to resolve all the SID's of the entries while
            // it's enumerating for an item.  This approach works around this issue by dynamically
            // invoking 'Add' with the DN of the user.
            var groupAsDirectoryEntry = group.GetUnderlyingObject() as DirectoryEntry;
            var userAsDirectoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
            groupAsDirectoryEntry.Invoke("Add", new object[] { userAsDirectoryEntry.Path });

            group.Save();
        }

        private Principal InvokeSearcherWithRetries(PrincipalSearcher searcher)
        {
            Principal group = null;

            Func<bool> findGroup = () =>
            {
                try
                {
                    group = searcher.FindOne() as GroupPrincipal;
                }
                catch (COMException)
                {
                }

                return group != null;
            };
<commit_msg>LocalPrincipalManager - Filter ComException catch via the ErrorCode.<commit_after>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Web.Security;
using IronFoundry.Container.Win32;
using NLog;

namespace IronFoundry.Container.Utilities
{
    // BR: Move this to IronFoundry.Container
    public class LocalPrincipalManager : IUserManager
    {
        const int NERR_Success = 0;
        [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int NetUserDel(string serverName, string userName);

        private const uint COM_EXCEPT_UNKNOWN_DIRECTORY_OBJECT = 0x80005004;
        private const uint COM_EXCEPT_ERROR_NONE_MAPPED = 0x80070534;

        // TODO: Determine if adding the user to IIS_USRS is really a requirement for the
        // IISHost.  If it is, then pass an array of groups for the user instead of having this hardcoded.
        //private const string IIS_IUSRS_NAME = "IIS_IUSRS";

        private readonly string directoryPath = String.Format("WinNT://{0}", Environment.MachineName);
        private readonly Logger log = LogManager.GetCurrentClassLogger();
        private readonly IDesktopPermissionManager permissionManager;
        private readonly IEnumerable<string> wardenUserGroups;

        public LocalPrincipalManager(IDesktopPermissionManager permissionManager, params string[] userGroupNames)
        {
            this.permissionManager = permissionManager;
            this.wardenUserGroups = userGroupNames ?? new String[0];
        }

        public LocalPrincipalManager(params string [] userGroupNames)
            : this(new DesktopPermissionManager(), userGroupNames)
        {
        }

        public void DeleteUser(string userName)
        {
            // Don't need to cleanup desktop permissions as they are managed by the group.

            // Using NetUserDel as the DirectoryService APIs were painfully slow (~20-30 seconds to delete a single user).  
            var result = NetUserDel(null, userName);

            if (result != NERR_Success)
            {
                log.Error("Failed to delete user with the error code: {0}", result);
            }
        }

        public NetworkCredential CreateUser(string userName)
        {
            var data = InnerCreateUser(userName);

            foreach (string userGroupName in wardenUserGroups)
            {
                permissionManager.AddDesktopPermission(userGroupName);
            }

            return new NetworkCredential(data.UserName, data.Password);
        }

        public string FindUser(string userName)
        {
            string rvUserName = null;

            using (var localDirectory = new DirectoryEntry(directoryPath))
            {
                DirectoryEntries users = localDirectory.Children;
                try
                {
                    using (DirectoryEntry user = users.Find(userName))
                    {
                        if (user != null)
                        {
                            rvUserName = user.Name;
                        }
                    }
                }
                catch (COMException ex)
                {
                    // Exception indicates the requested item could not be found, in which case we should return the default value
                    if ((uint) ex.ErrorCode != COM_EXCEPT_UNKNOWN_DIRECTORY_OBJECT)
                    {
                        throw;
                    }
                }
            }

            return rvUserName;
        }

        private LocalPrincipalData InnerCreateUser(string userName)
        {
            string rvUserName = null;
            string rvPassword = null;
            LocalPrincipalData rv = null;

            using (var context = new PrincipalContext(ContextType.Machine))
            {
                bool userSaved = false;
                ushort tries = 0;
                UserPrincipal user = null;

                do
                {
                    try
                    {
                        rvPassword = Membership.GeneratePassword(8, 2).ToLowerInvariant() + Membership.GeneratePassword(8, 2).ToUpperInvariant();
                        user = new UserPrincipal(context, userName, rvPassword, true);
                        user.DisplayName = "Warden User " + userName;
                        user.Save();
                        userSaved = true;
                    }
                    catch (PasswordException ex)
                    {
                        log.DebugException(ex);
                    }

                    ++tries;
                } while (userSaved == false && tries < 5);

                if (userSaved)
                {
                    rvUserName = user.SamAccountName;
                    //AddUserToGroup(context, IIS_IUSRS_NAME, user);

                    foreach(string userGroupName in this.wardenUserGroups)
                    {
                        AddUserToGroup(context, userGroupName, user);
                    }

                    rv = new LocalPrincipalData(rvUserName, rvPassword);
                }
            }

            return rv;
        }

        private void AddUserToGroup(PrincipalContext context, string groupName, UserPrincipal user)
        {
            var groupQuery = new GroupPrincipal(context, groupName);
            var searcher = new PrincipalSearcher(groupQuery);
            var group = InvokeSearcherWithRetries(searcher);


            if (group == null)
            {
                throw new ArgumentException(string.Format("The specified group '{0}' does not exist.", groupName), "groupName");
            }

            // The iisUserGroups.Members.Add attempts to resolve all the SID's of the entries while
            // it's enumerating for an item.  This approach works around this issue by dynamically
            // invoking 'Add' with the DN of the user.
            var groupAsDirectoryEntry = group.GetUnderlyingObject() as DirectoryEntry;
            var userAsDirectoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
            groupAsDirectoryEntry.Invoke("Add", new object[] { userAsDirectoryEntry.Path });

            group.Save();
        }

        private Principal InvokeSearcherWithRetries(PrincipalSearcher searcher)
        {
            Principal group = null;

            Func<bool> findGroup = () =>
            {
                try
                {
                    group = searcher.FindOne() as GroupPrincipal;
                }
                catch (COMException ex)
                {
                    // No mapping between account names and security IDs was done.
                    if ((uint)ex.ErrorCode != COM_EXCEPT_ERROR_NONE_MAPPED)
                    {
                        throw;
                    }
                }

                return group != null;
            };

{'fraction_non_alphanumeric': 0.06790791657645955, 'fraction_numerical': 0.004690770007938226, 'mean_word_length': 1.5941594908274055, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5627966', 'n_tokens_mistral': 3358, 'n_tokens_neox': 3077, 'n_words': 886}