comparison test/test_security.py @ 7224:01c1f357363f

flake8 fixes Test some unused variables, formatting fixes.
author John Rouillard <rouilj@ieee.org>
date Sun, 12 Mar 2023 22:15:44 -0400
parents 19db61be18e0
children 5b1b876054ef
comparison
equal deleted inserted replaced
7223:19db61be18e0 7224:01c1f357363f
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 # SOFTWARE. 19 # SOFTWARE.
20 20
21 from __future__ import print_function 21 from __future__ import print_function
22 import os, unittest, shutil 22 import os
23 import shutil
24 import unittest
23 25
24 from roundup import backends 26 from roundup import backends
25 import roundup.password 27 import roundup.password
26 from .db_test_base import setupSchema, MyTestCase, config 28 from .db_test_base import setupSchema, MyTestCase, config
27 29
39 def testInterfaceSecurity(self): 41 def testInterfaceSecurity(self):
40 ' test that the CGI and mailgw have initialised security OK ' 42 ' test that the CGI and mailgw have initialised security OK '
41 # TODO: some asserts 43 # TODO: some asserts
42 44
43 def testInitialiseSecurity(self): 45 def testInitialiseSecurity(self):
44 ei = self.db.security.addPermission(name="Edit", klass="issue", 46 ei = self.db.security.addPermission(
45 description="User is allowed to edit issues") 47 name="Edit", klass="issue",
48 description="User is allowed to edit issues")
46 self.db.security.addPermissionToRole('User', ei) 49 self.db.security.addPermissionToRole('User', ei)
47 ai = self.db.security.addPermission(name="View", klass="issue", 50 ai = self.db.security.addPermission(
48 description="User is allowed to access issues") 51 name="View", klass="issue",
52 description="User is allowed to access issues")
49 self.db.security.addPermissionToRole('User', ai) 53 self.db.security.addPermissionToRole('User', ai)
50 54
51 def testAdmin(self): 55 def testAdmin(self):
52 ei = self.db.security.addPermission(name="Edit", klass="issue", 56 ei = self.db.security.addPermission(
53 description="User is allowed to edit issues") 57 name="Edit", klass="issue",
58 description="User is allowed to edit issues")
54 self.db.security.addPermissionToRole('User', ei) 59 self.db.security.addPermissionToRole('User', ei)
55 ei = self.db.security.addPermission(name="Edit", klass=None, 60 ei = self.db.security.addPermission(
56 description="User is allowed to edit issues") 61 name="Edit", klass=None,
62 description="User is allowed to edit issues")
57 self.db.security.addPermissionToRole('Admin', ei) 63 self.db.security.addPermissionToRole('Admin', ei)
58 64
59 u1 = self.db.user.create(username='one', roles='Admin') 65 u1 = self.db.user.create(username='one', roles='Admin')
60 u2 = self.db.user.create(username='two', roles='User') 66 u2 = self.db.user.create(username='two', roles='User')
61 67
62 self.assertTrue(self.db.security.hasPermission('Edit', u1, None)) 68 self.assertTrue(self.db.security.hasPermission('Edit', u1, None))
63 self.assertTrue(not self.db.security.hasPermission('Edit', u2, None)) 69 self.assertTrue(not self.db.security.hasPermission('Edit', u2, None))
64
65 70
66 def testGetPermission(self): 71 def testGetPermission(self):
67 self.db.security.getPermission('Edit') 72 self.db.security.getPermission('Edit')
68 self.db.security.getPermission('View') 73 self.db.security.getPermission('View')
69 self.assertRaises(ValueError, self.db.security.getPermission, 'x') 74 self.assertRaises(ValueError, self.db.security.getPermission, 'x')
70 self.assertRaises(ValueError, self.db.security.getPermission, 'Edit', 75 self.assertRaises(ValueError, self.db.security.getPermission, 'Edit',
71 'fubar') 76 'fubar')
72 77
73 add = self.db.security.addPermission 78 add = self.db.security.addPermission
74 get = self.db.security.getPermission 79 get = self.db.security.getPermission
75 80
76 # class 81 # class
81 86
82 # property 87 # property
83 epi1 = add(name="Edit", klass="issue", properties=['title']) 88 epi1 = add(name="Edit", klass="issue", properties=['title'])
84 self.assertEqual(get('Edit', 'issue', properties=['title']), epi1) 89 self.assertEqual(get('Edit', 'issue', properties=['title']), epi1)
85 epi2 = add(name="Edit", klass="issue", properties=['title'], 90 epi2 = add(name="Edit", klass="issue", properties=['title'],
86 props_only=True) 91 props_only=True)
87 self.assertEqual(get('Edit', 'issue', properties=['title'], props_only=False), epi1) 92 self.assertEqual(get('Edit', 'issue', properties=['title'],
88 self.assertEqual(get('Edit', 'issue', properties=['title'], props_only=True), epi2) 93 props_only=False), epi1)
94 self.assertEqual(get('Edit', 'issue', properties=['title'],
95 props_only=True), epi2)
89 self.db.security.set_props_only_default(True) 96 self.db.security.set_props_only_default(True)
90 self.assertEqual(get('Edit', 'issue', properties=['title']), epi2) 97 self.assertEqual(get('Edit', 'issue', properties=['title']), epi2)
91 api1 = add(name="View", klass="issue", properties=['title']) 98 api1 = add(name="View", klass="issue", properties=['title'])
92 self.assertEqual(get('View', 'issue', properties=['title']), api1) 99 self.assertEqual(get('View', 'issue', properties=['title']), api1)
93 self.db.security.set_props_only_default(False) 100 self.db.security.set_props_only_default(False)
94 api2 = add(name="View", klass="issue", properties=['title']) 101 api2 = add(name="View", klass="issue", properties=['title'])
95 self.assertEqual(get('View', 'issue', properties=['title']), api2) 102 self.assertEqual(get('View', 'issue', properties=['title']), api2)
96 self.assertNotEqual(get('View', 'issue', properties=['title']), api1) 103 self.assertNotEqual(get('View', 'issue', properties=['title']), api1)
97 104
98 # check function 105 # check function
99 dummy = lambda: 0 106 dummy = lambda: 0
100 eci = add(name="Edit", klass="issue", check=dummy) 107 eci = add(name="Edit", klass="issue", check=dummy)
101 self.assertEqual(get('Edit', 'issue', check=dummy), eci) 108 self.assertEqual(get('Edit', 'issue', check=dummy), eci)
102 # props_only only makes sense if you are setting props. 109 # props_only only makes sense if you are setting props.
103 # make it a no-op unless properties is set. 110 # make it a no-op unless properties is set.
104 self.assertEqual(get('Edit', 'issue', check=dummy, 111 self.assertEqual(get('Edit', 'issue', check=dummy,
105 props_only=True), eci) 112 props_only=True), eci)
106 aci = add(name="View", klass="issue", check=dummy) 113 aci = add(name="View", klass="issue", check=dummy)
107 self.assertEqual(get('View', 'issue', check=dummy), aci) 114 self.assertEqual(get('View', 'issue', check=dummy), aci)
108 115
109 # all 116 # all
110 epci = add(name="Edit", klass="issue", properties=['title'], 117 epci = add(name="Edit", klass="issue", properties=['title'],
111 check=dummy) 118 check=dummy)
112 119
113 self.db.security.set_props_only_default(False) 120 self.db.security.set_props_only_default(False)
114 # implicit props_only=False 121 # implicit props_only=False
115 self.assertEqual(get('Edit', 'issue', properties=['title'], 122 self.assertEqual(get('Edit', 'issue', properties=['title'],
116 check=dummy), epci) 123 check=dummy), epci)
117 # explicit props_only=False 124 # explicit props_only=False
118 self.assertEqual(get('Edit', 'issue', properties=['title'], 125 self.assertEqual(get('Edit', 'issue', properties=['title'],
119 check=dummy, props_only=False), epci) 126 check=dummy, props_only=False), epci)
120 127
121 # implicit props_only=True 128 # implicit props_only=True
122 self.db.security.set_props_only_default(True) 129 self.db.security.set_props_only_default(True)
123 self.assertRaises(ValueError, get, 'Edit', 'issue', 130 self.assertRaises(ValueError, get, 'Edit', 'issue',
124 properties=['title'], 131 properties=['title'],
125 check=dummy) 132 check=dummy)
126 # explicit props_only=False 133 # explicit props_only=False
127 self.assertRaises(ValueError, get, 'Edit', 'issue', 134 self.assertRaises(ValueError, get, 'Edit', 'issue',
128 properties=['title'], 135 properties=['title'],
129 check=dummy, props_only=True) 136 check=dummy, props_only=True)
130 137
131 apci = add(name="View", klass="issue", properties=['title'], 138 apci = add(name="View", klass="issue", properties=['title'],
132 check=dummy) 139 check=dummy)
133 self.assertEqual(get('View', 'issue', properties=['title'], 140 self.assertEqual(get('View', 'issue', properties=['title'],
134 check=dummy), apci) 141 check=dummy), apci)
135 142
136 # Reset to default. Somehow this setting looks like it 143 # Reset to default. Somehow this setting looks like it
137 # was bleeding through to other tests in test_xmlrpc. 144 # was bleeding through to other tests in test_xmlrpc.
138 # Is the security module being loaded only once for all tests?? 145 # Is the security module being loaded only once for all tests??
139 self.db.security.set_props_only_default(False) 146 self.db.security.set_props_only_default(False)
163 self.assertEqual(has('Test', super, 'test'), 1) 170 self.assertEqual(has('Test', super, 'test'), 1)
164 self.assertEqual(has('Test', none, 'test'), 0) 171 self.assertEqual(has('Test', none, 'test'), 0)
165 172
166 # property 173 # property
167 addRole(name='Role2') 174 addRole(name='Role2')
168 addToRole('Role2', add(name="Test", klass="test", properties=['a','b'])) 175 addToRole('Role2', add(name="Test", klass="test",
176 properties=['a', 'b']))
169 user2 = self.db.user.create(username='user2', roles='Role2') 177 user2 = self.db.user.create(username='user2', roles='Role2')
170 178
171 # check function 179 # check function
172 check_old_style = lambda db, userid, itemid: itemid == '2' 180 check_old_style = lambda db, userid, itemid: itemid == '2'
173 #def check_old_style(db, userid, itemid): 181 # def check_old_style(db, userid, itemid):
174 # print "checking userid, itemid: %r"%((userid,itemid),) 182 # print "checking userid, itemid: %r"%((userid,itemid),)
175 # return(itemid == '2') 183 # return(itemid == '2')
176 184
177 # setup to check function new style. Make sure that 185 # setup to check function new style. Make sure that
178 # other args are passed. 186 # other args are passed.
179 def check(db,userid,itemid, **other): 187 def check(db, userid, itemid, **other):
180 prop = other['property'] 188 prop = other['property']
181 prop = other['classname'] 189 prop = other['classname']
182 prop = other['permission'] 190 prop = other['permission']
183 return (itemid == '1') 191 return (itemid == '1')
184 192
185 # also create a check as a callable of a class 193 # also create a check as a callable of a class
186 # https://issues.roundup-tracker.org/issue2550952 194 # https://issues.roundup-tracker.org/issue2550952
187 class CheckClass(object): 195 class CheckClass(object):
188 def __call__(self, db,userid,itemid, **other): 196 def __call__(self, db, userid, itemid, **other):
189 prop = other['property'] 197 prop = other['property']
190 prop = other['classname'] 198 prop = other['classname']
191 prop = other['permission'] 199 prop = other['permission']
192 return (itemid == '1') 200 return (itemid == '1')
193 201
237 self.assertEqual(has('Test', user7, 'test', itemid='2'), 1) 245 self.assertEqual(has('Test', user7, 'test', itemid='2'), 1)
238 # returns true because class tests ignore the check command 246 # returns true because class tests ignore the check command
239 # if there is no itemid no check command is run 247 # if there is no itemid no check command is run
240 self.assertEqual(has('Test', user7, 'test'), 1) 248 self.assertEqual(has('Test', user7, 'test'), 1)
241 self.assertEqual(has('Test', none, 'test'), 0) 249 self.assertEqual(has('Test', none, 'test'), 0)
242
243 250
244 # *any* access to item 251 # *any* access to item
245 self.assertEqual(has('Test', user1, 'test', itemid='1'), 1) 252 self.assertEqual(has('Test', user1, 'test', itemid='1'), 1)
246 self.assertEqual(has('Test', user2, 'test', itemid='1'), 1) 253 self.assertEqual(has('Test', user2, 'test', itemid='1'), 1)
247 self.assertEqual(has('Test', user3, 'test', itemid='1'), 1) 254 self.assertEqual(has('Test', user3, 'test', itemid='1'), 1)
311 self.assertEqual(has('Test', none, 'test', itemid='2'), 0) 318 self.assertEqual(has('Test', none, 'test', itemid='2'), 0)
312 319
313 # now mix property and check commands 320 # now mix property and check commands
314 # check is old style props_only = false 321 # check is old style props_only = false
315 self.assertEqual(has('Test', user7, 'test', property="c", 322 self.assertEqual(has('Test', user7, 'test', property="c",
316 itemid='2'), 0) 323 itemid='2'), 0)
317 self.assertEqual(has('Test', user7, 'test', property="c", 324 self.assertEqual(has('Test', user7, 'test', property="c",
318 itemid='1'), 0) 325 itemid='1'), 0)
319 326
320 self.assertEqual(has('Test', user7, 'test', property="a", 327 self.assertEqual(has('Test', user7, 'test', property="a",
321 itemid='2'), 1) 328 itemid='2'), 1)
322 self.assertEqual(has('Test', user7, 'test', property="a", 329 self.assertEqual(has('Test', user7, 'test', property="a",
323 itemid='1'), 0) 330 itemid='1'), 0)
324 331
325 # check is new style props_only = false 332 # check is new style props_only = false
326 self.assertEqual(has('Test', user6, 'test', itemid='2', 333 self.assertEqual(has('Test', user6, 'test', itemid='2',
327 property='c'), 0) 334 property='c'), 0)
328 self.assertEqual(has('Test', user6, 'test', itemid='1', 335 self.assertEqual(has('Test', user6, 'test', itemid='1',
329 property='c'), 0) 336 property='c'), 0)
330 self.assertEqual(has('Test', user6, 'test', itemid='2', 337 self.assertEqual(has('Test', user6, 'test', itemid='2',
331 property='b'), 0) 338 property='b'), 0)
332 self.assertEqual(has('Test', user6, 'test', itemid='1', 339 self.assertEqual(has('Test', user6, 'test', itemid='1',
333 property='b'), 1) 340 property='b'), 1)
334 self.assertEqual(has('Test', user6, 'test', itemid='2', 341 self.assertEqual(has('Test', user6, 'test', itemid='2',
335 property='a'), 0) 342 property='a'), 0)
336 self.assertEqual(has('Test', user6, 'test', itemid='1', 343 self.assertEqual(has('Test', user6, 'test', itemid='1',
337 property='a'), 1) 344 property='a'), 1)
338 345
339 # check is old style props_only = true 346 # check is old style props_only = true
340 self.assertEqual(has('Test', user5, 'test', itemid='2', 347 self.assertEqual(has('Test', user5, 'test', itemid='2',
341 property='b'), 0) 348 property='b'), 0)
342 self.assertEqual(has('Test', user5, 'test', itemid='1', 349 self.assertEqual(has('Test', user5, 'test', itemid='1',
343 property='b'), 0) 350 property='b'), 0)
344 self.assertEqual(has('Test', user5, 'test', itemid='2', 351 self.assertEqual(has('Test', user5, 'test', itemid='2',
345 property='a'), 1) 352 property='a'), 1)
346 self.assertEqual(has('Test', user5, 'test', itemid='1', 353 self.assertEqual(has('Test', user5, 'test', itemid='1',
347 property='a'), 0) 354 property='a'), 0)
348 355
349 # check is new style props_only = true 356 # check is new style props_only = true
350 self.assertEqual(has('Test', user4, 'test', itemid='2', 357 self.assertEqual(has('Test', user4, 'test', itemid='2',
351 property='b'), 0) 358 property='b'), 0)
352 self.assertEqual(has('Test', user4, 'test', itemid='1', 359 self.assertEqual(has('Test', user4, 'test', itemid='1',
353 property='b'), 0) 360 property='b'), 0)
354 self.assertEqual(has('Test', user4, 'test', itemid='2', 361 self.assertEqual(has('Test', user4, 'test', itemid='2',
355 property='a'), 0) 362 property='a'), 0)
356 self.assertEqual(has('Test', user4, 'test', itemid='1', 363 self.assertEqual(has('Test', user4, 'test', itemid='1',
357 property='a'), 1) 364 property='a'), 1)
358 365
359 def testTransitiveSearchPermissions(self): 366 def testTransitiveSearchPermissions(self):
360 add = self.db.security.addPermission 367 add = self.db.security.addPermission
361 has = self.db.security.hasSearchPermission 368 has = self.db.security.hasSearchPermission
362 addRole = self.db.security.addRole 369 addRole = self.db.security.addRole
418 # pretend import of crypt failed 425 # pretend import of crypt failed
419 orig_crypt = roundup.password.crypt 426 orig_crypt = roundup.password.crypt
420 roundup.password.crypt = None 427 roundup.password.crypt = None
421 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 428 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
422 roundup.password.test_missing_crypt() 429 roundup.password.test_missing_crypt()
430 self.assertEqual(ctx.exception.args[0],
431 "Unsupported encryption scheme 'crypt'")
423 roundup.password.crypt = orig_crypt 432 roundup.password.crypt = orig_crypt
424 433
425 def test_pbkdf2_unpack_errors(self): 434 def test_pbkdf2_unpack_errors(self):
426 pbkdf2_unpack = roundup.password.pbkdf2_unpack 435 pbkdf2_unpack = roundup.password.pbkdf2_unpack
427 436
428 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 437 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
429 pbkdf2_unpack("fred$password") 438 pbkdf2_unpack("fred$password")
430 439
431 self.assertEqual(ctx.exception.args[0], 440 self.assertEqual(ctx.exception.args[0],
432 'invalid PBKDF2 hash (wrong number of separators)') 441 'invalid PBKDF2 hash (wrong number of separators)')
433 442
434 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 443 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
435 pbkdf2_unpack("0200000$salt$password") 444 pbkdf2_unpack("0200000$salt$password")
436 445
437 self.assertEqual(ctx.exception.args[0], 446 self.assertEqual(ctx.exception.args[0],
438 'invalid PBKDF2 hash (zero-padded rounds)') 447 'invalid PBKDF2 hash (zero-padded rounds)')
439 448
440 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 449 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
441 pbkdf2_unpack("fred$salt$password") 450 pbkdf2_unpack("fred$salt$password")
442 451
463 def test_pbkdf2_migrate_rounds(self): 472 def test_pbkdf2_migrate_rounds(self):
464 '''Check that migration happens when number of rounds in 473 '''Check that migration happens when number of rounds in
465 config is larger than number of rounds in current password. 474 config is larger than number of rounds in current password.
466 ''' 475 '''
467 476
468
469 p = roundup.password.Password('sekrit', 'PBKDF2', 477 p = roundup.password.Password('sekrit', 'PBKDF2',
470 config=self.db.config) 478 config=self.db.config)
471 479
472 self.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 2000000 480 self.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 2000000
473 481
478 def test_encodePassword_errors(self): 486 def test_encodePassword_errors(self):
479 self.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 999 487 self.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 999
480 488
481 os.environ["PYTEST_USE_CONFIG"] = "True" 489 os.environ["PYTEST_USE_CONFIG"] = "True"
482 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 490 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
483 p = roundup.password.encodePassword('sekrit', 'PBKDF2', 491 roundup.password.encodePassword('sekrit', 'PBKDF2',
484 config=self.db.config) 492 config=self.db.config)
485 493
486 self.assertEqual(ctx.exception.args[0], 494 self.assertEqual(ctx.exception.args[0],
487 'invalid PBKDF2 hash (rounds too low)') 495 'invalid PBKDF2 hash (rounds too low)')
488 496
489 del(os.environ["PYTEST_USE_CONFIG"]) 497 del(os.environ["PYTEST_USE_CONFIG"])
490 498
491 with self.assertRaises(roundup.password.PasswordValueError) as ctx: 499 with self.assertRaises(roundup.password.PasswordValueError) as ctx:
492 p = roundup.password.encodePassword('sekrit', 'fred', 500 roundup.password.encodePassword('sekrit', 'fred',
493 config=self.db.config) 501 config=self.db.config)
494 502
495 self.assertEqual(ctx.exception.args[0], 503 self.assertEqual(ctx.exception.args[0],
496 "Unknown encryption scheme 'fred'") 504 "Unknown encryption scheme 'fred'")
497 505
498 def test_pbkdf2_errors(self): 506 def test_pbkdf2_errors(self):
499 507
500 with self.assertRaises(ValueError) as ctx: 508 with self.assertRaises(ValueError) as ctx:
501 roundup.password.pbkdf2('sekret', b'saltandpepper', 0, 41) 509 roundup.password.pbkdf2('sekret', b'saltandpepper', 0, 41)
502 510
503 self.assertEqual(ctx.exception.args[0], 511 self.assertEqual(ctx.exception.args[0],
504 "key length too large") 512 "key length too large")
508 516
509 self.assertEqual(ctx.exception.args[0], 517 self.assertEqual(ctx.exception.args[0],
510 "rounds must be positive number") 518 "rounds must be positive number")
511 519
512 def test_pbkdf2_sha512_errors(self): 520 def test_pbkdf2_sha512_errors(self):
513 521
514 with self.assertRaises(ValueError) as ctx: 522 with self.assertRaises(ValueError) as ctx:
515 roundup.password.pbkdf2_sha512('sekret', b'saltandpepper', 0, 65) 523 roundup.password.pbkdf2_sha512('sekret', b'saltandpepper', 0, 65)
516 524
517 self.assertEqual(ctx.exception.args[0], 525 self.assertEqual(ctx.exception.args[0],
518 "key length too large") 526 "key length too large")
520 with self.assertRaises(ValueError) as ctx: 528 with self.assertRaises(ValueError) as ctx:
521 roundup.password.pbkdf2_sha512('sekret', b'saltandpepper', 0, 64) 529 roundup.password.pbkdf2_sha512('sekret', b'saltandpepper', 0, 64)
522 530
523 self.assertEqual(ctx.exception.args[0], 531 self.assertEqual(ctx.exception.args[0],
524 "rounds must be positive number") 532 "rounds must be positive number")
525
526 533
527 def test_encodePasswordNoConfig(self): 534 def test_encodePasswordNoConfig(self):
528 # should run cleanly as we are in a test. 535 # should run cleanly as we are in a test.
529 # 536 #
530 p = roundup.password.encodePassword('sekrit', 'PBKDF2') 537 p = roundup.password.encodePassword('sekrit', 'PBKDF2')
538 # verify 1000 rounds being used becaue we are in test mode
539 self.assertTrue(p.startswith("1000$"))
531 540
532 del(os.environ["PYTEST_CURRENT_TEST"]) 541 del(os.environ["PYTEST_CURRENT_TEST"])
533 self.assertNotIn("PYTEST_CURRENT_TEST", os.environ) 542 self.assertNotIn("PYTEST_CURRENT_TEST", os.environ)
534 543
535 with self.assertRaises(roundup.password.ConfigNotSet) as ctx: 544 with self.assertRaises(roundup.password.ConfigNotSet) as ctx:
536 roundup.password.encodePassword('sekrit', 'PBKDF2') 545 roundup.password.encodePassword('sekrit', 'PBKDF2')
537 546
547 self.assertEqual(ctx.exception.args[0],
548 "encodePassword called without config.")
538 # vim: set filetype=python sts=4 sw=4 et si : 549 # vim: set filetype=python sts=4 sw=4 et si :

Roundup Issue Tracker: http://roundup-tracker.org/