Package ldaptor :: Package test :: Module test_webui
[hide private]
[frames] | no frames]

Source Code for Module ldaptor.test.test_webui

  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   
18 -def getTextContents(node):
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
35 -class MockLDAPConfig(config.LDAPConfig):
37 return {}
38
39 -class SiteMixin:
40 - def setUp(self):
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='], # "foo" 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
141 - def tearDown(self):
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/', # to test login 177 ] 178
179 - def _checkResults(self, data, cookies):
180 # while we're here, make sure the skin system seems to work 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
215 - def test_form_css(self):
216 for u in self.urls: 217 self.checkPage(u, cookies=True)
218
219 - def test_form_css_noCookies(self):
220 for u in self.urls: 221 self.checkPage(u, cookies=False)
222 223
224 -class TestAuthenticatedCSS(TestCSS):
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 # fill form, submit 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
268 -class TestAuthentication(SiteMixin, unittest.TestCase):
269 - def test_ensureBind(self):
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 # fill form, submit 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
295 - def test_edit(self):
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
299 - def test_edit_noCookies(self):
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
303 - def test_move(self):
304 data = self.checkPage('http://localhost/dc=example,dc=com/move', cookies=True) 305 self.assertEquals(data['title'], u'Ldaptor Move Page')
306
307 - def test_move_noCookies(self):
308 data = self.checkPage('http://localhost/dc=example,dc=com/move', cookies=False) 309 self.assertEquals(data['title'], u'Ldaptor Move Page')
310
311 - def test_add(self):
312 data = self.checkPage('http://localhost/dc=example,dc=com/add', cookies=True) 313 self.assertEquals(data['title'], u'Ldaptor Add Page')
314
315 - def test_add_noCookies(self):
316 data = self.checkPage('http://localhost/dc=example,dc=com/add', cookies=False) 317 self.assertEquals(data['title'], u'Ldaptor Add Page')
318
319 - def test_delete(self):
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
323 - def test_delete_noCookies(self):
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
335 - def test_change_password(self):
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
343 -class TestDelete(SiteMixin, unittest.TestCase):
344 - def checkPage(self, url, cookies):
345 data = self.getPage(url, cookies) 346 self.assertEquals(data['title'], 'Login') 347 348 # fill form, submit 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
366 - def test_nonExisting(self):
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
372 - def test_existing(self):
373 # TODO cookies don't work because there's nothing that would carry over their state 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 # fill form, submit 379 forms = data['tree'].getElementsByTagName('form') 380 self.assertEquals(len(forms), 1) 381 form = forms[0] 382 # TODO support multipart/form-data, that's what the form tells us to use 383 ## self.assertEquals(form.getAttribute('enctype', 'application/x-www-form-urlencoded'), 384 ## 'application/x-www-form-urlencoded') 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