/// <summary>
 /// Delete a user
 /// </summary>
 /// <param name="username"></param>
 /// <param name="acp"></param>
 public static void DeleteUser(
     string username,
     AccountManagementPrincipalContext acp)
 {
     using (PrincipalContext context = BuildPrincipal(acp))
     {
         var user = FindUser(username, context);
         user?.Delete();
     }
 }
        /// <summary>
        /// Search a user hierarchicaly in the AD structure
        /// </summary>
        /// <param name="userName">The user name or identifier</param>
        /// <param name="ac">The principal context global configuration</param>
        /// <param name="pc">The principal context this user was found in</param>
        /// <returns></returns>
        private static UserPrincipal SearchUser(string userName, AccountManagementPrincipalContext ac, out PrincipalContext pc)
        {
            UserPrincipal user = null;

            var name = new FqdnNameParser(userName);

            if (name.ContextType == ContextType.Machine)
            {
                pc = BuildPrincipal(null);

                user = FindUser(userName, pc);

                if (user != null)
                {
                    return(user);
                }

                pc.Dispose();

                return(null);
            }

            // Probar sin OU
            pc = BuildPrincipal(ac);

            user = FindUser(userName, pc);

            if (user != null)
            {
                return(user);
            }

            pc.Dispose();

            // Probar con OU
            pc = BuildPrincipalWithoutOu(ac);

            user = FindUser(userName, pc);

            if (user != null)
            {
                return(user);
            }

            pc.Dispose();

            return(null);
        }
        /// <summary>
        /// Search for a group in diferent principal context in the following order: local, domain without OU, domain with OU
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="ac"></param>
        /// <param name="pc"></param>
        /// <returns></returns>
        private static GroupPrincipal SearchGroup(string groupName, AccountManagementPrincipalContext ac, out PrincipalContext pc)
        {
            var name = new FqdnNameParser(groupName);

            GroupPrincipal group = null;

            if (name.ContextType == ContextType.Machine)
            {
                pc = BuildPrincipal(null);

                group = FindGroup(groupName, pc);

                if (group != null)
                {
                    return(group);
                }

                pc.Dispose();

                return(null);
            }

            // Look in provided OU
            pc = BuildPrincipal(ac);

            group = FindGroup(groupName, pc);

            if (group != null)
            {
                return(group);
            }

            pc.Dispose();

            // Look in domain
            pc = BuildPrincipalWithoutOu(ac);

            group = FindGroup(groupName, pc);

            if (group != null)
            {
                return(group);
            }

            pc.Dispose();
            return(null);
        }
        /// <summary>
        /// Ensure user is in group
        /// </summary>
        /// <param name="userPrincipalName"></param>
        /// <param name="groupname"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        public static void EnsureUserInGroup(
            string userPrincipalName,
            string groupname,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            logger.LogInfo(true, $"Ensure user '{userPrincipalName}' in group '{groupname}'");

            UserPrincipal user = SearchUser(userPrincipalName, acp, out var userContext);

            if (user == null)
            {
                userContext?.Dispose();
                throw new Exception($"User '{userPrincipalName}' not found.");
            }

            GroupPrincipal group = SearchGroup(groupname, acp, out var groupContext);

            if (group == null)
            {
                userContext?.Dispose();
                groupContext?.Dispose();
                throw new Exception($"Group '{groupname}' not found.");
            }

            logger.LogWarning(false, $"Found group '{group.Name}' '{group.Sid}' in context '{groupContext.ConnectedServer}' and '{groupContext.ContextType}'");

            foreach (Principal member in group.GetMembers(true))
            {
                if (member.SamAccountName == user.SamAccountName)
                {
                    logger.LogInfo(true, $"User already in group '{groupname}'");
                    return;
                }
            }

            group.Members.Add(user);
            group.Save();

            userContext.Dispose();
            groupContext.Dispose();

            logger.LogInfo(true, $"Added user '{userPrincipalName}' to group '{groupname}'");
        }
 /// <summary>
 /// Chef if user is in a group.
 /// </summary>
 /// <param name="userName"></param>
 /// <param name="groupName"></param>
 /// <param name="acp"></param>
 /// <returns></returns>
 public static bool IsUserInGroup(
     string userName,
     string groupName,
     AccountManagementPrincipalContext acp)
 {
     using (var context = BuildPrincipal(acp))
     {
         using (var searcher = new PrincipalSearcher(new UserPrincipal(context)
         {
             SamAccountName = userName
         }))
         {
             using (var user = searcher.FindOne() as UserPrincipal)
             {
                 return(user != null && user.IsMemberOf(context, IdentityType.SamAccountName, groupName));
             }
         }
     }
 }
        /// <summary>
        /// Add this permission to a directory and user
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="directory"></param>
        /// <param name="fileSystemRights"></param>
        /// <param name="acp"></param>
        public static void AddPermissionToDirectoryIfMissing(
            string identity,
            string directory,
            FileSystemRights fileSystemRights,
            AccountManagementPrincipalContext acp)
        {
            // Search for the target principal in all the domain
            using (PrincipalContext context = BuildPrincipalWithoutOu(acp))
            {
                var principal = FindIdentity(identity, context);

                if (principal == null)
                {
                    throw new Exception($"Unable to find principal with identifier: {identity}");
                }

                AddPermissionToDirectoryIfMissing(principal.Sid, directory, fileSystemRights);
            }
        }
        /// <summary>
        /// Ensure user not in group
        /// </summary>
        /// <param name="userPrincipalName"></param>
        /// <param name="groupname"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        public static void EnsureUserNotInGroup(
            string userPrincipalName,
            string groupname,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            logger.LogInfo(true, $"Ensure user '{userPrincipalName}' NOT in group {groupname}");

            UserPrincipal up = SearchUser(userPrincipalName, acp, out var userContext);

            if (up == null)
            {
                userContext?.Dispose();
                throw new Exception($"User '{userPrincipalName}' not found.");
            }

            GroupPrincipal group = SearchGroup(groupname, acp, out var groupContext);

            if (group == null)
            {
                userContext?.Dispose();
                groupContext?.Dispose();
                throw new Exception($"Group '{groupname}' not found.");
            }

            foreach (Principal member in group.GetMembers(true))
            {
                if (member.SamAccountName == up.SamAccountName)
                {
                    group.Members.Remove(member);
                    group.Save();
                    logger.LogInfo(true, $"Removed user '{userPrincipalName}' from group '{groupname}'");
                    return;
                }
            }

            logger.LogInfo(true, $"User '{userPrincipalName}' not found in group '{groupname}'");
        }
        /// <summary>
        /// Add a user to a group,
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="acp"></param>
        /// <returns></returns>
        public static void EnsureGroupExists(
            string groupName,
            AccountManagementPrincipalContext acp)
        {
            using (PrincipalContext context = BuildPrincipal(acp))
            {
                GroupPrincipal g = FindGroup(groupName, context);

                if (g == null)
                {
                    g                = new GroupPrincipal(context);
                    g.Name           = groupName;
                    g.SamAccountName = groupName;
                    g.Description    = groupName;

                    if (context.ContextType == ContextType.Domain)
                    {
                        g.UserPrincipalName = groupName;
                    }
                }

                g.Save();
            }
        }
        /// <summary>
        /// Generate a valid directory principal, removing any OU specification
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static PrincipalContext BuildPrincipalWithoutOu(AccountManagementPrincipalContext context)
        {
            if (context == null)
            {
                return(BuildPrincipal(null));
            }

            var ctx = context.JsonClone();

            var container = new ContainerParser(ctx.Container);

            for (int x = 0; x < container.Parts.Count; x++)
            {
                if (container.Parts[x].Item1.Equals("OU", StringComparison.CurrentCultureIgnoreCase))
                {
                    container.Parts.RemoveAt(x);
                    x--;
                }
            }

            ctx.Container = container.GetContainer();

            return(BuildPrincipal(ctx));
        }
        /// <summary>
        /// Create a user or return one if it does not exist.
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="password"></param>
        /// <param name="displayName"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        /// <returns></returns>
        public static UserPrincipal EnsureUserExists(
            string identity,
            string password,
            string displayName,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            var parsedUserName = new FqdnNameParser(identity);

            if (parsedUserName.UserPrincipalName.Length > 64)
            {
                throw new Exception($"Windows account userPrincipalName '{parsedUserName.UserPrincipalName}' cannot be longer than 64 characters.");
            }

            using (PrincipalContext pc = BuildPrincipal(acp))
            {
                UserPrincipal up = FindUser(identity, pc);

                string samAccountName = parsedUserName.SamAccountName;

                logger.LogInfo(false, $"Ensure windows account exists '{samAccountName}@{password}' with userPrincipal '{identity}'");

                if (up == null)
                {
                    up = new UserPrincipal(pc, samAccountName, password, true);
                }
                else
                {
                    logger.LogInfo(true, $"Found account IsAccountLockedOut={up.IsAccountLockedOut()}, SamAccountName={up.SamAccountName}");

                    // Make sure we have the latest password, just in case
                    // the pwd algorithm generation changes...
                    try
                    {
                        up.SetPassword(password);
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning(true, "Cannot update password for account: " + e.Message + e.InnerException?.Message);
                    }
                }

                up.UserCannotChangePassword = true;
                up.PasswordNeverExpires     = true;
                up.Enabled     = true;
                up.DisplayName = displayName;
                up.Description = parsedUserName.UserPrincipalName;

                // If we are in a domain, assign the user principal name
                if (pc.ContextType == ContextType.Domain)
                {
                    logger.LogInfo(true, "Setting UserPrincipalName to '{0}'", parsedUserName.UserPrincipalName);
                    up.UserPrincipalName = parsedUserName.UserPrincipalName + "@" + parsedUserName.DomainName.ToLower();
                }

                if (up.IsAccountLockedOut())
                {
                    try
                    {
                        up.UnlockAccount();
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning(true, "Cannot unlock account: " + e.Message + e.InnerException?.Message);
                    }
                }

                try
                {
                    up.Save();
                }
                catch (Exception e)
                {
                    logger.LogException(new Exception("Error while saving user", e), EventLogEntryType.Warning);

                    // Sometimes it crashes, but everything was OK (weird?)
                    // so we check again if the user has been created
                    Thread.Sleep(500);
                    up = FindUser(identity, pc);

                    if (up == null)
                    {
                        // Rethrow the original, whatever it was.
                        ExceptionDispatchInfo.Capture(e).Throw();
                    }
                }

                return(up);
            }
        }
        /// <summary>
        /// Generate a valid directory principal based on the application settings.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static PrincipalContext BuildPrincipal(AccountManagementPrincipalContext context)
        {
            if (context == null)
            {
                // Default to old configuration...
                return(new PrincipalContext(ContextType.Machine, Environment.MachineName));
            }

            ContextType contextType;

            switch (context.ContextType)
            {
            case nameof(ContextType.Machine):
                contextType = ContextType.Machine;
                break;

            case nameof(ContextType.ApplicationDirectory):
                contextType = ContextType.ApplicationDirectory;
                break;

            case nameof(ContextType.Domain):
                contextType = ContextType.Domain;
                break;

            default:
                throw new Exception($"Context type not supported: " + context.ContextType);
            }

            ContextOptions?options = null;

            switch (context.ContextOptions)
            {
            case nameof(ContextOptions.Negotiate):
                options = ContextOptions.Negotiate;
                break;

            case nameof(ContextOptions.Sealing):
                options = ContextOptions.Sealing;
                break;

            case nameof(ContextOptions.SecureSocketLayer):
                options = ContextOptions.SecureSocketLayer;
                break;

            case nameof(ContextOptions.Signing):
                options = ContextOptions.Signing;
                break;

            case nameof(ContextOptions.ServerBind):
                options = ContextOptions.ServerBind;
                break;

            case nameof(ContextOptions.SimpleBind):
                options = ContextOptions.SimpleBind;
                break;

            case null:
                break;

            default:
                throw new Exception($"Context option not supported: " + context.ContextOptions);
            }

            if (options != null)
            {
                return(new PrincipalContext(contextType, context.ContextName, context.Container, options.Value, context.ContextUsername, context.ContextPassword));
            }
            else
            {
                return(new PrincipalContext(contextType, context.ContextName, context.Container, context.ContextUsername, context.ContextPassword));
            }
        }