bepi_roggiuzza
bepi_roggiuzza

Reputation: 295

Invocation error saving a list of users in Active Directory

I have a problem saving users, from a csv file, in active directory, using an ASP.Net MVC 4 application (framework 4.5). The problem is that the first user is saved correctly, but the second returns me this error:

Server Error in '/ADManagementStudio' Application.

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

ASP.NET is not authorized to access the requested resource. [...]

[...]

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))]

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args) +630438
ADManagementStudio.Web.Controllers.UsersController.AddUsers(HttpPostedFileBase file) +1437
ADManagementStudio.Web.Controllers.UsersController.CSV(HttpPostedFileBase file) +23 lambda_method(Closure , ControllerBase , Object[] ) +127 System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) +248
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary
2 parameters) +39
System.Web.Mvc.Async.<>c_DisplayClass39.b_33() +120 System.Web.Mvc.Async.<>c_DisplayClass4f.b_49() +452 System.Web.Mvc.Async.<>c_DisplayClass37.b_36(IAsyncResult asyncResult) +15
System.Web.Mvc.Async.<>c_DisplayClass2a.b_20() +31 System.Web.Mvc.Async.<>c_DisplayClass25.b_22(IAsyncResult asyncResult) +230
System.Web.Mvc.<>c_DisplayClass1d.b_18(IAsyncResult asyncResult) +28
System.Web.Mvc.Async.<>c_DisplayClass4.b_3(IAsyncResult ar) +15 System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53
System.Web.Mvc.Async.<>c_DisplayClass4.b_3(IAsyncResult ar) +15
System.Web.Mvc.<>c_DisplayClass8.b_3(IAsyncResult asyncResult) +42
System.Web.Mvc.Async.<>c_DisplayClass4.b_3(IAsyncResult ar) +15
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +606 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288

I'm using impersonation in web.config, but I think it's funny that only the first user is saved and the others not, what's so different than the others? (or maybe I just ignore it because of my poor experience)

Here the code of the function:

private string AddUsers(HttpPostedFileBase file)
    {
        string tempFileName = string.Format("{0}_{1}", Guid.NewGuid(), Path.GetFileName(file.FileName));
        string filePath = Path.Combine(Server.MapPath("~/AD_App_Data/temp"), tempFileName);

        file.SaveAs(filePath);

        FileInfo tempFileInfo = new FileInfo(filePath);
        List<string[]> tempFileData = new List<string[]>();
        List<string> lines = new List<string>();

        using (StreamReader reader = new StreamReader(tempFileInfo.FullName, true))
        {
            string line = string.Empty;

            while ((line = reader.ReadLine()) != null)
            {
                string[] splitter = line.Split(';');

                lines.Add(line);
                tempFileData.Add(splitter);
            }
        }

        tempFileInfo.Delete();

        if ((tempFileData[0][0].ToLower() != "samaccountname") ||
            (tempFileData[0][1].ToLower() != "displayname"))
        {
            return "Error! sAMAccountName or displayName fields not found!";
        }

        try
        {
            string LDAPContextPath = string.Format(
                "LDAP://{0}/{1}",
                ActiveDirectoryManage.GetServerName(),
                ActiveDirectoryManage.GetLDAPUserPath());
            List<string> newUsersPassword = new List<string>();
            using (DirectoryEntry context = new DirectoryEntry(LDAPContextPath, "Administrator", "abcd,1234"))
            {
                foreach (string[] data in tempFileData.Skip(1))
                {
                    using (DirectoryEntry userEntry = context.Children.Add(string.Format("CN={0}", data[1]), "user"))
                    {
                        userEntry.Properties["sAMAccountName"].Value = data[0];
                        userEntry.CommitChanges();

                        for (int i = 1; i < data.Length; i++)
                        {
                            int number;

                            if (int.TryParse(data[i], out number))
                            {
                                userEntry.Properties[tempFileData[0][i]].Value = number;
                            }
                            else
                            {
                                userEntry.Properties[tempFileData[0][i]].Value = data[i];
                            }

                            userEntry.CommitChanges();
                        }

                        string newPassword = Membership.GeneratePassword(12, 0);

                        userEntry.Invoke("SetPassword", newPassword);
                        userEntry.CommitChanges();
                        newUsersPassword.Add(newPassword);
                        userEntry.Properties["userAccountControl"].Value = 512;
                        userEntry.CommitChanges();
                    }
                }

                Thread.Sleep(1000);
            }

            string timestamp = string.Format(
                "{0}{1}{2}-{3}{4}{5}",
                DateTime.Today.Hour, DateTime.Today.Minute, DateTime.Today.Second,
                DateTime.Today.Day, DateTime.Today.Month, DateTime.Today.Year);
            string doneFileName = string.Format("{0}_{1}.csv", file.FileName, timestamp);
            string donePath = Path.Combine(Server.MapPath("~/AD_App_Data/done"), doneFileName);

            using (StreamWriter writer = new StreamWriter(donePath))
            {
                writer.WriteLine(AppendPassword(lines[0], "password"));

                for (int i = 1; i < lines.Count; i++)
                {
                    writer.WriteLine(AppendPassword(lines[i], newUsersPassword[i - 1]));
                }
            }

            return doneFileName;
        }
        catch (DirectoryServicesCOMException ex)
        {
            return "Error! Exception! " + ex.Message;
        }
    }

