1 |
prometheanfire 14/06/01 02:19:16 |
2 |
|
3 |
Added: 2014.1-CVE-2014-0204.patch |
4 |
Log: |
5 |
fix for CVE-2014-0204 bug 511000 |
6 |
|
7 |
(Portage version: 2.2.8-r1/cvs/Linux x86_64, signed Manifest commit with key 0x2471eb3e40ac5ac3) |
8 |
|
9 |
Revision Changes Path |
10 |
1.1 sys-auth/keystone/files/2014.1-CVE-2014-0204.patch |
11 |
|
12 |
file : http://sources.gentoo.org/viewvc.cgi/gentoo-x86/sys-auth/keystone/files/2014.1-CVE-2014-0204.patch?rev=1.1&view=markup |
13 |
plain: http://sources.gentoo.org/viewvc.cgi/gentoo-x86/sys-auth/keystone/files/2014.1-CVE-2014-0204.patch?rev=1.1&content-type=text/plain |
14 |
|
15 |
Index: 2014.1-CVE-2014-0204.patch |
16 |
=================================================================== |
17 |
From 786af9829c5329a982e3451f77afebbfb21850bd Mon Sep 17 00:00:00 2001 |
18 |
From: Brant Knudson <bknudson@××××××.com> |
19 |
Date: Fri, 18 Apr 2014 11:18:42 -0500 |
20 |
Subject: [PATCH] SQL and LDAP fixes for get_roles_for_user_and_project |
21 |
user=group ID |
22 |
|
23 |
When there was a role assigned to a group with the same ID as a user, |
24 |
the SQL and LDAP assignment backends would incorrectly return the |
25 |
assignment to the group when requesting roles for the user via the |
26 |
get_roles_for_user_and_project method. |
27 |
|
28 |
With this change, assignments to a group with the same ID are not |
29 |
returned for the user when calling get_roles_for_user_and_project. |
30 |
|
31 |
Functions were added to compare DNs more accurately based on the |
32 |
LDAP RFCs. |
33 |
|
34 |
The fakeldap code was changed to normalize the values when |
35 |
comparing values for checking if the values match the filter. |
36 |
|
37 |
Co-Authored By: Nathan Kinder <nkinder@××××××.com> |
38 |
Co-Authored By: Adam Young <ayoung@××××××.com> |
39 |
|
40 |
Change-Id: Id3d6f66c995e65e37d909359420d71ecdde86b69 |
41 |
Closes-Bug: #1309228 |
42 |
--- |
43 |
keystone/assignment/backends/ldap.py | 11 +-- |
44 |
keystone/assignment/backends/sql.py | 15 +++ |
45 |
keystone/common/ldap/core.py | 110 +++++++++++++++++++++ |
46 |
keystone/tests/fakeldap.py | 17 +++- |
47 |
keystone/tests/test_backend.py | 37 +++++++ |
48 |
keystone/tests/test_backend_ldap.py | 28 ++++++ |
49 |
keystone/tests/unit/common/test_ldap.py | 169 ++++++++++++++++++++++++++++++++ |
50 |
7 files changed, 378 insertions(+), 9 deletions(-) |
51 |
create mode 100644 keystone/tests/unit/common/test_ldap.py |
52 |
|
53 |
diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py |
54 |
index 2afd339..09b0f01 100644 |
55 |
--- a/keystone/assignment/backends/ldap.py |
56 |
+++ b/keystone/assignment/backends/ldap.py |
57 |
@@ -88,24 +88,19 @@ def _get_metadata(self, user_id=None, tenant_id=None, |
58 |
|
59 |
def _get_roles_for_just_user_and_project(user_id, tenant_id): |
60 |
self.get_project(tenant_id) |
61 |
+ user_dn = self.user._id_to_dn(user_id) |
62 |
return [self.role._dn_to_id(a.role_dn) |
63 |
for a in self.role.get_role_assignments |
64 |
(self.project._id_to_dn(tenant_id)) |
65 |
- if self.user._dn_to_id(a.user_dn) == user_id] |
66 |
+ if common_ldap.is_dn_equal(a.user_dn, user_dn)] |
67 |
|
68 |
def _get_roles_for_group_and_project(group_id, project_id): |
69 |
self.get_project(project_id) |
70 |
group_dn = self.group._id_to_dn(group_id) |
71 |
- # NOTE(marcos-fermin-lobo): In Active Directory, for functions |
72 |
- # such as "self.role.get_role_assignments", it returns |
73 |
- # the key "CN" or "OU" in uppercase. |
74 |
- # The group_dn var has "CN" and "OU" in lowercase. |
75 |
- # For this reason, it is necessary to use the "upper()" |
76 |
- # function so both are consistent. |
77 |
return [self.role._dn_to_id(a.role_dn) |
78 |
for a in self.role.get_role_assignments |
79 |
(self.project._id_to_dn(project_id)) |
80 |
- if a.user_dn.upper() == group_dn.upper()] |
81 |
+ if common_ldap.is_dn_equal(a.user_dn, group_dn)] |
82 |
|
83 |
if domain_id is not None: |
84 |
msg = _('Domain metadata not supported by LDAP') |
85 |
diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py |
86 |
index 1d8c78f..b546a42 100644 |
87 |
--- a/keystone/assignment/backends/sql.py |
88 |
+++ b/keystone/assignment/backends/sql.py |
89 |
@@ -86,6 +86,21 @@ def _get_metadata(self, user_id=None, tenant_id=None, |
90 |
session = sql.get_session() |
91 |
|
92 |
q = session.query(RoleAssignment) |
93 |
+ |
94 |
+ def _calc_assignment_type(): |
95 |
+ # Figure out the assignment type we're checking for from the args. |
96 |
+ if user_id: |
97 |
+ if tenant_id: |
98 |
+ return AssignmentType.USER_PROJECT |
99 |
+ else: |
100 |
+ return AssignmentType.USER_DOMAIN |
101 |
+ else: |
102 |
+ if tenant_id: |
103 |
+ return AssignmentType.GROUP_PROJECT |
104 |
+ else: |
105 |
+ return AssignmentType.GROUP_DOMAIN |
106 |
+ |
107 |
+ q = q.filter_by(type=_calc_assignment_type()) |
108 |
q = q.filter_by(actor_id=user_id or group_id) |
109 |
q = q.filter_by(target_id=tenant_id or domain_id) |
110 |
refs = q.all() |
111 |
diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py |
112 |
index e8d1dc0..9561650 100644 |
113 |
--- a/keystone/common/ldap/core.py |
114 |
+++ b/keystone/common/ldap/core.py |
115 |
@@ -13,6 +13,7 @@ |
116 |
# under the License. |
117 |
|
118 |
import os.path |
119 |
+import re |
120 |
|
121 |
import ldap |
122 |
import ldap.filter |
123 |
@@ -101,6 +102,115 @@ def ldap_scope(scope): |
124 |
'options': ', '.join(LDAP_SCOPES.keys())}) |
125 |
|
126 |
|
127 |
+def prep_case_insensitive(value): |
128 |
+ """Prepare a string for case-insensitive comparison. |
129 |
+ |
130 |
+ This is defined in RFC4518. For simplicity, all this function does is |
131 |
+ lowercase all the characters, strip leading and trailing whitespace, |
132 |
+ and compress sequences of spaces to a single space. |
133 |
+ """ |
134 |
+ value = re.sub(r'\s+', ' ', value.strip().lower()) |
135 |
+ return value |
136 |
+ |
137 |
+ |
138 |
+def is_ava_value_equal(attribute_type, val1, val2): |
139 |
+ """Returns True if and only if the AVAs are equal. |
140 |
+ |
141 |
+ When comparing AVAs, the equality matching rule for the attribute type |
142 |
+ should be taken into consideration. For simplicity, this implementation |
143 |
+ does a case-insensitive comparison. |
144 |
+ |
145 |
+ Note that this function uses prep_case_insenstive so the limitations of |
146 |
+ that function apply here. |
147 |
+ |
148 |
+ """ |
149 |
+ |
150 |
+ return prep_case_insensitive(val1) == prep_case_insensitive(val2) |
151 |
+ |
152 |
+ |
153 |
+def is_rdn_equal(rdn1, rdn2): |
154 |
+ """Returns True if and only if the RDNs are equal. |
155 |
+ |
156 |
+ * RDNs must have the same number of AVAs. |
157 |
+ * Each AVA of the RDNs must be the equal for the same attribute type. The |
158 |
+ order isn't significant. Note that an attribute type will only be in one |
159 |
+ AVA in an RDN, otherwise the DN wouldn't be valid. |
160 |
+ * Attribute types aren't case sensitive. Note that attribute type |
161 |
+ comparison is more complicated than implemented. This function only |
162 |
+ compares case-insentive. The code should handle multiple names for an |
163 |
+ attribute type (e.g., cn, commonName, and 2.5.4.3 are the same). |
164 |
+ |
165 |
+ Note that this function uses is_ava_value_equal to compare AVAs so the |
166 |
+ limitations of that function apply here. |
167 |
+ |
168 |
+ """ |
169 |
+ |
170 |
+ if len(rdn1) != len(rdn2): |
171 |
+ return False |
172 |
+ |
173 |
+ for attr_type_1, val1, dummy in rdn1: |
174 |
+ found = False |
175 |
+ for attr_type_2, val2, dummy in rdn2: |
176 |
+ if attr_type_1.lower() != attr_type_2.lower(): |
177 |
+ continue |
178 |
+ |
179 |
+ found = True |
180 |
+ if not is_ava_value_equal(attr_type_1, val1, val2): |
181 |
+ return False |
182 |
+ break |
183 |
+ if not found: |
184 |
+ return False |
185 |
+ |
186 |
+ return True |
187 |
+ |
188 |
+ |
189 |
+def is_dn_equal(dn1, dn2): |
190 |
+ """Returns True if and only if the DNs are equal. |
191 |
+ |
192 |
+ Two DNs are equal if they've got the same number of RDNs and if the RDNs |
193 |
+ are the same at each position. See RFC4517. |
194 |
+ |
195 |
+ Note that this function uses is_rdn_equal to compare RDNs so the |
196 |
+ limitations of that function apply here. |
197 |
+ |
198 |
+ :param dn1: Either a string DN or a DN parsed by ldap.dn.str2dn. |
199 |
+ :param dn2: Either a string DN or a DN parsed by ldap.dn.str2dn. |
200 |
+ |
201 |
+ """ |
202 |
+ |
203 |
+ if not isinstance(dn1, list): |
204 |
+ dn1 = ldap.dn.str2dn(dn1) |
205 |
+ if not isinstance(dn2, list): |
206 |
+ dn2 = ldap.dn.str2dn(dn2) |
207 |
+ |
208 |
+ if len(dn1) != len(dn2): |
209 |
+ return False |
210 |
+ |
211 |
+ for rdn1, rdn2 in zip(dn1, dn2): |
212 |
+ if not is_rdn_equal(rdn1, rdn2): |
213 |
+ return False |
214 |
+ return True |
215 |
+ |
216 |
+ |
217 |
+def dn_startswith(descendant_dn, dn): |
218 |
+ """Returns True if and only if the descendant_dn is under the dn. |
219 |
+ |
220 |
+ :param descendant_dn: Either a string DN or a DN parsed by ldap.dn.str2dn. |
221 |
+ :param dn: Either a string DN or a DN parsed by ldap.dn.str2dn. |
222 |
+ |
223 |
+ """ |
224 |
+ |
225 |
+ if not isinstance(descendant_dn, list): |
226 |
+ descendant_dn = ldap.dn.str2dn(descendant_dn) |
227 |
+ if not isinstance(dn, list): |
228 |
+ dn = ldap.dn.str2dn(dn) |
229 |
+ |
230 |
+ if len(descendant_dn) <= len(dn): |
231 |
+ return False |
232 |
+ |
233 |
+ return is_dn_equal(descendant_dn[len(dn):], dn) |
234 |
+ |
235 |
+ |
236 |
_HANDLERS = {} |
237 |
|
238 |
|
239 |
diff --git a/keystone/tests/fakeldap.py b/keystone/tests/fakeldap.py |
240 |
index 8347d68..21e1bd3 100644 |
241 |
--- a/keystone/tests/fakeldap.py |
242 |
+++ b/keystone/tests/fakeldap.py |
243 |
@@ -51,6 +51,19 @@ def _process_attr(attr_name, value_or_values): |
244 |
|
245 |
def normalize_dn(dn): |
246 |
# Capitalize the attribute names as an LDAP server might. |
247 |
+ |
248 |
+ # NOTE(blk-u): Special case for this tested value, used with |
249 |
+ # test_user_id_comma. The call to str2dn here isn't always correct |
250 |
+ # here, because `dn` is escaped for an LDAP filter. str2dn() normally |
251 |
+ # works only because there's no special characters in `dn`. |
252 |
+ if dn == 'cn=Doe\\5c, John,ou=Users,cn=example,cn=com': |
253 |
+ return 'CN=Doe\\, John,OU=Users,CN=example,CN=com' |
254 |
+ |
255 |
+ # NOTE(blk-u): Another special case for this tested value. When a |
256 |
+ # roleOccupant has an escaped comma, it gets converted to \2C. |
257 |
+ if dn == 'cn=Doe\\, John,ou=Users,cn=example,cn=com': |
258 |
+ return 'CN=Doe\\2C John,OU=Users,CN=example,CN=com' |
259 |
+ |
260 |
dn = ldap.dn.str2dn(dn) |
261 |
norm = [] |
262 |
for part in dn: |
263 |
@@ -118,7 +131,9 @@ def _match(key, value, attrs): |
264 |
str_sids = [str(x) for x in attrs[key]] |
265 |
return str(value) in str_sids |
266 |
if key != 'objectclass': |
267 |
- return _process_attr(key, value)[0] in attrs[key] |
268 |
+ check_value = _process_attr(key, value)[0] |
269 |
+ norm_values = list(_process_attr(key, x)[0] for x in attrs[key]) |
270 |
+ return check_value in norm_values |
271 |
# it is an objectclass check, so check subclasses |
272 |
values = _subs(value) |
273 |
for v in values: |
274 |
diff --git a/keystone/tests/test_backend.py b/keystone/tests/test_backend.py |
275 |
index c8d7341..b42b209 100644 |
276 |
--- a/keystone/tests/test_backend.py |
277 |
+++ b/keystone/tests/test_backend.py |
278 |
@@ -1377,6 +1377,43 @@ def test_multi_group_grants_on_project_domain(self): |
279 |
self.assertIn(role_list[1]['id'], combined_role_list) |
280 |
self.assertIn(role_list[2]['id'], combined_role_list) |
281 |
|
282 |
+ def test_get_roles_for_user_and_project_user_group_same_id(self): |
283 |
+ """When a user has the same ID as a group, |
284 |
+ get_roles_for_user_and_project returns only the roles for the user and |
285 |
+ not the group. |
286 |
+ |
287 |
+ """ |
288 |
+ |
289 |
+ # Setup: create user, group with same ID, role, and project; |
290 |
+ # assign the group the role on the project. |
291 |
+ |
292 |
+ user_group_id = uuid.uuid4().hex |
293 |
+ |
294 |
+ user1 = {'id': user_group_id, 'name': uuid.uuid4().hex, |
295 |
+ 'domain_id': DEFAULT_DOMAIN_ID, } |
296 |
+ self.identity_api.create_user(user_group_id, user1) |
297 |
+ |
298 |
+ group1 = {'id': user_group_id, 'name': uuid.uuid4().hex, |
299 |
+ 'domain_id': DEFAULT_DOMAIN_ID, } |
300 |
+ self.identity_api.create_group(user_group_id, group1) |
301 |
+ |
302 |
+ role1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} |
303 |
+ self.assignment_api.create_role(role1['id'], role1) |
304 |
+ |
305 |
+ project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, |
306 |
+ 'domain_id': DEFAULT_DOMAIN_ID, } |
307 |
+ self.assignment_api.create_project(project1['id'], project1) |
308 |
+ |
309 |
+ self.assignment_api.create_grant(role1['id'], |
310 |
+ group_id=user_group_id, |
311 |
+ project_id=project1['id']) |
312 |
+ |
313 |
+ # Check the roles, shouldn't be any since the user wasn't granted any. |
314 |
+ roles = self.assignment_api.get_roles_for_user_and_project( |
315 |
+ user_group_id, project1['id']) |
316 |
+ |
317 |
+ self.assertEqual([], roles, 'role for group is %s' % role1['id']) |
318 |
+ |
319 |
def test_delete_role_with_user_and_group_grants(self): |
320 |
role1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} |
321 |
self.assignment_api.create_role(role1['id'], role1) |
322 |
diff --git a/keystone/tests/test_backend_ldap.py b/keystone/tests/test_backend_ldap.py |
323 |
index 310fbbc..9964527 100644 |
324 |
--- a/keystone/tests/test_backend_ldap.py |
325 |
+++ b/keystone/tests/test_backend_ldap.py |
326 |
@@ -546,6 +546,34 @@ def test_new_arbitrary_attributes_are_returned_from_update_user(self): |
327 |
def test_updated_arbitrary_attributes_are_returned_from_update_user(self): |
328 |
self.skipTest("Using arbitrary attributes doesn't work under LDAP") |
329 |
|
330 |
+ def test_user_id_comma_grants(self): |
331 |
+ """Even if the user has a , in their ID, can get user and group grants. |
332 |
+ """ |
333 |
+ |
334 |
+ # Create a user with a , in their ID |
335 |
+ # NOTE(blk-u): the DN for this user is hard-coded in fakeldap! |
336 |
+ user_id = u'Doe, John' |
337 |
+ user = { |
338 |
+ 'id': user_id, |
339 |
+ 'name': self.getUniqueString(), |
340 |
+ 'password': self.getUniqueString(), |
341 |
+ 'domain_id': CONF.identity.default_domain_id, |
342 |
+ } |
343 |
+ self.identity_api.create_user(user_id, user) |
344 |
+ |
345 |
+ # Grant the user a role on a project. |
346 |
+ |
347 |
+ role_id = 'member' |
348 |
+ project_id = self.tenant_baz['id'] |
349 |
+ |
350 |
+ self.assignment_api.create_grant(role_id, user_id=user_id, |
351 |
+ project_id=project_id) |
352 |
+ |
353 |
+ role_ref = self.assignment_api.get_grant(role_id, user_id=user_id, |
354 |
+ project_id=project_id) |
355 |
+ |
356 |
+ self.assertEqual(role_id, role_ref['id']) |
357 |
+ |
358 |
|
359 |
class LDAPIdentity(BaseLDAPIdentity, tests.TestCase): |
360 |
def setUp(self): |
361 |
diff --git a/keystone/tests/unit/common/test_ldap.py b/keystone/tests/unit/common/test_ldap.py |
362 |
new file mode 100644 |
363 |
index 0000000..220bf1a |
364 |
--- /dev/null |
365 |
+++ b/keystone/tests/unit/common/test_ldap.py |
366 |
@@ -0,0 +1,169 @@ |
367 |
+# Licensed under the Apache License, Version 2.0 (the "License"); you may |
368 |
+# not use this file except in compliance with the License. You may obtain |
369 |
+# a copy of the License at |
370 |
+# |
371 |
+# http://www.apache.org/licenses/LICENSE-2.0 |
372 |
+# |
373 |
+# Unless required by applicable law or agreed to in writing, software |
374 |
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
375 |
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
376 |
+# License for the specific language governing permissions and limitations |
377 |
+# under the License. |
378 |
+ |
379 |
+import ldap.dn |
380 |
+ |
381 |
+from keystone.common import ldap as ks_ldap |
382 |
+from keystone import tests |
383 |
+ |
384 |
+ |
385 |
+class DnCompareTest(tests.BaseTestCase): |
386 |
+ """Tests for the DN comparison functions in keystone.common.ldap.core.""" |
387 |
+ |
388 |
+ def test_prep(self): |
389 |
+ # prep_case_insensitive returns the string with spaces at the front and |
390 |
+ # end if it's already lowercase and no insignificant characters. |
391 |
+ value = 'lowercase value' |
392 |
+ self.assertEqual(value, ks_ldap.prep_case_insensitive(value)) |
393 |
+ |
394 |
+ def test_prep_lowercase(self): |
395 |
+ # prep_case_insensitive returns the string with spaces at the front and |
396 |
+ # end and lowercases the value. |
397 |
+ value = 'UPPERCASE VALUE' |
398 |
+ exp_value = value.lower() |
399 |
+ self.assertEqual(exp_value, ks_ldap.prep_case_insensitive(value)) |
400 |
+ |
401 |
+ def test_prep_insignificant(self): |
402 |
+ # prep_case_insensitive remove insignificant spaces. |
403 |
+ value = 'before after' |
404 |
+ exp_value = 'before after' |
405 |
+ self.assertEqual(exp_value, ks_ldap.prep_case_insensitive(value)) |
406 |
+ |
407 |
+ def test_prep_insignificant_pre_post(self): |
408 |
+ # prep_case_insensitive remove insignificant spaces. |
409 |
+ value = ' value ' |
410 |
+ exp_value = 'value' |
411 |
+ self.assertEqual(exp_value, ks_ldap.prep_case_insensitive(value)) |
412 |
+ |
413 |
+ def test_ava_equal_same(self): |
414 |
+ # is_ava_value_equal returns True if the two values are the same. |
415 |
+ value = 'val1' |
416 |
+ self.assertTrue(ks_ldap.is_ava_value_equal('cn', value, value)) |
417 |
+ |
418 |
+ def test_ava_equal_complex(self): |
419 |
+ # is_ava_value_equal returns True if the two values are the same using |
420 |
+ # a value that's got different capitalization and insignificant chars. |
421 |
+ val1 = 'before after' |
422 |
+ val2 = ' BEFORE afTer ' |
423 |
+ self.assertTrue(ks_ldap.is_ava_value_equal('cn', val1, val2)) |
424 |
+ |
425 |
+ def test_ava_different(self): |
426 |
+ # is_ava_value_equal returns False if the values aren't the same. |
427 |
+ self.assertFalse(ks_ldap.is_ava_value_equal('cn', 'val1', 'val2')) |
428 |
+ |
429 |
+ def test_rdn_same(self): |
430 |
+ # is_rdn_equal returns True if the two values are the same. |
431 |
+ rdn = ldap.dn.str2dn('cn=val1')[0] |
432 |
+ self.assertTrue(ks_ldap.is_rdn_equal(rdn, rdn)) |
433 |
+ |
434 |
+ def test_rdn_diff_length(self): |
435 |
+ # is_rdn_equal returns False if the RDNs have a different number of |
436 |
+ # AVAs. |
437 |
+ rdn1 = ldap.dn.str2dn('cn=cn1')[0] |
438 |
+ rdn2 = ldap.dn.str2dn('cn=cn1+ou=ou1')[0] |
439 |
+ self.assertFalse(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
440 |
+ |
441 |
+ def test_rdn_multi_ava_same_order(self): |
442 |
+ # is_rdn_equal returns True if the RDNs have the same number of AVAs |
443 |
+ # and the values are the same. |
444 |
+ rdn1 = ldap.dn.str2dn('cn=cn1+ou=ou1')[0] |
445 |
+ rdn2 = ldap.dn.str2dn('cn=CN1+ou=OU1')[0] |
446 |
+ self.assertTrue(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
447 |
+ |
448 |
+ def test_rdn_multi_ava_diff_order(self): |
449 |
+ # is_rdn_equal returns True if the RDNs have the same number of AVAs |
450 |
+ # and the values are the same, even if in a different order |
451 |
+ rdn1 = ldap.dn.str2dn('cn=cn1+ou=ou1')[0] |
452 |
+ rdn2 = ldap.dn.str2dn('ou=OU1+cn=CN1')[0] |
453 |
+ self.assertTrue(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
454 |
+ |
455 |
+ def test_rdn_multi_ava_diff_type(self): |
456 |
+ # is_rdn_equal returns False if the RDNs have the same number of AVAs |
457 |
+ # and the attribute types are different. |
458 |
+ rdn1 = ldap.dn.str2dn('cn=cn1+ou=ou1')[0] |
459 |
+ rdn2 = ldap.dn.str2dn('cn=cn1+sn=sn1')[0] |
460 |
+ self.assertFalse(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
461 |
+ |
462 |
+ def test_rdn_attr_type_case_diff(self): |
463 |
+ # is_rdn_equal returns True for same RDNs even when attr type case is |
464 |
+ # different. |
465 |
+ rdn1 = ldap.dn.str2dn('cn=cn1')[0] |
466 |
+ rdn2 = ldap.dn.str2dn('CN=cn1')[0] |
467 |
+ self.assertTrue(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
468 |
+ |
469 |
+ def test_rdn_attr_type_alias(self): |
470 |
+ # is_rdn_equal returns False for same RDNs even when attr type alias is |
471 |
+ # used. Note that this is a limitation since an LDAP server should |
472 |
+ # consider them equal. |
473 |
+ rdn1 = ldap.dn.str2dn('cn=cn1')[0] |
474 |
+ rdn2 = ldap.dn.str2dn('2.5.4.3=cn1')[0] |
475 |
+ self.assertFalse(ks_ldap.is_rdn_equal(rdn1, rdn2)) |
476 |
+ |
477 |
+ def test_dn_same(self): |
478 |
+ # is_dn_equal returns True if the DNs are the same. |
479 |
+ dn = 'cn=Babs Jansen,ou=OpenStack' |
480 |
+ self.assertTrue(ks_ldap.is_dn_equal(dn, dn)) |
481 |
+ |
482 |
+ def test_dn_diff_length(self): |
483 |
+ # is_dn_equal returns False if the DNs don't have the same number of |
484 |
+ # RDNs |
485 |
+ dn1 = 'cn=Babs Jansen,ou=OpenStack' |
486 |
+ dn2 = 'cn=Babs Jansen,ou=OpenStack,dc=example.com' |
487 |
+ self.assertFalse(ks_ldap.is_dn_equal(dn1, dn2)) |
488 |
+ |
489 |
+ def test_dn_equal_rdns(self): |
490 |
+ # is_dn_equal returns True if the DNs have the same number of RDNs |
491 |
+ # and each RDN is the same. |
492 |
+ dn1 = 'cn=Babs Jansen,ou=OpenStack+cn=OpenSource' |
493 |
+ dn2 = 'CN=Babs Jansen,cn=OpenSource+ou=OpenStack' |
494 |
+ self.assertTrue(ks_ldap.is_dn_equal(dn1, dn2)) |
495 |
+ |
496 |
+ def test_dn_parsed_dns(self): |
497 |
+ # is_dn_equal can also accept parsed DNs. |
498 |
+ dn_str1 = ldap.dn.str2dn('cn=Babs Jansen,ou=OpenStack+cn=OpenSource') |
499 |
+ dn_str2 = ldap.dn.str2dn('CN=Babs Jansen,cn=OpenSource+ou=OpenStack') |
500 |
+ self.assertTrue(ks_ldap.is_dn_equal(dn_str1, dn_str2)) |
501 |
+ |
502 |
+ def test_startswith_under_child(self): |
503 |
+ # dn_startswith returns True if descendant_dn is a child of dn. |
504 |
+ child = 'cn=Babs Jansen,ou=OpenStack' |
505 |
+ parent = 'ou=OpenStack' |
506 |
+ self.assertTrue(ks_ldap.dn_startswith(child, parent)) |
507 |
+ |
508 |
+ def test_startswith_parent(self): |
509 |
+ # dn_startswith returns False if descendant_dn is a parent of dn. |
510 |
+ child = 'cn=Babs Jansen,ou=OpenStack' |
511 |
+ parent = 'ou=OpenStack' |
512 |
+ self.assertFalse(ks_ldap.dn_startswith(parent, child)) |
513 |
+ |
514 |
+ def test_startswith_same(self): |
515 |
+ # dn_startswith returns False if DNs are the same. |
516 |
+ dn = 'cn=Babs Jansen,ou=OpenStack' |
517 |
+ self.assertFalse(ks_ldap.dn_startswith(dn, dn)) |
518 |
+ |
519 |
+ def test_startswith_not_parent(self): |
520 |
+ # dn_startswith returns False if descendant_dn is not under the dn |
521 |
+ child = 'cn=Babs Jansen,ou=OpenStack' |
522 |
+ parent = 'dc=example.com' |
523 |
+ self.assertFalse(ks_ldap.dn_startswith(child, parent)) |
524 |
+ |
525 |
+ def test_startswith_descendant(self): |
526 |
+ # dn_startswith returns True if descendant_dn is a descendant of dn. |
527 |
+ descendant = 'cn=Babs Jansen,ou=Keystone,ou=OpenStack,dc=example.com' |
528 |
+ dn = 'ou=OpenStack,dc=example.com' |
529 |
+ self.assertTrue(ks_ldap.dn_startswith(descendant, dn)) |
530 |
+ |
531 |
+ def test_startswith_parsed_dns(self): |
532 |
+ # dn_startswith also accepts parsed DNs. |
533 |
+ descendant = ldap.dn.str2dn('cn=Babs Jansen,ou=OpenStack') |
534 |
+ dn = ldap.dn.str2dn('ou=OpenStack') |
535 |
+ self.assertTrue(ks_ldap.dn_startswith(descendant, dn)) |
536 |
-- |
537 |
1.9.3 |