Skip to content

[Ldap] added Ldap entry rename for ExtLdap adapter #20390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 18, 2017
Merged

[Ldap] added Ldap entry rename for ExtLdap adapter #20390

merged 1 commit into from
Jan 18, 2017

Conversation

fruitwasp
Copy link
Contributor

@fruitwasp fruitwasp commented Nov 2, 2016

Q A
Branch? "master"
Bug fix? no
New feature? yes
BC breaks? yes
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

$result = $this->executeSearchQuery(1);
$entry = $result[0];

$this->assertEquals($entry->getAttribute('cn'), 'Mr Robot');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entry::getAttribute returns an array. So it should fail.

@fruitwasp
Copy link
Contributor Author

@csarrazi

Copy link
Contributor

@csarrazi csarrazi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in my line comment, I would actually move the rename logic in the update method. The goal of the add is basically to copy / save a new object, whereas update aims to provide means to update an existing object (which includes renaming an entry).

*
* @throws LdapException
*/
public function rename(Entry $entry, $newRdn, $newParent, $shouldDeleteOldRdn = true)
Copy link
Contributor

@csarrazi csarrazi Nov 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally speaking, I'm not really fond of simply exposing the same API as the ldap PHP extension. This would actually go against the goal of this component, which aims to provide a layer of abstraction on top of multiple implementations of Ldap (the PHP Ldap extension being the first available adapter).

Instead, I would improve the logic in the update() method, and add more logic in the Entry class in order to be able to compute a changeset, or simply flag the Entry instance as "dirty".

Currently, the logic for the update() method is pretty poor, and does not check whether the attributes have changed or not, or if the DN has changed or not. This could be pretty much improved, for Symfony 3.3.

@csarrazi
Copy link
Contributor

csarrazi commented Nov 8, 2016

In short, 👍 on the intent of the PR, 👎 on the actual implementation, which should not reflect the implementation details.

@nicolas-grekas nicolas-grekas added this to the 3.x milestone Dec 6, 2016
Copy link
Contributor

@csarrazi csarrazi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for going back and forth between designs. I think you were right to add a new rename method, but it needs a few tweaks.

Instead, I would do the following:

  • The update method should be kept as is, and should be used only to update an existing entry's attributes.
  • A rename method should be added, to explicitly support renaming, taking only the old RDN, the new RDN, and an optional $deleteRdn boolean argument to remove the old entry or not. The $deleteRdn attribute should be true by default, as in most cases we expect the old object to disappear when renaming an entry (think about the OS mv or move command, or an UPDATE statement in SQL).
  • If you want to create a new entry from an existing one with altered attributes, I would actually recommend creating a new entry and passing the original entry's attributes through $originalEntry->getAttributes() and specifying a new DN in the new entry's constructor, perform further modifications, and then use the EntryManager::save() method.

As I said before, I do not want the LDAP component to be a simple reflection of PHP's ldap extension. We want something which is higher level, and eases developers' life.

With this design, no need to track the objects' changes, except just to prevent updating an entry if it not flagged as "dirty" (in the EntryManager::update() method).

In this case, I would rename the $attributesState property to $dirty, and simply store a boolean value instead (no need for class constants for something binary).

@xunto
Copy link
Contributor

xunto commented Dec 16, 2016

@csarrazi we need to track objects' changes anyway because now if you have only read access to some properties, you won't be able to change properties you have access to by changing entry object and persisting it.

As example;

        $records = $ldap->query()->execute();
        $record = $records[0];
        $record->setAttribute("uid", ['data']);
        $ldap->getEntryManager()->update($record);

Won't work even if you have write access to uid property because it tries to update all properties this object have.

I used this workaround:

        $records = $ldap->query()->execute();
        $record = new Entry($records[0]->getDn());
        $record->setAttribute("uid", ['data']);
        $ldap->getEntryManager()->update($record);

It's not place for bug reports but it's connected to the topic :)

@fruitwasp
Copy link
Contributor Author

@csarrazi I have implemented the rename functionality as you suggested. About flagging the Entry as dirty, I think that's one worth to discuss in another pull request.

Copy link
Contributor

@csarrazi csarrazi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're nearly there! :)

Could you just add one more test in LdapManagerTest, and fix the method argument in the interface class?

We should have an additional case with $removeOldRdn = false for checking that the old entry has indeed is still there in this case.

Thanks a lot for that PR, and your patience, @fruitwasp!


$result = $this->executeSearchQuery(1);
$entry = $result[0];
$this->assertEquals($entry->getAttribute('cn')[0], 'Kevin');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check that the old DN no longer exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old DN no longer exists if there's only 1 entry. If more than 1 Entry is found, the assertCount check will fail in executeSearchQuery, therefore I don't think it necessary to have another check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I agree

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing. You should make sure that the test returns the directory in its original state. If you rename the entry, don't forget to rename it back to its original value after the test.