Thank you in advice

Upvotes: 0

Views: 1686

Answers (2)

bepi_roggiuzza
bepi_roggiuzza

Reputation: 295

Ok guys, i've finally made it! For some reason that for now I ignore, I used programmatically impersonation of a user with sufficent rights to manage AD at every iteration.

Here the Link at the page that describes how to implement server side impersonation programmatically.

And below how I used it:

private string AddUsers(HttpPostedFileBase file)
    {
        string tempFileName = string.Format("{0}_{1}", Guid.NewGuid(), Path.GetFileName(file.FileName));
        string filePath = Path.Combine(Server.MapPath("~/AD_App_Data/temp"), tempFileName);

        file.SaveAs(filePath);

        FileInfo tempFileInfo = new FileInfo(filePath);
        List<string[]> tempFileData = new List<string[]>();
        List<string> lines = new List<string>();

        using (StreamReader reader = new StreamReader(tempFileInfo.FullName, true))
        {
            string line = string.Empty;

            while ((line = reader.ReadLine()) != null)
            {
                string[] splitter = line.Split(';');

                lines.Add(line);
                tempFileData.Add(splitter);
            }
        }

        tempFileInfo.Delete();

        if ((tempFileData[0][0].ToLower() != "samaccountname") ||
            (tempFileData[0][1].ToLower() != "displayname"))
        {
            return "Error! sAMAccountName or displayName fields not found!";
        }

        List<string> users = new List<string>();

        try
        {
            string LDAPContextPath = string.Format(
                "LDAP://{0}/{1}",
                ActiveDirectoryManage.GetServerName(),
                ActiveDirectoryManage.GetLDAPUserPath());
            List<string> newUsersPassword = new List<string>();

            foreach (string[] data in tempFileData.Skip(1))
            {
                if (impersonateValidUser("Administrator", ActiveDirectoryManage.GetDomainName(), "abcd,1234"))
                {
                    using (DirectoryEntry context = new DirectoryEntry(LDAPContextPath, "Administrator", "abcd,1234"))
                    {
                        users.Add(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
                        using (DirectoryEntry userEntry = context.Children.Add(string.Format("CN={0}", data[1]), "user"))
                        {
                            userEntry.Properties["sAMAccountName"].Value = data[0];
                            userEntry.CommitChanges();

                            for (int i = 1; i < data.Length; i++)
                            {
                                int number;

                                if (int.TryParse(data[i], out number))
                                {
                                    userEntry.Properties[tempFileData[0][i]].Value = number;
                                }
                                else
                                {
                                    userEntry.Properties[tempFileData[0][i]].Value = data[i];
                                }

                                userEntry.CommitChanges();
                            }

                            string newPassword = Membership.GeneratePassword(12, 0);

                            userEntry.Invoke("SetPassword", newPassword);
                            userEntry.CommitChanges();
                            newUsersPassword.Add(newPassword);
                            userEntry.Properties["userAccountControl"].Value = 512;
                            userEntry.CommitChanges();
                        }
                    }
                    undoImpersonation();
                }
                else
                {
                    return "Error! Impersonation Failed";
                }
            }


            string timestamp = string.Format(
                "{0}{1}{2}-{3}{4}{5}",
                DateTime.Today.Hour, DateTime.Today.Minute, DateTime.Today.Second,
                DateTime.Today.Day, DateTime.Today.Month, DateTime.Today.Year);
            string doneFileName = string.Format("{0}_{1}.csv", file.FileName, timestamp);
            string donePath = Path.Combine(Server.MapPath("~/AD_App_Data/done"), doneFileName);

            using (StreamWriter writer = new StreamWriter(donePath))
            {
                writer.WriteLine(AppendPassword(lines[0], "password"));

                for (int i = 1; i < lines.Count; i++)
                {
                    writer.WriteLine(AppendPassword(lines[i], newUsersPassword[i - 1]));
                }
            }

            return doneFileName;
        }
        catch (Exception ex)
        {
            string error = "Error! Exception! " + ex.Message + "\n\n";

            foreach (string s in users)
            {
                error = error + s + "\n\n";
            }

            return error;
        }
    }

I Hope that this post could be helpful!

Upvotes: 0

Sean Airey
Sean Airey

Reputation: 6372

If you are using impersonation, you need to be sure that the impersonated user has sufficient permissions to modify/create objects in active directory. This will almost never be the case if the impersonated user is not a domain admin or does not have custom permissions set.

I would suggest you ditch impersonation and either run the application pool as a domain account that has limited permissions in active directory (think least-privilege here, give it only the permissions it needs to do its job), or create an impersonation context in code manually with a domain account that has the same restrictions as the proposed application pool account.

There are a couple of links in this SO answer that may help you out with impersonating another user in code.

Upvotes: 0

Related Questions