locked
Reset passwords for Users in AD from a webpage RRS feed

  • Question

  • User1145601490 posted
    Hi, my first post. yeah =)
    I'm trying to develop a webpage were users with specific permissions can search and reset other users passwords.

    I have managed to do a webpage were user put in their login/password and the user loginID they want to search for. This information shows corrects. But my problem is when you click on the button to reset the password for the user you have found I get an permission-denied error like the first login to the AD is not active anymore. I paste some code to help you understand:


    Imports System
    Imports System.DirectoryServices
    Partial Class _Default
    Inherits System.Web.UI.Page

     Dim UserEntry As New System.DirectoryServices.DirectoryEntry
    Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSearch.Click
       
            Dim AuthUser, AuthPass As String
            AuthUser = txtUsername.Text
            AuthPass = txtPassword.Text
            '        authLDAP(AuthUser, AuthPass)
            Dim oOU As New System.DirectoryServices.DirectoryEntry("LDAP://test.domain.com, AuthUser, AuthPass)
            '   UserEntry = searchUser2(oOU)

        End Sub

    Protected Sub btnChangePsw_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnChangePsw.Click
          
           changePassword(UserEntry)
        End Sub

    Public Function searchUser2(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            '      lblDebug.Text = ""
            Dim UserEntrySearch As New System.DirectoryServices.DirectoryEntry
            Try
                'skapar en instans av objekten directoryentry för att komma åt LDAP
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)


                'skapar sökfiltret samt lägger till vilka egenskaper som skall läggas med i sökningen
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")


                'listar informationen från resultatet från sökningen
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    '   lblDebug.Text = result.Properties.Count.ToString
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "Användaren hittades ej"
                    Else
                        lstResults.Items.Add(result.Properties("cn")(0))
                        lstResults.Items.Add(result.Properties("sAMAccountName")(0))
                        lstResults.Items.Add(result.Properties("description")(0))
                        lstResults.Items.Add(result.Path.ToString)
                        UserEntrySearch = result.GetDirectoryEntry()

                    End If

                Next result
                'returnera det aktuella objektets path
                Return UserEntrySearch

            Catch ex As Exception
                lblDebug.Text = "fel"
                Return "no user"
            End Try
        End Function

    Protected Sub changePassword(ByVal DirEntry As System.DirectoryServices.DirectoryEntry)
            DirEntry.Invoke("setpassword", "p@ssw0rd")
            DirEntry.CommitChanges()
            lstResults.Text = "Lösenordet har ändrats"
                
        End Sub

    I know that there are alot of security issues now but I just need it to work.
    /Hoff
    Friday, June 30, 2006 7:37 AM

All replies

  • User-1513591455 posted
    Every time you create a directory entry in your code, you must do so with a login that allows you to perform the work
    Friday, June 30, 2006 2:32 PM
  • User1145601490 posted
    yeah, I figured that out, but how can I save the password from the first entry so the user don't have to enter their username/pass twice?

    As it is now I have to have first a login for search and then a new login for a reset. Feels very unneccesary.

    Thx in advance
    /Hoff
    Monday, July 3, 2006 3:29 AM
  • User-1513591455 posted

    In a word (or three) : security, security, scurity.

    When you create the first DirectoryEntry, you can work with its children without having to pass a username/password.  Since web pages are stateless, you will have to create the initial DE each time with a log-in.

    There are some options.  You could store the initial DE in application session, and load it each time the page posts back.  Messy, and not secure.

    The best option is to encrypt the user name and password, then store is session, and create using thyis information each time

    Monday, July 3, 2006 5:52 AM
  • User1145601490 posted
    oki,. totally new to actice directory and ASP so I have no idea how to store the logininfo as a session. Some help would be nice
    Thx
    /Hoff
    Monday, July 3, 2006 5:57 AM
  • User1145601490 posted
    Hi again, Managed to solve it with caching. But another problem has come up (why can't it just work?!!). The searchobject DirectoryEntry which is needed when I want to reset a user password can only be used in the specific search-class.
    How to store the directory entry so it can be used by other sub/functions?

    /Hoff
    Monday, July 3, 2006 6:54 AM
  • User-1513591455 posted

    Not sure if this helps, but here are some steps to try.

    Create your root DE

    Create your Searcher, and get the user (I assume one is found)

    Create a new User DE based on the users Path from the search

    Change the Password of the user using the specifically created DE

    DirectoryEntry de = new DirectoryEntry(LDAP://YourDomain, "AuthorisedUser", "YourPassword");

    DirectorySearcher ds = new DirectorySearcher(de, "(&(objectClass=user) (cn=SomeUser))");

    DirectoryEntry deUserToChange = new DirectoryEntry(ds.FindOne().Path, "AuthorisedUser", "YourPassword");

    deUserToChange.Password = "NewPassword";

    Monday, July 3, 2006 9:12 AM
  • User1145601490 posted
    Thx!! Finally I got it to work. Just one question, using cache to store and retreive variables, is this a big security risk?

    /Hoff
    Tuesday, July 4, 2006 1:20 AM
  • User-1513591455 posted

    As a recommendation,

    You should not pass credentials in clear text, which implies encrypting them at the client, or  using a htpps conection.

    You can cache sensitive information if you encrypt it.  This would require a symmetric encryption class with the password embedded in code.  You can also split up the data into chunks to be stored (encrypted) separately, using diffreent passwords and anonymous names.

    Tuesday, July 4, 2006 6:44 AM
  • User1354132231 posted
    A couple points here that bear mentioning:

    1. The .Password property of the DirectoryEntry is not used to change passwords.  It is for setting the security context of the directory connection only.  Use 'SetPassword' or 'ChangePassword' methods to set or change a password.
    2. Setting the username and password on the DirectoryEntry directly is generally frowned upon for general access.  It is much more secure to rely on your thread's security context than to set the LDAP connection explicitly.  There are always exceptions to this rule (ADAM is a good one).  When putting credentials into the DirectoryEntry you do need to protect them.  DPAPI is a good choice.
    The sticky posts on the top of this forum make for a good read and cover these issues.
    Wednesday, July 5, 2006 10:05 AM
  • User1145601490 posted
    A couple points here that bear mentioning:

    1. The .Password property of the DirectoryEntry is not used to change passwords.  It is for setting the security context of the directory connection only.  Use 'SetPassword' or 'ChangePassword' methods to set or change a password.
    2. Setting the username and password on the DirectoryEntry directly is generally frowned upon for general access.  It is much more secure to rely on your thread's security context than to set the LDAP connection explicitly.  There are always exceptions to this rule (ADAM is a good one).  When putting credentials into the DirectoryEntry you do need to protect them.  DPAPI is a good choice.
    The sticky posts on the top of this forum make for a good read and cover these issues.


    Hi! and thx for all the help.
    Dunnry, not sure what you mean  on your 2. Any example I can look into to get a better picture?

    I have got it to work now but their are some issues, like security that would be good to fix. As you can see in btnSearch-Click sub the login/password is sent in cleartext, Any good way to fix this. When you use authenticationtype.secure, what does that mean?
    Another problem is that I try to change a property so that the user has to change password at login.
    UserEntryChange.Properties("pwdLastSet")(0) = 0
                        UserEntryChange.CommitChanges()
    But it's not working.

    Imports System
    Imports System.DirectoryServices
    Imports System.Web.Caching

    Partial Class _Default
        Inherits System.Web.UI.Page
        ' Dim UserEntry As New System.DirectoryServices.DirectoryEntry
        Dim UserEntry As New DirectoryEntry
        'Dim UserPath As String

        Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSearch.Click
            Dim AuthUser, AuthPass As String
            '        authLDAP(AuthUser, AuthPass)
            AuthUser = txtUsername.Text
            AuthPass = txtPassword.Text
            Cache("UserName") = AuthUser
            Cache("UserPsw") = AuthPass
            Dim oOU As New System.DirectoryServices.DirectoryEntry("LDAP://test.hoff.se", AuthUser, AuthPass)
            UserEntry = searchUser2(oOU)
        End Sub

        Protected Sub btnChangePsw_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnChangePsw.Click
            Dim UserID, UserPassword As String
            UserID = Cache.Get("UserName")
            UserPassword = Cache.Get("UserPsw")
            Dim oOUChange As New System.DirectoryServices.DirectoryEntry("LDAP://test.hoff.se", UserID, UserPassword)
            ChangeUserPsw(oOUChange)
            '   UserEntry = Cache.Get("UserEntry1")
            'UserEntry = (oOUChange)
            'changePassword(UserEntry)
            'changePassword(UserEntry)
            '    lblDebug.Text = UserPath
            '  Dim user As New DirectoryEntry(UserPath)
            ' changePassword(user)
            '   End Sub
            'Protected Sub authLDAP(ByVal userName As String, ByVal password As String)
            'Dim userID, passw As String
            'Dim UserEntry As New System.DirectoryServices.DirectoryEntry
            '   userID = userName
            '  passw = password
            'Dim oOU As New System.DirectoryServices.DirectoryEntry("LDAP://test.hoff.se", userID, password)
            '   UserEntry = searchUser2(oOU)
            ' changePassword(UserEntry)
        End Sub

        Public Function searchUser2(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            lblDebug.Text = ""

            Try
                'skapar en instans av objekten directoryentry för att komma åt LDAP
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntrySearch As New DirectoryEntry

                'skapar sökfiltret samt lägger till vilka egenskaper som skall läggas med i sökningen
                lblDebug.Text = txtSearch.Text
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")


                'listar informationen från resultatet från sökningen
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    '   lblDebug.Text = result.Properties.Count.ToString
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "Användaren hittades ej"
                    Else
                        lstResults.Items.Add(result.Properties("cn")(0))
                        lstResults.Items.Add(result.Properties("sAMAccountName")(0))
                        lstResults.Items.Add(result.Properties("description")(0))
                        lstResults.Items.Add(result.Path.ToString)
                        UserEntrySearch = result.GetDirectoryEntry()
                        'UserPath = result.Path

                    End If

                Next result
                'returnera det aktuella objektets path
                Return UserEntrySearch
                'Return UserPath

            Catch ex As Exception
                lblDebug.Text = "fel"
                Return Nothing
            End Try
        End Function

        Public Sub ChangeUserPsw(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            lblDebug.Text = ""

            Try
                'skapar en instans av objekten directoryentry för att komma åt LDAP
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntryChange As New DirectoryEntry

                'skapar sökfiltret samt lägger till vilka egenskaper som skall läggas med i sökningen
                lblDebug.Text = txtSearch.Text
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")


                'listar informationen från resultatet från sökningen
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    '   lblDebug.Text = result.Properties.Count.ToString
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "Användaren hittades ej"
                    Else
                        ' lstResults.Items.Add(result.Properties("cn")(0))
                        'lstResults.Items.Add(result.Properties("sAMAccountName")(0))
                        'lstResults.Items.Add(result.Properties("description")(0))
                        'lstResults.Items.Add(result.Path.ToString)
                        UserEntryChange = result.GetDirectoryEntry()
                        'UserPath = result.Path
                        UserEntryChange.Invoke("setpassword", "sommar1")
                        UserEntryChange.CommitChanges()

                        'Att användaren får byta lösen vid nästa inloggning
                        UserEntryChange.Properties("pwdLastSet")(0) = 0
                        UserEntryChange.CommitChanges()
                        UserEntryChange.Close()
                    End If

                Next result
                'returnera det aktuella objektets path
                'Return UserEntryChange
                'Return UserPath

            Catch ex As Exception
                lblDebug.Text = "error"
            End Try
        End Sub


        Protected Sub btnReset_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnReset.Click

        End Sub

        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        End Sub
    End Class

    Thursday, July 6, 2006 1:53 AM
  • User1354132231 posted
    There are times when you should set the username and password explicitly on the DirectoryEntry, but I have found that they are relatively rare in most deployments.  When you construct your DirectoryEntry, it generally should look like this:

    Dim entry as new DirectoryEntry("LDAP://blah...", Nothing, Nothing, AuthenticationTypes.Secure)

    You can add other AuthenticationTypes like Signing and Sealing (or FastBind) as well.  This constructor means that the application will access AD with the credentials of the process (or impersonated thread).  This will be with whatever credentials are in your app pool in IIS6.

    This also means that a single account will be accessing AD on behalf of your application.  It is to this account that you should give permission to reset a user's password and update a user's attributes (but don't make them domain admin).  Normal users will not be able to use SetPassword and your app will fail.

    FYI - If you read the Common Patterns sticky, you will also notice a pattern for safely handling the read of your user's attributes.  You should be doing the same thing.  For instance, the first user without a description set on them will cause your app to crash (it is commented out now).

    As far as the 'pwdLastSet' attribute problem, this can be for a variety of reasons.  The most common is that the user has been marked with 'password does not expire'.  That would be an incompatible setting.

    Thursday, July 6, 2006 9:10 AM
  • User1145601490 posted
    There are times when you should set the username and password explicitly on the DirectoryEntry, but I have found that they are relatively rare in most deployments.  When you construct your DirectoryEntry, it generally should look like this:

    Dim entry as new DirectoryEntry("LDAP://blah...", Nothing, Nothing, AuthenticationTypes.Secure)

    You can add other AuthenticationTypes like Signing and Sealing (or FastBind) as well.  This constructor means that the application will access AD with the credentials of the process (or impersonated thread).  This will be with whatever credentials are in your app pool in IIS6.

    This also means that a single account will be accessing AD on behalf of your application.  It is to this account that you should give permission to reset a user's password and update a user's attributes (but don't make them domain admin).  Normal users will not be able to use SetPassword and your app will fail.

    FYI - If you read the Common Patterns sticky, you will also notice a pattern for safely handling the read of your user's attributes.  You should be doing the same thing.  For instance, the first user without a description set on them will cause your app to crash (it is commented out now).

    As far as the 'pwdLastSet' attribute problem, this can be for a variety of reasons.  The most common is that the user has been marked with 'password does not expire'.  That would be an incompatible setting.



    The webpage I'm trying to code will be used by helpdesk to reset password for users. As it is now they have to have permissions to do this, right?. What is the advantage of using impersonation instead of my solution more than now I have to give every new Helpdesk personal this permissions.
    I'm have only run my app. from my machine so I'm not sure if it works outside localhost.

    I have deleted every commented line that I won't use.


    Imports System
    Imports System.DirectoryServices
    Imports System.Web.Caching

    Partial Class _Default
        Inherits System.Web.UI.Page
        ' Dim UserEntry As New System.DirectoryServices.DirectoryEntry
        Dim UserEntry As New DirectoryEntry
        'Dim UserPath As String

        Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSearch.Click
            Dim AuthUser, AuthPass As String
            'Dim UserEntry As New System.DirectoryServices.DirectoryEntry

            '        authLDAP(AuthUser, AuthPass)
            AuthUser = txtUsername.Text
            AuthPass = txtPassword.Text
            Cache("UserName") = AuthUser
            Cache("UserPsw") = AuthPass
            Dim oOU As New System.DirectoryServices.DirectoryEntry("LDAP://test.hoff.se", AuthUser, AuthPass, AuthenticationTypes.Secure)
            UserEntry = searchUser2(oOU)
         
        End Sub

        Protected Sub btnChangePsw_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnChangePsw.Click
            Dim UserID, UserPassword As String
            UserID = Cache.Get("UserName")
            UserPassword = Cache.Get("UserPsw")
            Dim oOUChange As New System.DirectoryServices.DirectoryEntry("LDAP://test.hoff.se", UserID, UserPassword, AuthenticationTypes.Secure)
            ChangeUserPsw(oOUChange)
         
        End Sub

        Public Function searchUser2(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            lblDebug.Text = ""

            Try
                'skapar en instans av objekten directoryentry för att komma åt LDAP
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntrySearch As New DirectoryEntry

                'skapar sökfiltret samt lägger till vilka egenskaper som skall läggas med i sökningen
                lblDebug.Text = txtSearch.Text
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")


                'listar informationen från resultatet från sökningen
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "Användaren hittades ej"
                    Else
                        lstResults.Items.Add(result.Properties("cn")(0))
                        lstResults.Items.Add(result.Properties("sAMAccountName")(0))
                        lstResults.Items.Add(result.Properties("description")(0))
                        lstResults.Items.Add(result.Path.ToString)
                        UserEntrySearch = result.GetDirectoryEntry()

                    End If

                Next result
                'returnera det aktuella objektets path
                Return UserEntrySearch

            Catch ex As Exception
                lblDebug.Text = "fel"
                Return Nothing
            End Try
        End Function

        Public Sub ChangeUserPsw(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            lblDebug.Text = ""

            Try
                'skapar en instans av objekten directoryentry för att komma åt LDAP
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntryChange As New DirectoryEntry

                'skapar sökfiltret samt lägger till vilka egenskaper som skall läggas med i sökningen
                lblDebug.Text = txtSearch.Text
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")


                'listar informationen från resultatet från sökningen
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    '   lblDebug.Text = result.Properties.Count.ToString
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "Användaren hittades ej"
                    Else
         
                        UserEntryChange = result.GetDirectoryEntry()

                        UserEntryChange.Invoke("setpassword", "sommar1")
                        UserEntryChange.CommitChanges()

                        'Att användaren får byta lösen vid nästa inloggning
                        UserEntryChange.Properties("pwdLastSet").Item(0) = -1
                        UserEntryChange.CommitChanges()
                        UserEntryChange.Close()
                    End If

                Next result
         
            Catch ex As Exception
                lblDebug.Text = "fel"
            End Try
        End Sub


        Protected Sub btnReset_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnReset.Click

        End Sub

        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        End Sub
    End Class

    Wednesday, July 12, 2006 4:53 AM
  • User1145601490 posted
    Q:  My application works fine on my desktop, but blows up when I put it on the web.  Why?
    A:
      Your code runs with the same permissions as the process that executes it.  When you run it on your desktop, it runs with YOUR credentials.  When you run it on the web, it runs with ASPNET user or NETWORK SERVICE user (depending on platform).  These accounts typically do not have access to AD, so access will be denied.

    Ok, now I get why I need impersonation. But in the examples I've seen the credential of the person you impersonate is in cleartext in the code and to me it's seems like a big security issue. Need som help here to get the impersonation work. Anyone got some examples or good articles about impersonation and active directory?

    Thx
    Hoff
    Thursday, July 13, 2006 2:34 AM
  • User1354132231 posted
    There is an idea of using a trusted subsystem to do your work.  In IIS6 this is very simple - just use an app pool and set the identity to a service account that has permission to work with AD.  Then use 'null' or 'Nothing' values in your constructor and you are good to go.

    If you are really interested in using impersonation and delegation for your helpdesk users, then you need to read the section about setting up delegation and adding <identity impersonate="true"/> to your web.config.  It is not horribly difficult, but can be a pain to manage for this type of application.

    The main reason I don't like giving the users the ability to actually do anything (and only giving the rights to the service account) is that they can later open up an MMC or use a tool like ldp.exe to do things that I might not want them to mess around with.  For instance, if they have the right to reset passwords they will reset my service account passwords which cause a bunch of apps to fail and lockout.  Short of beating them, I cannot stop it.  If I don't give them that permission and make sure my application does not show them that the service accounts even exist, then I don't have to worry about it.  That's a true example, btw.
    Thursday, July 13, 2006 12:59 PM
  • User1145601490 posted

    hi again"

    still hasn't got it right:/. I can't get impersonation to work, everything seems to be fine but when I try to modify an attribute for an object I get a Logon messagebox that says I have to logon the the server that the webapplication exists on. But now when I'm using IIS 6 I understand that there is a easier way to get the setpassword-function to work other than impersonation

     Would be extremly happy if someone could do a little step-by-step solution to I should get this thing working

    Best regard

    Christian
     

    Monday, August 28, 2006 3:37 AM
  • User1354132231 posted

    Hoff,  I am a little confused as to your setup and what you are trying.  Can you explain how you have your application configured in detail?  What is the scenario that you are trying to code for?

     

    Monday, August 28, 2006 5:59 PM
  • User1145601490 posted

    Te web application is for helpdesk users that would be able to reset passwords for the users. Maybe a functionality where you can create/modify users should be available also but not for now. We use Win2k3 Server on all servers and therefor got a AD in win2k server native mode if this does any difference.

    I thought that you could create a group and the members of that group would be able to reset other people password and there for I thought that people with right login in the web applicaition would be able to reset the user that they have found during the search. I know that there is quite a large security risk with my code as it is for now because I am using cache to store the password but I don't know how to use DPAPI or any other encryption in ASP.NET.

    My application is thought to work like this:

     A help desk user is putting their own credentials in the login-section and the user username that they want to reset password for.

    First a search is going on in the Active directory to see if the user is found. The result is the displayed in a label where the users path, username and full name is displayed. Then if it was the user that they was looking for they click on the "change password" button. This does a new search (just to get the path for the object) and continue by reseting the password. (Here comes the problem where I get a logon-message that want me to logon to "Server016" where the IIS6 is running from. )

    And that would be it.  My code looks like this:

    Imports System
    Imports System.DirectoryServices
    Imports System.Web.Caching
    Imports System.Security
    Imports System.Security.Principal

    Partial Class _Default
        Inherits System.Web.UI.Page

        Dim LOGON32_LOGON_INTERACTIVE As Integer = 2
        Dim LOGON32_PROVIDER_DEFAULT As Integer = 0

        Dim impersonationContext As WindowsImpersonationContext

        Declare Function LogonUserA Lib "advapi32.dll" (ByVal lpszUsername As String, _
                               ByVal lpszDomain As String, _
                              ByVal lpszPassword As String, _
                             ByVal dwLogonType As Integer, _
                            ByVal dwLogonProvider As Integer, _
                           ByRef phToken As IntPtr) As Integer

        Declare Auto Function DuplicateToken Lib "advapi32.dll" ( _
                               ByVal ExistingTokenHandle As IntPtr, _
                              ByVal ImpersonationLevel As Integer, _
                             ByRef DuplicateTokenHandle As IntPtr) As Integer

        Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
        Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long


        Public Sub Page_Load(ByVal s As Object, ByVal e As EventArgs)
            
        End Sub
        Private Sub Run(ByVal userName As String, ByVal password As String)
            Dim _userName, _password As String
            _userName = userName
            _password = password
            If impersonateValidUser(_userName, "hoff.test.se", _password) Then
                lblAfter.Text = WindowsIdentity.GetCurrent().Name

                ' Insert your code that runs under the security context of a specific user here.
                undoImpersonation()

            End If
        End Sub

        Private Function impersonateValidUser(ByVal userName As String, _
         ByVal domain As String, ByVal password As String) As Boolean

            Dim tempWindowsIdentity As WindowsIdentity
            Dim token As IntPtr = IntPtr.Zero
            Dim tokenDuplicate As IntPtr = IntPtr.Zero
            impersonateValidUser = False
            If RevertToSelf() Then
                If LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
                    If DuplicateToken(token, 2, tokenDuplicate) <> 0 Then
                        tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
                        impersonationContext = tempWindowsIdentity.Impersonate()
                        If Not impersonationContext Is Nothing Then
                            impersonateValidUser = True
                        End If
                    End If
                End If
            End If
            If Not tokenDuplicate.Equals(IntPtr.Zero) Then
                CloseHandle(tokenDuplicate)
            End If
            If Not token.Equals(IntPtr.Zero) Then
                CloseHandle(token)
            End If
        End Function

        Private Sub undoImpersonation()
            impersonationContext.Undo()
            lblAfterImp.Text = WindowsIdentity.GetCurrent().Name
        End Sub

        Dim UserEntry As New DirectoryEntry


        Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSearch.Click
            Dim AuthUser, AuthPass, Domain As String
            AuthUser = txtUsername.Text
            AuthPass = txtPassword.Text
            Domain = txtDomain.Text
            Cache("UserName") = AuthUser
            Cache("UserPsw") = AuthPass
            Dim oOU As New System.DirectoryServices.DirectoryEntry("LDAP://hoff.test.se", AuthUser, AuthPass, AuthenticationTypes.Secure)
            UserEntry = searchUser2(oOU)

        End Sub

        Protected Sub btnChangePsw_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnChangePsw.Click
            Dim UserID, UserPassword As String
            UserID = Cache.Get("UserName")
            UserPassword = Cache.Get("UserPsw")
            lblBeforeImp.Text = WindowsIdentity.GetCurrent().Name
            'Impersonate...
            If impersonateValidUser(UserID, "hoff.test.se", UserPassword) Then
                Dim oOUChange As New System.DirectoryServices.DirectoryEntry("LDAP://hoff.test.se", UserID, UserPassword, AuthenticationTypes.Secure)
                ChangeUserPsw(oOUChange)
            Else
                lblDebug.Text = "Impersonation failed"
            End If
            undoImpersonation()
        End Sub

        Public Function searchUser2(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            lstResults.Items.Clear()
            lblDebug.Text = ""

            Try
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntrySearch As New DirectoryEntry

                lblDebug.Text = txtSearch.Text
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")

                'search results
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    If result.Properties.Count > 0 Then
                        lstResults.Items.Add(result.Properties("cn")(0))
                        lstResults.Items.Add(result.Properties("sAMAccountName")(0))
                        lstResults.Items.Add(result.Properties("description")(0))
                        lstResults.Items.Add(result.Path.ToString)
                        UserEntrySearch = result.GetDirectoryEntry()

                    Else
                        lblDebug.Text = "No user was found"

                    End If

                Next result
                Return UserEntrySearch

            Catch ex As Exception
                lblDebug.Text = "Wrong username/password"
                Return Nothing
            End Try
        End Function

        Public Sub ChangeUserPsw(ByVal myEntry As System.DirectoryServices.DirectoryEntry)
            Try
                Dim mySearch As New System.DirectoryServices.DirectorySearcher(myEntry)
                Dim UserEntryChange As New DirectoryEntry

                'search filter
                mySearch.Filter = "(SAMAccountName=" & txtSearch.Text & ")"
                mySearch.PropertiesToLoad.Add("cn")
                mySearch.PropertiesToLoad.Add("sAMAccountName")
                mySearch.PropertiesToLoad.Add("description")

                'the results from the search
                Dim result As System.DirectoryServices.SearchResult
                For Each result In mySearch.FindAll()
                    If result.Properties.Count = 0 Then
                        lblDebug.Text = "The user was not found"
                    Else

                        UserEntryChange = result.GetDirectoryEntry()

                        UserEntryChange.Invoke("setpassword", "p@ssw0rd")
                        lblDebug.Text = "Password has been changed"
                        UserEntryChange.CommitChanges()

                        UserEntryChange.Close()
                    End If

                Next result

            Catch ex As Exception
                Throw
            End Try
        End Sub


        Protected Sub btnReset_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnReset.Click
            txtUsername.Text = ""
            txtPassword.Text = ""
            txtSearch.Text = ""
            lblDebug.Text = ""
            lstResults.Items.Clear()

        End Sub


    End Class


    Regards

    Hoff 

    Tuesday, August 29, 2006 1:42 AM
  • User1145601490 posted

    Or is it better to first have  a login-page where the user puts there credentials. Then the login-page checks if the user exist in the AD and has the right priviliges to reset password, and if yes then go to the web application and there you only have to enter the userID you want to reset?.

    And in the web.config use impersonation

    And in IIS let the web application run with a account that has the priviliges to reset passwords? 

    Tuesday, August 29, 2006 5:55 AM
  • User1354132231 posted

    The way I did this way back when is the method I would recommend.  I had to deal with Windows 2000 and you do not, so your life is simplified.

    1. Create the website such that it uses IWA (deny anonymous in web.config).
    2. Check the user's role to make sure they are member's of HelpDesk group (use web.config or programmatic IsInRole() check - I prefer former to latter).
    3. Create a Page the displays the user's information with a button to reset password.  This page is only available to people who can get past #2.
    4. Create an Application Pool in IIS 6 for this app that runs under a domain identity.  Give this domain account ability to reset password in AD.
    5. Remove all explicit credentials from your application.  Use default constructor - New DirectoryEntry(path, Nothing, Nothing, AuthenticationTypes.Secure)
    6. Remove all your impersonation code as this was really only necessary under Windows 2000.
    7. On the button click, call SetPassword.  It will just work.
    Make sense?
    Tuesday, August 29, 2006 11:49 AM