d7976793415b7bdeec2d67a0cc510bcb7c7d7063
[nivanova/samba-autobuild/.git] / source4 / dsdb / samdb / ldb_modules / tests / possibleinferiors.py
1 #!/usr/bin/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 import samba
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 def uniq_list(alist):
54     """return a unique list"""
55     set = {}
56     return [set.setdefault(e,e) for e in alist if e not in set]
57
58
59 lp_ctx = sambaopts.get_loadparm()
60
61 creds = credopts.get_credentials(lp_ctx)
62 db = Ldb(url, credentials=creds, lp=lp_ctx, options=["modules:paged_searches"])
63
64 # get the rootDSE
65 res = db.search(base="", expression="",
66                 scope=ldb.SCOPE_BASE,
67                 attrs=["schemaNamingContext"])
68 rootDse = res[0]
69
70 schema_base = rootDse["schemaNamingContext"][0]
71
72 def possible_inferiors_search(db, oc):
73     """return the possible inferiors via a search for the possibleInferiors attribute"""
74     res = db.search(base=schema_base,
75                     expression=("ldapDisplayName=%s" % oc),
76                     attrs=["possibleInferiors"])
77
78     poss=[]
79     if len(res) == 0 or res[0].get("possibleInferiors") is None:
80         return poss
81     for item in res[0]["possibleInferiors"]:
82         poss.append(str(item))
83     poss = uniq_list(poss)
84     poss.sort()
85     return poss;
86
87
88
89 # see [MS-ADTS] section 3.1.1.4.5.21
90 # and section 3.1.1.4.2 for this algorithm
91
92 # !systemOnly=TRUE
93 # !objectClassCategory=2
94 # !objectClassCategory=3
95
96 def SUPCLASSES(classinfo, oc):
97     list = []
98     if oc == "top":
99         return list
100     if classinfo[oc].get("SUPCLASSES") is not None:
101         return classinfo[oc]["SUPCLASSES"]
102     res = classinfo[oc]["subClassOf"];
103     for r in res:
104         list.append(r)
105         list.extend(SUPCLASSES(classinfo,r))
106     classinfo[oc]["SUPCLASSES"] = list
107     return list
108
109 def AUXCLASSES(classinfo, oclist):
110     list = []
111     if oclist == []:
112         return list
113     for oc in oclist:
114         if classinfo[oc].get("AUXCLASSES") is not None:
115             list.extend(classinfo[oc]["AUXCLASSES"])
116         else:
117             list2 = []
118             list2.extend(classinfo[oc]["systemAuxiliaryClass"])
119             list2.extend(AUXCLASSES(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
120             list2.extend(classinfo[oc]["auxiliaryClass"])
121             list2.extend(AUXCLASSES(classinfo, classinfo[oc]["auxiliaryClass"]))
122             list2.extend(AUXCLASSES(classinfo, SUPCLASSES(classinfo, oc)))
123             classinfo[oc]["AUXCLASSES"] = list2
124             list.extend(list2)
125     return list
126
127 def SUBCLASSES(classinfo, oclist):
128     list = []
129     for oc in oclist:
130         list.extend(classinfo[oc]["SUBCLASSES"])
131     return list
132
133 def POSSSUPERIORS(classinfo, oclist):
134     list = []
135     for oc in oclist:
136         if classinfo[oc].get("POSSSUPERIORS") is not None:
137             list.extend(classinfo[oc]["POSSSUPERIORS"])
138         else:
139             list2 = []
140             list2.extend(classinfo[oc]["systemPossSuperiors"])
141             list2.extend(classinfo[oc]["possSuperiors"])
142             list2.extend(POSSSUPERIORS(classinfo, SUPCLASSES(classinfo, oc)))
143             if opts.wspp:
144                 # the WSPP docs suggest we should do this:
145                 list2.extend(POSSSUPERIORS(classinfo, AUXCLASSES(classinfo, [oc])))
146             else:
147                 # but testing against w2k3 and w2k8 shows that we need to do this instead
148                 list2.extend(SUBCLASSES(classinfo, list2))
149             classinfo[oc]["POSSSUPERIORS"] = list2
150             list.extend(list2)
151     return list
152
153 def pull_classinfo(db):
154     """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
155     classinfo = {}
156     res = db.search(base=schema_base,
157                     expression="objectclass=classSchema",
158                     attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
159                            "possSuperiors", "systemPossSuperiors",
160                            "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
161     for r in res:
162         name = str(r["ldapDisplayName"][0])
163         classinfo[name] = {}
164         if str(r["systemOnly"]) == "TRUE":
165             classinfo[name]["systemOnly"] = True
166         else:
167             classinfo[name]["systemOnly"] = False
168         if r.get("objectClassCategory"):
169             classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
170         else:
171             classinfo[name]["objectClassCategory"] = 0
172         for a in [ "possSuperiors", "systemPossSuperiors",
173                    "auxiliaryClass", "systemAuxiliaryClass",
174                    "subClassOf" ]:
175             classinfo[name][a] = []
176             if r.get(a):
177                 for i in r[a]:
178                     classinfo[name][a].append(str(i))
179
180     # build a list of subclasses for each class
181     def subclasses_recurse(subclasses, oc):
182         list = subclasses[oc]
183         for c in list:
184             list.extend(subclasses_recurse(subclasses, c))
185         return list
186
187     subclasses = {}
188     for oc in classinfo:
189         subclasses[oc] = []
190     for oc in classinfo:
191         for c in classinfo[oc]["subClassOf"]:
192             if not c == oc:
193                 subclasses[c].append(oc)
194     for oc in classinfo:
195         classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
196
197     return classinfo
198
199 def is_in_list(list, c):
200     for a in list:
201         if c == a:
202             return True
203     return False
204
205 def possible_inferiors_constructed(db, classinfo, c):
206     list = []
207     for oc in classinfo:
208         superiors = POSSSUPERIORS(classinfo, [oc])
209         if (is_in_list(superiors, c) and
210             classinfo[oc]["systemOnly"] == False and
211             classinfo[oc]["objectClassCategory"] != 2 and
212             classinfo[oc]["objectClassCategory"] != 3):
213             list.append(oc)
214     list = uniq_list(list)
215     list.sort()
216     return list
217
218 def test_class(db, classinfo, oc):
219     """test to see if one objectclass returns the correct possibleInferiors"""
220     print "Testing objectClass %s" % oc
221     poss1 = possible_inferiors_search(db, oc)
222     poss2 = possible_inferiors_constructed(db, classinfo, oc)
223     if poss1 != poss2:
224         print "Returned incorrect list for objectclass %s" % oc
225         print "search:      %s" % poss1
226         print "constructed: %s" % poss2
227         for i in range(0,min(len(poss1),len(poss2))):
228             print "%30s %30s" % (poss1[i], poss2[i])
229         exit(1)
230
231 def get_object_classes(db):
232     """return a list of all object classes"""
233     list=[]
234     for item in classinfo:
235         list.append(item)
236     return list
237
238 classinfo = pull_classinfo(db)
239
240 if objectclass is None:
241     for oc in get_object_classes(db):
242         test_class(db,classinfo,oc)
243 else:
244     test_class(db,classinfo,objectclass)
245
246 print "Lists match OK"