Merge branch 'master' of ctdb into 'master' of samba
[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 """Tests the possibleInferiors generation in the schema_fsmo ldb module"""
21
22 import optparse
23 import sys
24
25
26 # Find right directory when running from source tree
27 sys.path.insert(0, "bin/python")
28
29 from samba import getopt as options, Ldb
30 import ldb
31
32 parser = optparse.OptionParser("possibleinferiors.py <URL> [<CLASS>]")
33 sambaopts = options.SambaOptions(parser)
34 parser.add_option_group(sambaopts)
35 credopts = options.CredentialsOptions(parser)
36 parser.add_option_group(credopts)
37 parser.add_option_group(options.VersionOptions(parser))
38 parser.add_option("--wspp", action="store_true")
39
40 opts, args = parser.parse_args()
41
42 if len(args) < 1:
43     parser.print_usage()
44     sys.exit(1)
45
46 url = args[0]
47 if (len(args) > 1):
48     objectclass = args[1]
49 else:
50     objectclass = None
51
52 def uniq_list(alist):
53     """return a unique list"""
54     set = {}
55     return [set.setdefault(e,e) for e in alist if e not in set]
56
57
58 lp_ctx = sambaopts.get_loadparm()
59
60 creds = credopts.get_credentials(lp_ctx)
61
62 ldb_options = []
63 # use 'paged_search' module when connecting remotely
64 if url.lower().startswith("ldap://"):
65     ldb_options = ["modules:paged_searches"]
66
67 db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options)
68
69 # get the rootDSE
70 res = db.search(base="", expression="",
71                 scope=ldb.SCOPE_BASE,
72                 attrs=["schemaNamingContext"])
73 rootDse = res[0]
74
75 schema_base = rootDse["schemaNamingContext"][0]
76
77 def possible_inferiors_search(db, oc):
78     """return the possible inferiors via a search for the possibleInferiors attribute"""
79     res = db.search(base=schema_base,
80                     expression=("ldapDisplayName=%s" % oc),
81                     attrs=["possibleInferiors"])
82
83     poss=[]
84     if len(res) == 0 or res[0].get("possibleInferiors") is None:
85         return poss
86     for item in res[0]["possibleInferiors"]:
87         poss.append(str(item))
88     poss = uniq_list(poss)
89     poss.sort()
90     return poss
91
92
93
94 # see [MS-ADTS] section 3.1.1.4.5.21
95 # and section 3.1.1.4.2 for this algorithm
96
97 # !systemOnly=TRUE
98 # !objectClassCategory=2
99 # !objectClassCategory=3
100
101 def supclasses(classinfo, oc):
102     list = []
103     if oc == "top":
104         return list
105     if classinfo[oc].get("SUPCLASSES") is not None:
106         return classinfo[oc]["SUPCLASSES"]
107     res = classinfo[oc]["subClassOf"]
108     for r in res:
109         list.append(r)
110         list.extend(supclasses(classinfo,r))
111     classinfo[oc]["SUPCLASSES"] = list
112     return list
113
114 def auxclasses(classinfo, oclist):
115     list = []
116     if oclist == []:
117         return list
118     for oc in oclist:
119         if classinfo[oc].get("AUXCLASSES") is not None:
120             list.extend(classinfo[oc]["AUXCLASSES"])
121         else:
122             list2 = []
123             list2.extend(classinfo[oc]["systemAuxiliaryClass"])
124             list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
125             list2.extend(classinfo[oc]["auxiliaryClass"])
126             list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"]))
127             list2.extend(auxclasses(classinfo, supclasses(classinfo, oc)))
128             classinfo[oc]["AUXCLASSES"] = list2
129             list.extend(list2)
130     return list
131
132 def subclasses(classinfo, oclist):
133     list = []
134     for oc in oclist:
135         list.extend(classinfo[oc]["SUBCLASSES"])
136     return list
137
138 def posssuperiors(classinfo, oclist):
139     list = []
140     for oc in oclist:
141         if classinfo[oc].get("POSSSUPERIORS") is not None:
142             list.extend(classinfo[oc]["POSSSUPERIORS"])
143         else:
144             list2 = []
145             list2.extend(classinfo[oc]["systemPossSuperiors"])
146             list2.extend(classinfo[oc]["possSuperiors"])
147             list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc)))
148             if opts.wspp:
149                 # the WSPP docs suggest we should do this:
150                 list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc])))
151             else:
152                 # but testing against w2k3 and w2k8 shows that we need to do this instead
153                 list2.extend(subclasses(classinfo, list2))
154             classinfo[oc]["POSSSUPERIORS"] = list2
155             list.extend(list2)
156     return list
157
158 def pull_classinfo(db):
159     """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
160     classinfo = {}
161     res = db.search(base=schema_base,
162                     expression="objectclass=classSchema",
163                     attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
164                            "possSuperiors", "systemPossSuperiors",
165                            "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
166     for r in res:
167         name = str(r["ldapDisplayName"][0])
168         classinfo[name] = {}
169         if str(r["systemOnly"]) == "TRUE":
170             classinfo[name]["systemOnly"] = True
171         else:
172             classinfo[name]["systemOnly"] = False
173         if r.get("objectClassCategory"):
174             classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
175         else:
176             classinfo[name]["objectClassCategory"] = 0
177         for a in [ "possSuperiors", "systemPossSuperiors",
178                    "auxiliaryClass", "systemAuxiliaryClass",
179                    "subClassOf" ]:
180             classinfo[name][a] = []
181             if r.get(a):
182                 for i in r[a]:
183                     classinfo[name][a].append(str(i))
184
185     # build a list of subclasses for each class
186     def subclasses_recurse(subclasses, oc):
187         list = subclasses[oc]
188         for c in list:
189             list.extend(subclasses_recurse(subclasses, c))
190         return list
191
192     subclasses = {}
193     for oc in classinfo:
194         subclasses[oc] = []
195     for oc in classinfo:
196         for c in classinfo[oc]["subClassOf"]:
197             if not c == oc:
198                 subclasses[c].append(oc)
199     for oc in classinfo:
200         classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
201
202     return classinfo
203
204 def is_in_list(list, c):
205     for a in list:
206         if c == a:
207             return True
208     return False
209
210 def possible_inferiors_constructed(db, classinfo, c):
211     list = []
212     for oc in classinfo:
213         superiors = posssuperiors(classinfo, [oc])
214         if (is_in_list(superiors, c) and
215             classinfo[oc]["systemOnly"] == False and
216             classinfo[oc]["objectClassCategory"] != 2 and
217             classinfo[oc]["objectClassCategory"] != 3):
218             list.append(oc)
219     list = uniq_list(list)
220     list.sort()
221     return list
222
223 def test_class(db, classinfo, oc):
224     """test to see if one objectclass returns the correct possibleInferiors"""
225     print "test: objectClass.%s" % oc
226     poss1 = possible_inferiors_search(db, oc)
227     poss2 = possible_inferiors_constructed(db, classinfo, oc)
228     if poss1 != poss2:
229         print "failure: objectClass.%s [" % oc
230         print "Returned incorrect list for objectclass %s" % oc
231         print "search:      %s" % poss1
232         print "constructed: %s" % poss2
233         for i in range(0,min(len(poss1),len(poss2))):
234             print "%30s %30s" % (poss1[i], poss2[i])
235         print "]"
236         sys.exit(1)
237     else:
238         print "success: objectClass.%s" % oc
239
240 def get_object_classes(db):
241     """return a list of all object classes"""
242     list=[]
243     for item in classinfo:
244         list.append(item)
245     return list
246
247 classinfo = pull_classinfo(db)
248
249 if objectclass is None:
250     for oc in get_object_classes(db):
251         test_class(db,classinfo,oc)
252 else:
253     test_class(db,classinfo,objectclass)
254
255 print "Lists match OK"