Bug 54347 - On object creation return object's entryUUID
On object creation return object's entryUUID
Status: CLOSED FIXED
Product: UCS
Classification: Unclassified
Component: UDM - REST API
UCS 5.0
All All
: P5 normal (vote)
: UCS 5.0-1-errata
Assigned To: Florian Best
Arvid Requate
:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2022-01-17 10:07 CET by Thorsten
Modified: 2022-02-02 16:05 CET (History)
3 users (show)

See Also:
What kind of report is it?: Development Internal
What type of bug is this?: ---
Who will be affected by this bug?: ---
How will those affected feel about the bug?: ---
User Pain:
Enterprise Customer affected?:
School Customer affected?:
ISV affected?:
Waiting Support:
Flags outvoted (downgraded) after PO Review:
Ticket number:
Bug group (optional): API change
Max CVSS v3 score:
best: Patch_Available+


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Thorsten univentionstaff 2022-01-17 10:07:11 CET
When creating an object using the UDM REST API the response should include the entryUUID of the object. 

This is required for adhoc federation use cases using a ldap federated Keycloak.

The scope of this enhancement is limited to user objects, but it seems reasonable to have this also as a generic approach for all objects. But this might change the efforts on this request significantly.
Comment 1 Florian Best univentionstaff 2022-01-17 10:29:58 CET
(In reply to Thorsten from comment #0)
> When creating an object using the UDM REST API the response should include
> the entryUUID of the object. 
Should we really only return the entryUUID? Or will there be further requests in some weeks to also include something else?

> This is required for adhoc federation use cases using a ldap federated
> Keycloak.
OK: the problem is a race condition between creation and fetching of the object?! In chat you wrote "multiple roundtrips cause sync problems with Keycloak LDAP federation". I don't understand that aspect yet.
 
> The scope of this enhancement is limited to user objects, but it seems
> reasonable to have this also as a generic approach for all objects. But this
> might change the efforts on this request significantly.
This doesn't change any effords - only doing it for user objects would be more efford. UDM is and should always a generic thing.

From Bug #50252 comment 5:
I disagree to implement that:
We are currently only returning no representation after creating a object giving a Location header with a link where the object can be retrieved. So just follow that to receive the object representation including entryUUID!

If we add the entryUUID (and all other properties?) we have to open the object again, do some calulations while it is most often not needed and decreases the performance especially for scenarios where we do a mass import of users. I don't know if significantly.

A patch to achieve this only for entryUUID would be:

diff --git management/univention-directory-manager-rest/src/univention/admin/rest/module.py management/univention-directory-manager-rest/src/univention/admin/rest/module.py
index afdb35f162..9bbfac8626 100755
--- management/univention-directory-manager-rest/src/univention/admin/rest/module.py
+++ management/univention-directory-manager-rest/src/univention/admin/rest/module.py
@@ -2804,7 +2804,7 @@ class Object(FormBase, Resource):
                        self.set_header('Location', self.urljoin(quote_dn(obj.dn)))
                        self.set_status(201)
                        self.add_caching(public=False, must_revalidate=True)
-                       self.content_negotiation({})
+                       self.content_negotiation({'uuid': self.ldap_connection.getAttr(obj.dn, 'entryUUID')[0].decode('ASCII')})
                        return
 
                self.set_metadata(obj)

Alternatively with PostReadControl (untested):

diff --git management/univention-directory-manager-rest/src/univention/admin/rest/module.py management/univention-directory-manager-rest/src/univention/admin/rest/module.py
index afdb35f162..4811dcd46b 100755
--- management/univention-directory-manager-rest/src/univention/admin/rest/module.py
+++ management/univention-directory-manager-rest/src/univention/admin/rest/module.py
@@ -2800,11 +2800,21 @@ class Object(FormBase, Resource):
 
                obj = yield self.pool.submit(module.get, dn)
                if not obj:
-                       obj = yield self.create(object_type, dn)
+                       from ldap.controls.readentry import PostReadControl
+                       serverctrls = [PostReadControl(True, ['entryUUID'])]
+                       response = {}
+
+                       obj = yield self.create(object_type, dn, serverctrls=serverctrls, response=response)
                        self.set_header('Location', self.urljoin(quote_dn(obj.dn)))
                        self.set_status(201)
                        self.add_caching(public=False, must_revalidate=True)
-                       self.content_negotiation({})
+
+                       uuid = None
+                       for c in response.get('ctrls', []):
+                               if c.controlType == PostReadControl.controlType:
+                                       uuid = c.entry['entryUUID'][0].decode('ASCII')
+
+                       self.content_negotiation({'uuid': uuid})
                        return
 
                self.set_metadata(obj)
@@ -2859,7 +2869,7 @@ class Object(FormBase, Resource):
                raise Finish()
 
        @tornado.gen.coroutine
-       def create(self, object_type, dn=None):
+       def create(self, object_type, dn=None, **kwargs):
                module = self.get_module(object_type)
                container = self.request.body_arguments['position']
                superordinate = self.request.body_arguments['superordinate']
@@ -2884,7 +2894,7 @@ class Object(FormBase, Resource):
                if dn and not self.ldap_connection.compare_dn(dn, obj._ldap_dn()):
                        self.raise_sanitization_error('dn', _('Trying to create an object with wrong RDN.'))
 
-               dn = yield self.pool.submit(self.handle_udm_errors, obj.create)
+               dn = yield self.pool.submit(self.handle_udm_errors, obj.create, **kwargs)
                raise tornado.gen.Return(obj)
 
        @tornado.gen.coroutine
@@ -2894,12 +2904,12 @@ class Object(FormBase, Resource):
                yield self.pool.submit(self.handle_udm_errors, obj.modify)
                raise tornado.gen.Return(obj)
 
-       def handle_udm_errors(self, action):
+       def handle_udm_errors(self, action, *args, **kwargs):
                try:
                        exists_msg = None
                        error = None
                        try:
-                               return action()
+                               return action(*args, **kwargs)
                        except udm_errors.objectExists as exc:
                                exists_msg = 'dn: %s' % (exc.args[0],)
                                error = exc
Comment 2 Florian Best univentionstaff 2022-01-17 13:34:42 CET
The entryUUID (uuid) and DN (dn) has been added to the responses of object creations.
We should not add any other information here in the future for performance reasons.

univention-directory-manager-rest.yaml
9fcd42f02df7 | Bug #54347: return the entryUUID and DN after object creation

univention-directory-manager-rest (10.0.2-5)
9fcd42f02df7 | Bug #54347: return the entryUUID and DN after object creation
Comment 3 Florian Best univentionstaff 2022-01-18 10:11:49 CET
Added another commit so that this can also be achieved via PUT:

univention-directory-manager-rest (10.0.2-6)
fefd6f05e86d | Bug #54347: fix creation of object via PUT request
Comment 4 Arvid Requate univentionstaff 2022-01-18 10:43:29 CET
Verified:
* Code review; Ok
* Package update: Ok
* Function test: Ok
  * Before:
root@primary20:~# curl -k -X POST -H "Accept: application/json" -H "Content-Type: application/json"   https://Administrator:univention@localhost/univention/udm/users/user/ --data @1.json
{"_links": {"curies": [{"name": "udm", "templated": true, "href": "https://localhost/univention/udm/relation/{rel}"}]}}
  * After:
root@primary20:~# curl -k -X POST -H "Accept: application/json" -H "Content-Type: application/json"   https://Administrator:univention@localhost/univention/udm/users/user/ --data @1.json
{"dn": "uid=rest4,cn=users,dc=ucs50domain,dc=net", "uuid": "64501eca-0c8d-103c-9c46-8d8af8520506", "_links": {"curies": [{"name": "udm", "templated": true, "href": "https://localhost/univention/udm/relation/{rel}"}]}}
* Advisory: Ok
  * 03374ae4ab | Minor wording adjustment
Comment 5 Arvid Requate univentionstaff 2022-01-18 13:35:25 CET
Also works for user creation via PUT:

root@primary20:~# curl -k -X PUT \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://Administrator:univention@localhost/univention/udm/users/user/uid=rest7,cn=users,dc=ucs50domain,dc=net \
  --data @user.json
{"dn": "uid=rest7,cn=users,dc=ucs50domain,dc=net", "uuid": "b29872f6-0ca3-103c-9c70-8d8af8520506", "_links": {"curies": [{"name": "udm", "templated": true, "href": "https://localhost/univention/udm/relation/{rel}"}]}}