comparison test/test_templating.py @ 5201:a9ace22e0a2f

issue 2550690 - Adding anti-csrf measures to roundup following https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet and https://seclab.stanford.edu/websec/csrf/csrf.pdf Basically implement Synchronizer (CSRF) Tokens per form on a page. Single use (destroyed once used). Random input data for the token includes: system random implementation in python using /dev/urandom (fallback to random based on timestamp as the seed. Not as good, but should be ok for the short lifetime of the token??) the id (in cpython it's the memory address) of the object requesting a token. In theory this depends on memory layout, the history of the process (how many previous objects have been allocated from the heap etc.) I claim without any proof that for long running processes this is another source of randomness. For short running processes with little activity it could be guessed. last the floating point time.time() value is added. This may only have 1 second resolution so may be guessable. Hopefully for a short lived (2 week by default) token this is sufficient. Also in the current implementation the user is notified when validation fails and is told why. This allows the roundup admin to find the log entry (at error level) and try to resolve the issue. In the future user notification may change but for now this is probably best.
author John Rouillard <rouilj@ieee.org>
date Sat, 18 Mar 2017 16:59:01 -0400
parents 89b1870b1bc9
children f4b6a2a3e605
comparison
equal deleted inserted replaced
5200:16a8a3f0772c 5201:a9ace22e0a2f
5 from test_actions import MockNull, true 5 from test_actions import MockNull, true
6 6
7 class MockDatabase(MockNull): 7 class MockDatabase(MockNull):
8 def getclass(self, name): 8 def getclass(self, name):
9 return self.classes[name] 9 return self.classes[name]
10
11 # setup for csrf testing of otks database api
12 storage = {}
13 def set(self, key, **props):
14 MockDatabase.storage[key] = {}
15 MockDatabase.storage[key].update(props)
16
17 def get(self, key, field, default=None):
18 if key not in MockDatabase.storage:
19 return default
20 return MockDatabase.storage[key][field]
21
22 def exists(self,key):
23 return key in MockDatabase.storage
24
25 def getOTKManager(self):
26 return MockDatabase()
10 27
11 class TemplatingTestCase(unittest.TestCase): 28 class TemplatingTestCase(unittest.TestCase):
12 def setUp(self): 29 def setUp(self):
13 self.form = FieldStorage() 30 self.form = FieldStorage()
14 self.client = MockNull() 31 self.client = MockNull()
15 self.client.db = db = MockDatabase() 32 self.client.db = db = MockDatabase()
16 db.security.hasPermission = lambda *args, **kw: True 33 db.security.hasPermission = lambda *args, **kw: True
17 self.client.form = self.form 34 self.client.form = self.form
35
36 # add client props for testing anti_csrf_nonce
37 self.client.session_api = MockNull(_sid="1234567890")
38 self.client.db.getuid = lambda : 10
39 self.client.db.config = {'WEB_CSRF_TOKEN_LIFETIME': 10 }
18 40
19 class HTMLDatabaseTestCase(TemplatingTestCase): 41 class HTMLDatabaseTestCase(TemplatingTestCase):
20 def test_HTMLDatabase___getitem__(self): 42 def test_HTMLDatabase___getitem__(self):
21 db = HTMLDatabase(self.client) 43 db = HTMLDatabase(self.client)
22 self.assert_(isinstance(db['issue'], HTMLClass)) 44 self.assert_(isinstance(db['issue'], HTMLClass))
101 ( issue = MockNull(getprops = lambda : dict(nosy = nosy)) 123 ( issue = MockNull(getprops = lambda : dict(nosy = nosy))
102 , user = MockNull(get = lambda id, name : id, lookup = lookup) 124 , user = MockNull(get = lambda id, name : id, lookup = lookup)
103 ) 125 )
104 cls = HTMLClass(self.client, "issue") 126 cls = HTMLClass(self.client, "issue")
105 cls["nosy"] 127 cls["nosy"]
128
129 def test_anti_csrf_nonce(self):
130 '''call the csrf creation function and do basic length test
131
132 Store the data in a mock db with the same api as the otk
133 db. Make sure nonce is 64 chars long. Lookup the nonce in
134 db and retrieve data. Verify that the nonce lifetime is
135 correct (within 1 second of 1 week - lifetime), the uid is
136 correct (1), the dummy sid is correct.
137
138 Consider three cases:
139 * create nonce via module function setting lifetime
140 * create nonce via TemplatingUtils method setting lifetime
141 * create nonce via module function with default lifetime
142
143 '''
144
145 # the value below is number of seconds in a week.
146 week_seconds = 648000
147 for test in [ 'module', 'template', 'default_time' ]:
148 if test == 'module':
149 # test the module function
150 nonce1 = anti_csrf_nonce(self, self.client, lifetime=1)
151 # lifetime * 60 is the offset
152 greater_than = week_seconds - 1 * 60
153 elif test == 'template':
154 # call the function through the TemplatingUtils class
155 cls = TemplatingUtils(self.client)
156 nonce1 = cls.anti_csrf_nonce(lifetime=5)
157 greater_than = week_seconds - 5 * 60
158 elif test == 'default_time':
159 # use the module function but with no lifetime
160 nonce1 = anti_csrf_nonce(self, self.client)
161 # see above for web nonce lifetime.
162 greater_than = week_seconds - 10 * 60
163
164 self.assertEqual(len(nonce1), 64)
165 otks=self.client.db.getOTKManager()
166
167 uid = otks.get(nonce1, 'uid', default=None)
168 sid = otks.get(nonce1, 'sid', default=None)
169 timestamp = otks.get(nonce1, '__timestamp', default=None)
170
171 self.assertEqual(uid, 10)
172 self.assertEqual(sid, self.client.session_api._sid)
173
174 ts = time.time()
175
176 # lower bound of the difference is above. Upper bound
177 # of difference is run time between time.time() in
178 # the call to anti_csrf_nonce and the time.time() call
179 # that assigns ts above. I declare that difference
180 # to be less than 1 second for this to pass.
181 self.assertEqual(True,
182 greater_than < ts - timestamp < (greater_than + 1) )
183
184 print "completed", test
106 185
107 def test_string_url_quote(self): 186 def test_string_url_quote(self):
108 ''' test that urlquote quotes the string ''' 187 ''' test that urlquote quotes the string '''
109 p = StringHTMLProperty(self.client, 'test', '1', None, 'test', 'test string< foo@bar') 188 p = StringHTMLProperty(self.client, 'test', '1', None, 'test', 'test string< foo@bar')
110 self.assertEqual(p.url_quote(), 'test%20string%3C%20foo%40bar') 189 self.assertEqual(p.url_quote(), 'test%20string%3C%20foo%40bar')
360 def __getitem__(self, index): 439 def __getitem__(self, index):
361 def propchanged(self, property): 440 def propchanged(self, property):
362 def previous(self): 441 def previous(self):
363 def next(self): 442 def next(self):
364 443
365 class TemplatingUtils: 444 #class TemplatingUtils:
366 def __init__(self, client): 445 # def __init__(self, client):
367 def Batch(self, sequence, size, start, end=0, orphan=0, overlap=0): 446 # def Batch(self, sequence, size, start, end=0, orphan=0, overlap=0):
368 447
369 class NoTemplate(Exception): 448 class NoTemplate(Exception):
370 class Unauthorised(Exception): 449 class Unauthorised(Exception):
371 def __init__(self, action, klass): 450 def __init__(self, action, klass):
372 def __str__(self): 451 def __str__(self):

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