Unable to delete domain if user from other domain was added

Bug #1262360 reported by Sergii Kashaba
14
This bug affects 3 people
Affects Status Importance Assigned to Milestone
OpenStack Identity (keystone)
Fix Released
Medium
Alexey Miroshkin

Bug Description

I tried a use case where I
1. Have list of users (in the LDAP) in the default domain
2. Create domain
3. Add member role to user for the new domain
4. Remove role
5. Try do delete domain

And there I got the problem
{"domain": {"enabled": false, "id": "5bc213efd70e4ec197c8a85350b127e3", "links": {"self": "http://127.0.0.1:5000/v3/domains/5bc213efd70e4ec197c8a85350b127e3"}, "name": "test-domain"}}####Delete domain
HTTP/1.1 500 Internal Server Error
Vary: X-Auth-Token
Content-Type: application/json
Content-Length: 450
Date: Wed, 18 Dec 2013 19:47:47 GMT

{"error": {"message": "An unexpected error prevented the server from fulfilling your request. (IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`keystone`.`user_domain_metadata`, CONSTRAINT `user_domain_metadata_ibfk_2` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`))') 'DELETE FROM domain WHERE domain.id = %s' ('5bc213efd70e4ec197c8a85350b127e3',)", "code": 500, "title": "Internal Server Error"}}####List domains

mysql> select * from domain where id="5bc213efd70e4ec197c8a85350b127e3";
+----------------------------------+-------------+---------+-------+
| id | name | enabled | extra |
+----------------------------------+-------------+---------+-------+
| 5bc213efd70e4ec197c8a85350b127e3 | test-domain | 0 | {} |
+----------------------------------+-------------+---------+-------+
1 row in set (0.00 sec)
mysql> select * from user_domain_metadata;
+----------------------------------+----------------------------------+---------------+
| user_id | domain_id | data |
+----------------------------------+----------------------------------+---------------+
| d0c8e779e094463cabc425f1d222e7e0 | 5bc213efd70e4ec197c8a85350b127e3 | {"roles": []} |
+----------------------------------+----------------------------------+---------------+
1 row in set (0.00 sec)

Changed in keystone:
assignee: nobody → Wu Wenxiang (wu-wenxiang)
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Fix proposed to keystone (master)

Fix proposed to branch: master
Review: https://review.openstack.org/64607

Changed in keystone:
status: New → In Progress
Revision history for this message
Wu Wenxiang (wu-wenxiang) wrote :
Download full text (3.8 KiB)

Hello, I try to add unittest to repro this problem.
However I find create/delete domain with LDAP backends was not supported yet.
How could you do this?

My test cases in test_backends:

diff --git a/keystone/tests/test_backend.py b/keystone/tests/test_backend.py
index 5a66ea0..8fbf2bd 100644
--- a/keystone/tests/test_backend.py
+++ b/keystone/tests/test_backend.py
@@ -2458,6 +2458,22 @@ class IdentityTests(object):
         project_ref = self.assignment_api.get_project(project['id'])
         self.assertDictEqual(project_ref, project)

+ def test_delete_domain_with_user_added(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'enabled': True}
+ self.assignment_api.create_domain(domain['id'], domain)
+ domain_ref = self.assignment_api.get_domain(domain['id'])
+ self.assertDictEqual(domain_ref, domain)
+
+ self.assignment_api.create_grant(user_id=self.user_foo['id'],
+ domain_id=domain['id'],
+ role_id=self.role_member['id'])
+
+ self.assignment_api.delete_domain(domain['id'])
+ self.assertRaises(exception.DomainNotFound,
+ self.assignment_api.get_domain,
+ domain['id'])
+
     def test_domain_crud(self):
         domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
                   'enabled': True}

Traceback (most recent call last):
  File "keystone/tests/test_backend.py", line 2464, in test_delete_domain_with_user_added
    self.assignment_api.create_domain(domain['id'], domain)
  File "keystone/assignment/core.py", line 273, in create_domain
    ret = self.driver.create_domain(domain_id, domain)
  File "keystone/assignment/backends/ldap.py", line 201, in create_domain
    raise exception.Forbidden('Domains are read-only against LDAP')
Forbidden: You are not authorized to perform the requested action.

And in test_backends_LDAP, I find:

