PEP8: fix E303: too many blank lines (2)
[nivanova/samba-autobuild/.git] / source4 / dsdb / samdb / ldb_modules / tests / possibleinferiors.py
1 #!/usr/bin/env python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Andrew Tridgell 2009
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 from __future__ import print_function
21 """Tests the possibleInferiors generation in the schema_fsmo ldb module"""
22
23 import optparse
24 import sys
25
26
27 # Find right directory when running from source tree
28 sys.path.insert(0, "bin/python")
29
30 from samba import getopt as options, Ldb
31 import ldb
32
33 parser = optparse.OptionParser("possibleinferiors.py <URL> [<CLASS>]")
34 sambaopts = options.SambaOptions(parser)
35 parser.add_option_group(sambaopts)
36 credopts = options.CredentialsOptions(parser)
37 parser.add_option_group(credopts)
38 parser.add_option_group(options.VersionOptions(parser))
39 parser.add_option("--wspp", action="store_true")
40
41 opts, args = parser.parse_args()
42
43 if len(args) < 1:
44     parser.print_usage()
45     sys.exit(1)
46
47 url = args[0]
48 if (len(args) > 1):
49     objectclass = args[1]
50 else:
51     objectclass = None
52
53
54 def uniq_list(alist):
55     """return a unique list"""
56     set = {}
57     return [set.setdefault(e, e) for e in alist if e not in set]
58
59
60 lp_ctx = sambaopts.get_loadparm()
61
62 creds = credopts.get_credentials(lp_ctx)
63
64 ldb_options = []
65 # use 'paged_search' module when connecting remotely
66 if url.lower().startswith("ldap://"):
67     ldb_options = ["modules:paged_searches"]
68
69 db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options)
70
71 # get the rootDSE
72 res = db.search(base="", expression="",
73                 scope=ldb.SCOPE_BASE,
74                 attrs=["schemaNamingContext"])
75 rootDse = res[0]
76
77 schema_base = rootDse["schemaNamingContext"][0]
78
79
80 def possible_inferiors_search(db, oc):
81     """return the possible inferiors via a search for the possibleInferiors attribute"""
82     res = db.search(base=schema_base,
83                     expression=("ldapDisplayName=%s" % oc),
84                     attrs=["possibleInferiors"])
85
86     poss = []
87     if len(res) == 0 or res[0].get("possibleInferiors") is None:
88         return poss
89     for item in res[0]["possibleInferiors"]:
90         poss.append(str(item))
91     poss = uniq_list(poss)
92     poss.sort()
93     return poss
94
95
96 # see [MS-ADTS] section 3.1.1.4.5.21
97 # and section 3.1.1.4.2 for this algorithm
98
99 # !systemOnly=TRUE
100 # !objectClassCategory=2
101 # !objectClassCategory=3
102
103 def supclasses(classinfo, oc):
104     list = []
105     if oc == "top":
106         return list
107     if classinfo[oc].get("SUPCLASSES") is not None:
108         return classinfo[oc]["SUPCLASSES"]
109     res = classinfo[oc]["subClassOf"]
110     for r in res:
111         list.append(r)
112         list.extend(supclasses(classinfo, r))
113     classinfo[oc]["SUPCLASSES"] = list
114     return list
115
116
117 def auxclasses(classinfo, oclist):
118     list = []
119     if oclist == []:
120         return list
121     for oc in oclist:
122         if classinfo[oc].get("AUXCLASSES") is not None:
123             list.extend(classinfo[oc]["AUXCLASSES"])
124         else:
125             list2 = []
126             list2.extend(classinfo[oc]["systemAuxiliaryClass"])
127             list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
128             list2.extend(classinfo[oc]["auxiliaryClass"])
129             list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"]))
130             list2.extend(auxclasses(classinfo, supclasses(classinfo, oc)))
131             classinfo[oc]["AUXCLASSES"] = list2
132             list.extend(list2)
133     return list
134
135
136 def subclasses(classinfo, oclist):
137     list = []
138     for oc in oclist:
139         list.extend(classinfo[oc]["SUBCLASSES"])
140     return list
141
142
143 def posssuperiors(classinfo, oclist):
144     list = []
145     for oc in oclist:
146         if classinfo[oc].get("POSSSUPERIORS") is not None:
147             list.extend(classinfo[oc]["POSSSUPERIORS"])
148         else:
149             list2 = []
150             list2.extend(classinfo[oc]["systemPossSuperiors"])
151             list2.extend(classinfo[oc]["possSuperiors"])
152             list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc)))
153             if opts.wspp:
154                 # the WSPP docs suggest we should do this:
155                 list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc])))
156             else:
157                 # but testing against w2k3 and w2k8 shows that we need to do this instead
158                 list2.extend(subclasses(classinfo, list2))
159             classinfo[oc]["POSSSUPERIORS"] = list2
160             list.extend(list2)
161     return list
162
163
164 def pull_classinfo(db):
165     """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
166     classinfo = {}
167     res = db.search(base=schema_base,
168                     expression="objectclass=classSchema",
169                     attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
170                            "possSuperiors", "systemPossSuperiors",
171                            "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
172     for r in res:
173         name = str(r["ldapDisplayName"][0])
174         classinfo[name] = {}
175         if str(r["systemOnly"]) == "TRUE":
176             classinfo[name]["systemOnly"] = True
177         else:
178             classinfo[name]["systemOnly"] = False
179         if r.get("objectClassCategory"):
180             classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
181         else:
182             classinfo[name]["objectClassCategory"] = 0
183         for a in ["possSuperiors", "systemPossSuperiors",
184                    "auxiliaryClass", "systemAuxiliaryClass",
185                    "subClassOf"]:
186             classinfo[name][a] = []
187             if r.get(a):
188                 for i in r[a]:
189                     classinfo[name][a].append(str(i))
190
191     # build a list of subclasses for each class
192     def subclasses_recurse(subclasses, oc):
193         list = subclasses[oc]
194         for c in list:
195             list.extend(subclasses_recurse(subclasses, c))
196         return list
197
198     subclasses = {}
199     for oc in classinfo:
200         subclasses[oc] = []
201     for oc in classinfo:
202         for c in classinfo[oc]["subClassOf"]:
203             if not c == oc:
204                 subclasses[c].append(oc)
205     for oc in classinfo:
206         classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
207
208     return classinfo
209
210
211 def is_in_list(list, c):
212     for a in list:
213         if c == a:
214             return True
215     return False
216
217
218 def possible_inferiors_constructed(db, classinfo, c):
219     list = []
220     for oc in classinfo:
221         superiors = posssuperiors(classinfo, [oc])
222         if (is_in_list(superiors, c) and
223             classinfo[oc]["systemOnly"] == False and
224             classinfo[oc]["objectClassCategory"] != 2 and
225             classinfo[oc]["objectClassCategory"] != 3):
226             list.append(oc)
227     list = uniq_list(list)
228     list.sort()
229     return list
230
231
232 def test_class(db, classinfo, oc):
233     """test to see if one objectclass returns the correct possibleInferiors"""
234     print("test: objectClass.%s" % oc)
235     poss1 = possible_inferiors_search(db, oc)
236     poss2 = possible_inferiors_constructed(db, classinfo, oc)
237     if poss1 != poss2:
238         print("failure: objectClass.%s [" % oc)
239         print("Returned incorrect list for objectclass %s" % oc)
240         print("search:      %s" % poss1)
241         print("constructed: %s" % poss2)
242         for i in range(0, min(len(poss1), len(poss2))):
243             print("%30s %30s" % (poss1[i], poss2[i]))
244         print("]")
245         sys.exit(1)
246     else:
247         print("success: objectClass.%s" % oc)
248
249
250 def get_object_classes(db):
251     """return a list of all object classes"""
252     list = []
253     for item in classinfo:
254         list.append(item)
255     return list
256
257 classinfo = pull_classinfo(db)
258
259 if objectclass is None:
260     for oc in get_object_classes(db):
261         test_class(db, classinfo, oc)
262 else:
263     test_class(db, classinfo, objectclass)
264
265 print("Lists match OK")