DirectoryTree / LdapRecord-Laravel

Multi-domain LDAP Authentication & Management for Laravel.
https://ldaprecord.com/docs/laravel/v3
MIT License
483 stars 51 forks source link

[Bug] `unicodepwd` attribute is not updated on model when testing password update #650

Closed Jacobs63 closed 2 months ago

Jacobs63 commented 2 months ago

Environment:

Describe the bug: When running the DirectoryEmulator and updating user's password, the unicodewd is not updated on the user. I'm not entirely sure, whether this is intended or not - manually calling refresh fixes this issue and assertions are then correct.

Test case:

    public function testUpdatePassword(): void
    {
        DirectoryEmulator::setup(config('ldap.default'));

        $user = ActiveDirectoryUser::create([
            'displayname' => 'John Doe',
            'cn' => 'John Doe',
            'unicodepwd' => 'secret',
        ]);

        $this->assertEquals(
            Password::encode('secret'),
            $user->getPasswordAttribute(),
        );

        $user->updatePassword(NonEmptyString::fromString('new-secret'));

        // Note: The `unicodepwd` attribute is not updated when the LDAP record model is updated
        // we have to refresh the model manually before making assertions
        $user->refresh();

        $this->assertEquals([AccountControl::NORMAL_ACCOUNT], $user->userAccountControl);

        $this->assertEquals(
            Password::encode('new-secret'),
            $user->getPasswordAttribute(),
        );
    }

User model:

        /**
     * @throws ConnectionException|LdapRecordException
     */
    public function updatePassword(NonEmptyString $password): void
    {
        $this->setEnabled();
        $this->unicodepwd = $password->toString();
        $this->save();
    }

    private function setEnabled(): void
    {
        $this->userAccountControl = AccountControl::NORMAL_ACCOUNT;
    }
stevebauman commented 2 months ago

Hi @Jacobs63,

This can't be done with an Active Directory server. Active Directory does not allow you to retrieve the unicodepwd attribute. The directory emulator is working as intended as to replicate this behaviour 👍

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2

Screenshot 2024-05-02 at 9 29 13 AM

Jacobs63 commented 2 months ago

Got it, thank you 💯

stevebauman commented 2 months ago

No problem! If you're looking to test a set or change password operation against the emulated server, you can use the LdapFake utility.

Here's how this is done in the core LdapRecord package:

https://github.com/DirectoryTree/LdapRecord/blob/17e1daca04bfee46116eabd1587ed754d0c00877/tests/Unit/Models/ActiveDirectory/UserTest.php#L80-L98

use LdapRecord\Testing\LdapFake;
use LdapRecord\Testing\DirectoryFake;
use LdapRecord\Models\ActiveDirectory\User;
use LdapRecord\Models\Attributes\AccountControl;

public function test_create_user_with_password()
{
    DirectoryFake::setup()
        ->getLdapConnection()
        ->expect([
            LdapFake::operation('isUsingSSL')->once()->andReturnTrue(),
            LdapFake::operation('add')->once()->with(fn ($dn) => true, fn ($attributes) => (
                $attributes['unicodepwd'] === [Password::encode('foobar')]
                && $attributes['useraccountcontrol'] = 512
            ))->andReturnTrue(),
        ]);

    $user = new User();

    $user->password = 'foobar';
    $user->userAccountControl = 512;

    $user->save();
}
Jacobs63 commented 2 months ago

That's great to know, will do so, appreciate your help :)