# TODO(henry-nash): These need to be removed when the full LDAP implementation
# is submitted - see Bugs 1092187, 1101287, 1101276, 1101289

    def test_domain_crud(self):
        domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
                  'enabled': True, 'description': uuid.uuid4().hex}
        self.assertRaises(exception.Forbidden,
                          self.assignment_api.create_domain,
                          domain['id'],
                          domain)
        self.assertRaises(exception.Conflict,
                          self.assignment_api.create_domain,
                          CONF.identity.default_domain_id,
                          domain)
        self.assertRaises(exception.DomainNotFound,
                          self.assignment_api.get_domain,
                          domain['id'])

        domain['description'] = uuid.uuid4().hex
        self.assertRaises(exception.DomainNotFound,
                          self.assignment_api.update_domain,
                          domain['id'],
                          domain)
        self.assertRaises(exception.Forbidden,
                          self.assignment_api.update_domain,
              ...

Read more...

Changed in keystone:
status: In Progress → Incomplete
Revision history for this message
Sergii Kashaba (skashaba) wrote :

I have ldap used only for identity backend
[identity]
driver = keystone.identity.backends.ldap.Identity
[assignment]
driver = keystone.assignment.backends.sql.Assignment

So domains are created in the sql backend
Only users and passwords are in the LDAP.

Revision history for this message
Sergii Kashaba (skashaba) wrote :
Download full text (4.5 KiB)

Issue is not reproduced on sqlite backend, which is obviously not a production one. Is reqproduced on the mysql.
I put your code (slightly modified) to LdapIdentitySqlAssignment.

    def test_delete_domain_with_user_added(self):
        domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
                  'enabled': True}
        project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
                   'domain_id': domain['id'],
                   'description': uuid.uuid4().hex, 'enabled': True
                   }
        self.assignment_api.create_domain(domain['id'], domain)
        self.assignment_api.create_project(project['id'], project)
        project_ref = self.assignment_api.get_project(project['id'])
        self.assertDictEqual(project_ref, project)

        self.assignment_api.create_grant(user_id=self.user_foo['id'],
                                         project_id=project['id'],
                                         role_id=self.role_member['id'])
        self.assignment_api.delete_grant(user_id=self.user_foo['id'],
                                         project_id=project['id'],
                                         role_id=self.role_member['id'])
        self.assignment_api.delete_domain(domain['id'])
        self.assertRaises(exception.DomainNotFound,
                          self.assignment_api.get_domain,
                          domain['id'])
Test pass ok
But, when I changed config keystone/tests/backend_ldap_sql.conf
#connection = sqlite://
connection = mysql://root:qwerty@127.0.0.1/keystone_test?charset=utf8
Issue was reproduced
nosetests keystone.tests.test_backend_ldap:LdapIdentitySqlAssignment.test_delete_domain_with_user_added
======================================================================
ERROR: keystone.tests.test_backend_ldap.LdapIdentitySqlAssignment.test_delete_domain_with_user_added
----------------------------------------------------------------------
_StringException: traceback-1: {{{
Traceback (most recent call last):
  File "/opt/stack/keystone/keystone/tests/test_backend_ldap.py", line 965, in tearDown
    sql.ModelBase.metadata.drop_all(bind=self.engine)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/schema.py", line 2598, in drop_all
    tables=tables)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 2302, in _run_visitor
    conn._run_visitor(visitorcallable, element, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1972, in _run_visitor
    **kwargs).traverse_single(element)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/sql/visitors.py", line 106, in traverse_single
    return meth(obj, **kw)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/ddl.py", line 130, in visit_metadata
    self.traverse_single(table, drop_ok=True)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/sql/visitors.py", line 106, in traverse_sin...

Read more...

Revision history for this message
Sergii Kashaba (skashaba) wrote :
Download full text (5.9 KiB)

I copy-pasted wrong part of traceback in previous comment. Correct one is
Traceback (most recent call last):
  File "/opt/stack/keystone/keystone/tests/test_backend_ldap.py", line 1004, in test_delete_domain_with_user_added
    self.assignment_api.delete_domain(domain['id'])
  File "/opt/stack/keystone/keystone/assignment/core.py", line 284, in delete_domain
    self.driver.delete_domain(domain_id)
  File "/opt/stack/keystone/keystone/assignment/backends/sql.py", line 559, in delete_domain
    session.flush()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 1718, in flush
    self._flush(objects)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 1789, in _flush
    flush_context.execute()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/unitofwork.py", line 331, in execute
    rec.execute(self)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/unitofwork.py", line 498, in execute
    uow
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/persistence.py", line 115, in delete_obj
    cached_connections, mapper, table, delete)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/persistence.py", line 671, in _emit_delete_statements
    connection.execute(statement, del_objects)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1449, in execute
    params)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1584, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1698, in _execute_context
    context)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1691, in _execute_context
    context)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.py", line 331, in do_execute
    cursor.execute(statement, parameters)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/cursors.py", line 174, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
IntegrityError: (IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`keystone_test`.`project`, CONSTRAINT `project_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`))') 'DELETE FROM domain WHERE domain.id = %s' ('6ec418ea6e6b41f7ac6c6b916819e913',)
-----------------------------------------------------------------------------------------------------------------------------------------
When i used your test case the result was almost the same
Traceback (most recent call last):
  File "/opt/stack/keystone/keyston...

Read more...

Revision history for this message
Sergii Kashaba (skashaba) wrote :