* @param string $newRdn
* @param bool $deleteOldRdn
*/
public function rename(Entry $entry, $newRdn, $deleteOldRdn = true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the $deleteOldRdn to $removeOldRdn so it is the same as in the implementation class.

Copy link
Contributor

@csarrazi csarrazi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to reinitialise the directory to its original state. Tests should be independent from each other, and should have no impact. Thus, you should run rename() again to restore the entry in the first test, and run remove() to remove the newly created entry.


$result = $this->executeSearchQuery(1);
$entry = $result[0];
$this->assertEquals($entry->getAttribute('cn')[0], 'Kevin');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing. You should make sure that the test returns the directory in its original state. If you rename the entry, don't forget to rename it back to its original value after the test.

$entryManager = $this->adapter->getEntryManager();
$entryManager->rename($entry, 'cn=Kevin', false);

$this->executeSearchQuery(2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing. Don't forget to make sure that the new entry is removed after checking that you have two of them.

We do not want tests to put the directory in a state which differs from the startup.

Copy link
Contributor Author

@fruitwasp fruitwasp Jan 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somehow the test fails on this executeSearchQuery(2) even though the old RDN shouldn't be removed. Any idea, @csarrazi?

https://travis-ci.org/symfony/symfony/jobs/190618432#L3686

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of $this->executeSearchQuery(2), try to copy part of the logic, and use something to print what you have in the test.

My guess is that there is something wrong with the parent DN or the RDN, which may not be set to the previous value.

I would recommend you try to run the tests locally (by using the csarrazi/symfony-ldap docker image, for example), and check what you have in the LDAP directory using var_dump(), for example.

FYI, the container can be run as follows:

docker run -d --name ldap -p 389:389 -v "/path/to/symfony/source/code/src/Symfony/Component/Ldap/Tests/Fixtures/data:/init-ldap.d" -e "LDAP_SUFFIX=dc=symfony,dc=com" -e "LDAP_ROOTDN=cn=admin,dc=symfony,dc=com" -e "LDAP_ROOTPW=symfony" csarrazi/symfony-ldap

Then you can run the tests as follows (from /path/to/symfony/source/code):

LDAP_HOST=localhost LDAP_PORT=389 ./phpunit src/Symfony/Component/Ldap

Cheers!

Copy link
Contributor Author

@fruitwasp fruitwasp Jan 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@csarrazi Thanks for that. I misunderstood what not deleting the old RDN actually does. All tests pass now.

@csarrazi
Copy link
Contributor

@fruitwasp you're welcome! 👍

* @param string $newRdn
* @param bool $removeOldRdn
*/
public function rename(Entry $entry, $newRdn, $removeOldRdn = true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still consider the API of the LDAP component to be internal and subject to change? Otherwise, this change is a BC break.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readme states that the component is stable since Symfony 3.1 (https://github.com/symfony/ldap/blob/3.2/README.md). So I guess this is a BC break.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, this interface should still actually be marked as private, and was only added in 3.2.
Write support is only in its infancy, and is not completely mature yet, so we may need to add new methods at later points (move(Entry $entry, $newRoot) for example).

We could create another interface which extends the EntryManagerInterface or a separate interface for newer methods, which could be merged in the main interface for Symfony 4.0 (See what was done for LdapClientInterface and LdapInterface, for example).

On the other hand, the query part is stable, and should not change in the upcoming months.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a MoveEntryInterface would be a good solution?

interface MoveEntryInterface
{
    public function move(Entry $entry, $newRoot);
}

Which should be merged in the main interface for Symfony 4.0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I do think so. @xabbuh what do you think about this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move() instead of rename()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move() is something else, and not related to rename().

So the correct interface would be something like RenameEntryInterface. We could add MoveEntryInterface at a later time, when we actually implement moving an entry to a new parent tree.

@fruitwasp
Copy link
Contributor Author

@csarrazi @xabbuh fixed conflicts with the master branch, implemented the RenameEntryInterface and rebased. Ready for merge?

@xabbuh
Copy link
Member

xabbuh commented Jan 13, 2017

@fruitwasp I am not very familiar with LDAP, but my BC concerns have been addressed.

use Symfony\Component\Ldap\Entry;

/**
* @author Kevin Schuurmans <kevin.schuurmans@freshheads.com>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just add a mention that this interface will be deprecated for 4.0, and merged with EntryManagerInterface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done that.

@fabpot
Copy link
Member

fabpot commented Jan 18, 2017

Thank you @fruitwasp.

@fabpot fabpot merged commit 2d8eeb2 into symfony:master Jan 18, 2017
fabpot added a commit that referenced this pull request Jan 18, 2017
…uitwasp)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[Ldap] added Ldap entry rename for ExtLdap adapter

| Q             | A
| ------------- | ---
| Branch?       | "master"
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Commits
-------

2d8eeb2 [LDAP] implemented LDAP entry rename for ExtLdap adapter
@fruitwasp fruitwasp deleted the ldap-entry-rename branch January 18, 2017 10:10
@nicolas-grekas nicolas-grekas modified the milestones: 3.x, 3.3 Mar 24, 2017
@fabpot fabpot mentioned this pull request May 1, 2017
fabpot added a commit that referenced this pull request Jul 6, 2017
…maid)

This PR was merged into the 4.0-dev branch.

Discussion
----------

[Ldap] Remove the RenameEntryInterface interface

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | yes
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20390
| License       | MIT
| Doc PR        | /

Commits
-------

5d35184 Remove the RenameEntryInterface interface
fabpot added a commit that referenced this pull request Jul 6, 2017
This PR was merged into the 3.3 branch.

Discussion
----------

[Ldap] Fix deprecated message

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20390 #23402
| License       | MIT
| Doc PR        | /

``RenameEntryInterface`` is **deprecated** in 3.3 and **removed/merged** in 4.0.

Commits
-------

bbd0c7f Fix deprecated message
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants