0

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

bepi_roggiuzza
  • 195
  • 1
  • 3
  • 12
  • What if you switch places of the very first "using" and the "foreach" so that each time a new, fresh context is created and then disposed properly? Chances are that one of the internal operations involved in your saving just makes the DirectoryEntry unusable for consecutive operations. This happens. – Wiktor Zychla Jun 10 '13 at 10:41
  • Which line does the error occur on? – Sean Airey Jun 10 '13 at 10:43
  • the error occour on userEntry.Invoke("SetPassword", newPassword); yes i've tried, but the result was the same – bepi_roggiuzza Jun 10 '13 at 10:58
  • I've tried to save the current asp.net user for each iteration, and the result is that the first user is saved with TESTLAB2012\Administrator privilege, than IIS APPPOOL\DefaultAppPool, but i don't know why – bepi_roggiuzza Jun 10 '13 at 11:42
  • Now I tried to impersonate a user programmatically, but the result was the same, the first user is successfully saved, but the second gives me an error and the current asp.net user is again IIS APPPOOL\DefaultAppPool... IMHO that's a very strange thing... in both way, putting context using before foreach and viceversa – bepi_roggiuzza Jun 10 '13 at 14:18

2 Answers2

0

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.

Community
  • 1
  • 1
Sean Airey
  • 6,352
  • 1
  • 20
  • 38
  • First of all, thank you for your reply! Then if you are right, why the first user is correctly written in AD and the second not? I have also a function that adds a single user and that works fine. Then, when I decide to publish the application, i will think about a specific user (now is admin XD) – bepi_roggiuzza Jun 10 '13 at 09:54
  • Well it depends on who the first user in the list is and who the application is currently executing as. It would be helpful if you could provide an example of your code to see what you are doing. The access denied error does suggest that the execution context is either different for the second user in the list or that the user executing the code doesn't have permissions to modify/create the second object. – Sean Airey Jun 10 '13 at 10:10
  • I've just updated the question with the code, never mind about the thred.sleep(), it was an attempt – bepi_roggiuzza Jun 10 '13 at 10:24
0

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!

bepi_roggiuzza
  • 195
  • 1
  • 3
  • 12