Git diff
diff --git a/keystone/tests/backend_ldap_sql.conf b/keystone/tests/backend_ldap_sql.conf
index 5579e75..e64e9bf 100644
--- a/keystone/tests/backend_ldap_sql.conf
+++ b/keystone/tests/backend_ldap_sql.conf
@@ -1,5 +1,6 @@
 [sql]
-connection = sqlite://
+#connection = sqlite://
+connection = mysql://root:qwerty@127.0.0.1/keystone_test?charset=utf8
 #For a file based sqlite use
 #connection = sqlite:////tmp/keystone.db
 #To Test MySQL:
diff --git a/keystone/tests/test_backend_ldap.py b/keystone/tests/test_backend_ldap.py
index 103ccf2..aec2f66 100644
--- a/keystone/tests/test_backend_ldap.py
+++ b/keystone/tests/test_backend_ldap.py
@@ -980,6 +980,27 @@ class LdapIdentitySqlAssignment(sql.Base, tests.TestCase, BaseLDAPIdentity):
         self.skipTest(
             'N/A: Not part of SQL backend')

+ def test_delete_domain_with_user_added(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'enabled': True}
+ project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'domain_id': domain['id'],
+ 'description': uuid.uuid4().hex, 'enabled': True
+ }
+ self.assignment_api.create_domain(domain['id'], domain)
+ domain_ref = self.assignment_api.get_domain(domain['id'])
+ self.assertDictEqual(domain_ref, domain)
+
+ self.assignment_api.create_grant(user_id=self.user_foo['id'],
+ domain_id=domain['id'],
+ role_id=self.role_member['id'])
+ self.assignment_api.delete_grant(user_id=self.user_foo['id'],
+ domain_id=domain['id'],
+ role_id=self.role_member['id'])
+ self.assignment_api.delete_domain(domain['id'])
+ self.assertRaises(exception.DomainNotFound,
+ self.assignment_api.get_domain,
+ domain['id'])

Changed in keystone:
status: Incomplete → New
Revision history for this message
Sergii Kashaba (skashaba) wrote :

Guys, good job! 3 months and there are no any progress.

Changed in keystone:
assignee: Wu Wenxiang (wu-wenxiang) → nobody
Dolph Mathews (dolph)
Changed in keystone:
importance: Undecided → Medium
status: New → Triaged
tags: added: sql
tags: added: havana-backport-potential icehouse-rc-potential
Thierry Carrez (ttx)
tags: added: icehouse-backport-potential
removed: icehouse-rc-potential
Changed in keystone:
assignee: nobody → Alexey Miroshkin (amirosh)
Revision history for this message
Alexey Miroshkin (amirosh) wrote :

According Henry Nash "Things have changed quite a bit since this bug was raised. The most significant is that my patches to support domain-specific LDAPs. The rule is that if you have the standard LDAP driver for identity (without configuring domain-specifc LDAPs), then you can really only have the default domain."

The suggested test is valid in MultiLDAPandSQLIdentity. However we should prevent creation of new domains using standard LDAP driver for identity.

Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

Fix proposed to branch: master
Review: https://review.openstack.org/116858

Changed in keystone:
status: Triaged → In Progress
Dolph Mathews (dolph)
Changed in keystone:
milestone: none → juno-rc1
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Fix merged to keystone (master)

Reviewed: https://review.openstack.org/116858
Committed: https://git.openstack.org/cgit/openstack/keystone/commit/?id=0da55a921f4bac227a743be991705986294e94de
Submitter: Jenkins
Branch: master

commit 0da55a921f4bac227a743be991705986294e94de
Author: Alexey Miroshkin <email address hidden>
Date: Tue Aug 26 15:34:22 2014 +0400

    Prevent domains creation for the default LDAP+SQL

    This patch is supposed to fix the bug 'Unable to delete domain if user
    from other domain was added', but because of the recent changes this
    should be done by configuring domain-specifc LDAPs. However the original
    scenario must not be reproducible.

    The rule is that if you have the standard LDAP driver for identity
    (without configuring domain-specifc LDAPs), then you can really only
    have the default domain. However currently it's possible to create
    domains with the folowing configuration:

    [identity]
    driver = keystone.identity.backends.ldap.Identity
    [assignment]
    driver = keystone.assignment.backends.sql.Assignment

    At the same time such new domains can not be updated nor deleted. This
    fix prevents new domains creation for such configuration.

    Also this fix adds an additional test to MultiLDAPandSQLIdentity to make
    sure that the original bug has gone.

    Closes-Bug: #1262360

    Change-Id: I3baee40f74cefaae601aea34e75630cc618ac31a

Changed in keystone:
status: In Progress → Fix Committed
tags: removed: havana-backport-potential
Thierry Carrez (ttx)
Changed in keystone:
status: Fix Committed → Fix Released
Thierry Carrez (ttx)
Changed in keystone:
milestone: juno-rc1 → 2014.2
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.