1 from twisted.trial import unittest
2
3 import urllib, string
4
5 from twisted.internet import address, protocol, defer
6 from twisted.python import components
7 from twisted.web import microdom
8
9 from nevow import appserver, tags
10 from webut.skin import skin
11
12 from ldaptor import inmemory, interfaces, config
13 from ldaptor.protocols.ldap import ldapserver
14 from ldaptor.apps.webui import main, defskin
15
16 from ldaptor.test import mockweb, util
17
19 s=u''
20 for text in node.childNodes:
21 assert (isinstance(text, microdom.Text)
22 or isinstance(text, microdom.EntityReference))
23 if isinstance(text, microdom.Text):
24 s += text.toxml()
25 elif isinstance(text, microdom.EntityReference):
26 if text.eref.startswith('#x'):
27 n = int(text.eref[len('#x'):], 16)
28 s += unichr(n)
29 else:
30 s += text.toxml()
31 else:
32 raise RuntimeError, 'TODO'
33 return s
34
38
41 db = inmemory.ReadOnlyInMemoryLDAPEntry('')
42 db.addChild('cn=schema',
43 {'objectClass': ['TODO'],
44 'cn': ['schema'],
45 'attributeTypes': [
46 """( 0.9.2342.19200300.100.1.25
47 NAME ( 'dc' 'domainComponent' )
48 DESC 'RFC1274/2247: domain component'
49 EQUALITY caseIgnoreIA5Match
50 SUBSTR caseIgnoreIA5SubstringsMatch
51 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )""",
52 """( 2.5.4.0 NAME 'objectClass'
53 DESC 'RFC2256: object classes of the entity'
54 EQUALITY objectIdentifierMatch
55 SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )""",
56 """( 2.5.4.4 NAME ( 'sn' 'surname' )
57 DESC 'RFC2256: last (family) name(s) for which the entity is known by'
58 SUP name )""",
59 """( 2.5.4.3 NAME ( 'cn' 'commonName' )
60 DESC 'RFC2256: common name(s) for which the entity is known by'
61 SUP name )""",
62 """( 2.5.4.35 NAME 'userPassword'
63 DESC 'RFC2256/2307: password of user'
64 EQUALITY octetStringMatch
65 SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )""",
66 """( 2.5.4.20 NAME 'telephoneNumber'
67 DESC 'RFC2256: Telephone Number'
68 EQUALITY telephoneNumberMatch
69 SUBSTR telephoneNumberSubstringsMatch
70 SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )""",
71 """( 2.5.4.34 NAME 'seeAlso'
72 DESC 'RFC2256: DN of related object'
73 SUP distinguishedName )""",
74 """( 2.5.4.13 NAME 'description'
75 DESC 'RFC2256: descriptive information'
76 EQUALITY caseIgnoreMatch
77 SUBSTR caseIgnoreSubstringsMatch
78 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )""",
79 ],
80 'objectClasses': [
81 """( 2.5.6.0 NAME 'top'
82 DESC 'top of the superclass chain'
83 ABSTRACT
84 MUST objectClass )""",
85 """( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
86 DESC 'RFC2247: domain component object'
87 SUP top AUXILIARY MUST dc )""",
88 """( 2.5.6.6 NAME 'person'
89 DESC 'RFC2256: a person'
90 SUP top
91 STRUCTURAL
92 MUST ( sn $ cn )
93 MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )""",
94 ],
95 })
96 self.com = db.addChild('dc=com', {})
97 self.example = self.com.addChild('dc=example',
98 {'objectClass': ['dcObject'],
99 'dc': ['example'],
100 'subschemaSubentry': ['cn=schema'],
101 })
102 self.foo = self.example.addChild('uid=foo',
103 {'objectClass': ['person'],
104 'uid': ['foo'],
105 'cn': ['Foo Bar'],
106 'sn': ['Bar'],
107 'userPassword': ['{SSHA}1feEJLgP7OB5mUKU/fYJzBoAGlOrze8='],
108 'subschemaSubentry': ['cn=schema'],
109 })
110
111 class LDAPServerFactory(protocol.ServerFactory):
112 protocol = ldapserver.LDAPServer
113 def __init__(self, root):
114 self.root = root
115
116 components.registerAdapter(lambda x: x.root,
117 LDAPServerFactory,
118 interfaces.IConnectedLDAPEntry)
119 serverFactory = LDAPServerFactory(db)
120
121 def _doConnect(factory):
122 factory.doStart()
123 client = factory.buildProtocol(address.IPv4Address('TCP', 'localhost', '389'))
124 server = serverFactory.buildProtocol(address.IPv4Address('TCP', 'localhost', '1024'))
125 util.returnConnected(server, client)
126
127 cfg = MockLDAPConfig(baseDN='dc=example,dc=com',
128 serviceLocationOverrides={'': _doConnect},
129 identityBaseDN='dc=example,dc=com',)
130 class TestSkin(defskin.DefaultSkin):
131 def render_head(self, ctx, data):
132 d = defer.maybeDeferred(super(TestSkin, self).render_head, ctx, data)
133 def cb(stan):
134 return stan[tags.comment['kilroy was here']]
135 d.addCallback(cb)
136 return d
137 self.root = main.getResource(cfg, skinFactory=TestSkin)
138 self.site = appserver.NevowSite(self.root)
139 self.site.startFactory()
140
142 assert isinstance(self.root, skin.Skinner)
143 for name, sess in self.root.resource.sessions.items():
144 sess.expire()
145 self.site.stopFactory()
146
147 - def getPage(self, url, cookies, *a, **kw):
148 parse = kw.pop('parse', True)
149
150 if cookies:
151 getter = mockweb.getPage
152 else:
153 getter = mockweb.getPage_noCookies
154 kw['extraInfo'] = True
155 d = getter(self.site, url, *a, **kw)
156 data = util.pumpingDeferredResult(d)
157
158 if parse:
159 tree = microdom.parseString(data['page'], beExtremelyLenient=True)
160 assert 'tree' not in data
161 data['tree'] = tree
162
163 title = data['tree'].getElementsByTagName('title')[0]
164 assert 'title' not in data
165 data['title'] = getTextContents(title)
166
167 return data
168
169
170 -class TestCSS(SiteMixin, unittest.TestCase):
171 urls = [
172 'http://localhost/',
173 'http://localhost/dc=example,dc=com/',
174 'http://localhost/dc=example,dc=com/search',
175 'http://localhost/dc=example,dc=com/search/',
176 'http://localhost/dc=example,dc=com/edit/',
177 ]
178
180
181 self.assertIn('<!--kilroy was here-->', data['page'])
182 self.assertNotIn('Title Goes Here', data['page'])
183
184 head = data['tree'].getElementsByTagName('head')
185 assert len(head) == 1, \
186 "Expected exactly one <head> element, got %r" % head
187 links = head[0].getElementsByTagName('link')
188 for link in links:
189 if link.getAttribute('rel') == 'stylesheet':
190 href = link.getAttribute('href')
191 u = data['url'].clear().click(href)
192 self.assertEquals(u.scheme, 'http')
193 self.assertEquals(u.netloc, 'localhost')
194 self.assertEquals(u.queryList(), [])
195
196 l = u.pathList()
197 self.failUnless(l[-1].endswith('.css'),
198 "url %s has invalid CSS extension" % data['url'])
199 basename = l[-1][:-len('.css')]
200 self.failUnless(len(basename) >= 1)
201 for c in basename:
202 self.failUnless(c in string.ascii_lowercase, "url %s has invalid character %r in CSS reference %r" % (data['url'], c, l[-1]))
203
204 cssData = self.getPage(u, cookies,
205 followRedirect=False,
206 parse=False)
207 self.assertEquals(cssData['status'], '200',
208 "CSS files must not be without a guard session")
209
210
211 - def checkPage(self, url, cookies):
212 data = self.getPage(url, cookies)
213 self._checkResults(data, cookies)
214
218
222
223
225 urls = [
226 'http://localhost/dc=example,dc=com/edit',
227 'http://localhost/dc=example,dc=com/edit/',
228 'http://localhost/dc=example,dc=com/edit/uid=foo,dc=example,dc=com',
229 'http://localhost/dc=example,dc=com/add',
230 'http://localhost/dc=example,dc=com/add/',
231 'http://localhost/dc=example,dc=com/add/manual/dcObject',
232 'http://localhost/dc=example,dc=com/change_password',
233 'http://localhost/dc=example,dc=com/change_password/',
234 'http://localhost/dc=example,dc=com/change_password/uid=foo,dc=example,dc=com',
235 'http://localhost/dc=example,dc=com/mass_change_password',
236 'http://localhost/dc=example,dc=com/mass_change_password/',
237 'http://localhost/dc=example,dc=com/mass_change_password/(uid=foo)',
238 'http://localhost/dc=example,dc=com/delete',
239 'http://localhost/dc=example,dc=com/delete/',
240 'http://localhost/dc=example,dc=com/delete/uid=foo,dc=example,dc=com',
241 'http://localhost/dc=example,dc=com/move',
242 'http://localhost/dc=example,dc=com/move/',
243 'http://localhost/dc=example,dc=com/move/uid=foo,dc=example,dc=com',
244 ]
245
246 - def checkPage(self, url, cookies):
247 data = self.getPage(url, cookies)
248 self.assertEquals(data['title'], 'Login')
249
250
251 forms = data['tree'].getElementsByTagName('form')
252 self.assertEquals(len(forms), 1)
253 form = forms[0]
254 self.assertEquals(form.getAttribute('enctype', 'application/x-www-form-urlencoded'),
255 'application/x-www-form-urlencoded')
256 data = self.getPage(data['url'].clear().click(form.getAttribute('action')),
257 cookies,
258 method=form.getAttribute('method', 'get').upper(),
259 headers={'Content-Type': 'application/x-www-form-urlencoded'},
260 postdata='&'.join(['%s=%s' % (urllib.quote('username'),
261 urllib.quote('foo')),
262 '%s=%s' % (urllib.quote('password'),
263 urllib.quote('foo')),
264 ]),
265 )
266 self._checkResults(data, cookies)
267
270 self.failUnless(self.foo.bind('foo'))
271
272 - def checkPage(self, url, cookies):
273 data = self.getPage(url, cookies)
274 self.assertEquals(data['title'], 'Login')
275
276
277 forms = data['tree'].getElementsByTagName('form')
278 self.assertEquals(len(forms), 1)
279 form = forms[0]
280 self.assertEquals(form.getAttribute('enctype', 'application/x-www-form-urlencoded'),
281 'application/x-www-form-urlencoded')
282 data = self.getPage(data['url'].clear().click(form.getAttribute('action')),
283 cookies,
284 method=form.getAttribute('method', 'get').upper(),
285 headers={'Content-Type': 'application/x-www-form-urlencoded'},
286 postdata='&'.join(['%s=%s' % (urllib.quote('username'),
287 urllib.quote('foo')),
288 '%s=%s' % (urllib.quote('password'),
289 urllib.quote('foo')),
290 ]),
291 )
292
293 return data
294
296 data = self.checkPage('http://localhost/dc=example,dc=com/edit/dc=example,dc=com', cookies=True)
297 self.assertEquals(data['title'], u'Ldaptor Edit Page')
298
300 data = self.checkPage('http://localhost/dc=example,dc=com/edit/dc=example,dc=com', cookies=False)
301 self.assertEquals(data['title'], u'Ldaptor Edit Page')
302
304 data = self.checkPage('http://localhost/dc=example,dc=com/move', cookies=True)
305 self.assertEquals(data['title'], u'Ldaptor Move Page')
306
308 data = self.checkPage('http://localhost/dc=example,dc=com/move', cookies=False)
309 self.assertEquals(data['title'], u'Ldaptor Move Page')
310
312 data = self.checkPage('http://localhost/dc=example,dc=com/add', cookies=True)
313 self.assertEquals(data['title'], u'Ldaptor Add Page')
314
316 data = self.checkPage('http://localhost/dc=example,dc=com/add', cookies=False)
317 self.assertEquals(data['title'], u'Ldaptor Add Page')
318
320 data = self.checkPage('http://localhost/dc=example,dc=com/delete/dc=example,dc=com', cookies=True)
321 self.assertEquals(data['title'], u'Ldaptor Delete Page')
322
324 data = self.checkPage('http://localhost/dc=example,dc=com/delete/dc=example,dc=com', cookies=False)
325 self.assertEquals(data['title'], u'Ldaptor Delete Page')
326
328 data = self.checkPage('http://localhost/dc=example,dc=com/mass_change_password', cookies=True)
329 self.assertEquals(data['title'], u'Ldaptor Mass Password Change Page')
330
332 data = self.checkPage('http://localhost/dc=example,dc=com/mass_change_password', cookies=False)
333 self.assertEquals(data['title'], u'Ldaptor Mass Password Change Page')
334
336 data = self.checkPage('http://localhost/dc=example,dc=com/change_password', cookies=True)
337 self.assertEquals(data['title'], u'Ldaptor Password Change Page')
338
340 data = self.checkPage('http://localhost/dc=example,dc=com/change_password', cookies=False)
341 self.assertEquals(data['title'], u'Ldaptor Password Change Page')
342
344 - def checkPage(self, url, cookies):
345 data = self.getPage(url, cookies)
346 self.assertEquals(data['title'], 'Login')
347
348
349 forms = data['tree'].getElementsByTagName('form')
350 self.assertEquals(len(forms), 1)
351 form = forms[0]
352 self.assertEquals(form.getAttribute('enctype', 'application/x-www-form-urlencoded'),
353 'application/x-www-form-urlencoded')
354 data = self.getPage(data['url'].clear().click(form.getAttribute('action')),
355 cookies,
356 method=form.getAttribute('method', 'get').upper(),
357 headers={'Content-Type': 'application/x-www-form-urlencoded'},
358 postdata='&'.join(['%s=%s' % (urllib.quote('username'),
359 urllib.quote('foo')),
360 '%s=%s' % (urllib.quote('password'),
361 urllib.quote('foo')),
362 ]),
363 )
364 return data
365
367 data = self.checkPage('http://localhost/dc=example,dc=com/delete/uid=bar,dc=example,dc=com', cookies=True)
368 self.assertEquals(data['title'], u'Ldaptor Delete Page')
369 self.failUnless('An error occurred' in data['page'])
370 self.failUnless('noSuchObject' in data['page'])
371
373
374 data = self.checkPage('http://localhost/dc=example,dc=com/delete/uid=foo,dc=example,dc=com', cookies=False)
375 self.assertEquals(data['title'], u'Ldaptor Delete Page')
376 self.failUnless('<p>Remove <span>uid=foo,dc=example,dc=com</span>?</p>' in data['page'])
377
378
379 forms = data['tree'].getElementsByTagName('form')
380 self.assertEquals(len(forms), 1)
381 form = forms[0]
382
383
384
385 action = data['url'].clear().click(form.getAttribute('action'))
386 data = self.getPage(action,
387 cookies=False,
388 method=form.getAttribute('method', 'get').upper(),
389 headers={'Content-Type': 'application/x-www-form-urlencoded'},
390 )
391
392 self.assertEquals(data['title'], 'Ldaptor Search Page')
393 self.failUnless('Deleted uid=foo,dc=example,dc=com.' in data['page'])
394
395 d = self.example.children()
396 children = util.pumpingDeferredResult(d)
397 self.assertEquals(children, [])
398