#
# Copyright (c) 2003 Richard Jones, rjones@ekit-inc.com
# This module is free software, and you may redistribute it and/or modify
# under the same terms as Python, so long as this copyright message and
# disclaimer are retained in their original form.
#
# This module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
from __future__ import print_function
import unittest, os, shutil, errno, sys, difflib, re, io
import pytest
import copy
from os.path import normpath
from roundup.anypy.cgi_ import cgi
from roundup.cgi import client, actions, exceptions
from roundup.cgi.exceptions import FormError, NotFound, Redirect, NotModified
from roundup.exceptions import UsageError, Reject
from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate
from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce
from roundup.cgi.templating import TemplatingUtils
from roundup.cgi.form_parser import FormParser
from roundup import init, instance, password, hyperdb, date
from roundup.anypy.strings import u2s, b2s, s2b
from roundup.test.tx_Source_detector import init as tx_Source_init
from time import sleep
# For testing very simple rendering
from roundup.cgi.engine_zopetal import RoundupPageTemplate
from roundup.test.mocknull import MockNull
from . import db_test_base
from .db_test_base import FormTestParent, setupTracker, FileUpload
from .cmp_helper import StringFragmentCmpHelper
from .test_postgresql import skip_postgresql
from .test_mysql import skip_mysql
class FileList:
def __init__(self, name, *files):
self.name = name
self.files = files
def items (self):
for f in self.files:
yield (self.name, f)
class testFtsQuery(object):
def testRenderContextFtsQuery(self):
self.db.issue.create(title='i1 is found', status="chatting")
self.client.form=db_test_base.makeForm(
{ "@ok_message": "ok message", "@template": "index",
"@search_text": "found"})
self.client.path = 'issue'
self.client.determine_context()
result = self.client.renderContext()
expected = '">i1 is found'
self.assertIn(expected, result)
self.assertEqual(self.client.response_code, 200)
cm = client.add_message
class MessageTestCase(unittest.TestCase):
# Note: Escaping is now handled on a message-by-message basis at a
# point where we still know what generates a message. In this way we
# can decide when to escape and when not. We test the add_message
# routine here.
# Of course we won't catch errors in judgement when to escape here
# -- but at the time of this change only one message is not escaped.
def testAddMessageOK(self):
self.assertEqual(cm([],'a\nb'), ['a \nb'])
self.assertEqual(cm([],'a\nb\nc\n'), ['a \nb \nc \n'])
def testAddMessageBAD(self):
self.assertEqual(cm([],''),
['<script>x</script>'])
self.assertEqual(cm([],''),
['<iframe>x</iframe>'])
self.assertEqual(cm([],'<>'),
['<<script >>alert(42);5<</script >>'])
self.assertEqual(cm([],'x'),
['<a href="y">x</a>'])
self.assertEqual(cm([],'x'),
['<a href="<y>">x</a>'])
self.assertEqual(cm([],'x'),
['<A HREF="y">x</A>'])
self.assertEqual(cm([],' x '), ['<br>x<br />'])
self.assertEqual(cm([],'x'), ['<i>x</i>'])
self.assertEqual(cm([],'x'), ['<b>x</b>'])
self.assertEqual(cm([],' x '), ['<BR>x<BR />'])
self.assertEqual(cm([],'x'), ['<I>x</I>'])
self.assertEqual(cm([],'x'), ['<B>x</B>'])
def testAddMessageNoEscape(self):
self.assertEqual(cm([],'x',False), ['x'])
self.assertEqual(cm([],'x\nx',False),
['x \nx'])
class testCsvExport(object):
def testCSVExportBase(self):
cl = self._make_client(
{'@columns': 'id,title,status,keyword,assignedto,nosy,creation'},
nodeid=None, userid='1')
cl.classname = 'issue'
demo_id=self.db.user.create(username='demo', address='demo@test.test',
roles='User', realname='demo')
key_id1=self.db.keyword.create(name='keyword1')
key_id2=self.db.keyword.create(name='keyword2')
originalDate = date.Date
dummy=date.Date('2000-06-26.00:34:02.0')
# is a closure the best way to return a static Date object??
def dummyDate(adate=None):
def dummyClosure(adate=None, translator=None):
return dummy
return dummyClosure
date.Date = dummyDate()
self.db.issue.create(title='foo1', status='2', assignedto='4', nosy=['3',demo_id])
self.db.issue.create(title='bar2', status='1', assignedto='3', keyword=[key_id1,key_id2])
self.db.issue.create(title='baz32', status='4')
output = io.BytesIO()
cl.request = MockNull()
cl.request.wfile = output
# call export version that outputs names
actions.ExportCSVAction(cl).handle()
should_be=(s2b('"id","title","status","keyword","assignedto","nosy","creation"\r\n'
'"1","foo1","deferred","","Contrary, Mary","Bork, Chef;Contrary, Mary;demo","2000-06-26 00:34"\r\n'
'"2","bar2","unread","keyword1;keyword2","Bork, Chef","Bork, Chef","2000-06-26 00:34"\r\n'
'"3","baz32","need-eg","","","","2000-06-26 00:34"\r\n'))
#print(should_be)
#print(output.getvalue())
self.assertEqual(output.getvalue(), should_be)
output = io.BytesIO()
cl.request = MockNull()
cl.request.wfile = output
# call export version that outputs id numbers
actions.ExportCSVWithIdAction(cl).handle()
should_be = s2b('"id","title","status","keyword","assignedto","nosy","creation"\r\n'
'''"1","foo1","2","[]","4","['3', '4', '5']","2000-06-26.00:34:02"\r\n'''
'''"2","bar2","1","['1', '2']","3","['3']","2000-06-26.00:34:02"\r\n'''
'''"3","baz32","4","[]","None","[]","2000-06-26.00:34:02"\r\n''')
#print(should_be)
#print(output.getvalue())
self.assertEqual(output.getvalue(), should_be)
# reset the real date command
date.Date = originalDate
# test full text search
# call export version that outputs names
cl = self._make_client(
{'@columns': 'id,title,status,keyword,assignedto,nosy',
"@search_text": "bar2"}, nodeid=None, userid='1')
cl.classname = 'issue'
output = io.BytesIO()
cl.request = MockNull()
cl.request.wfile = output
actions.ExportCSVAction(cl).handle()
should_be=(s2b('"id","title","status","keyword","assignedto","nosy"\r\n'
'"2","bar2","unread","keyword1;keyword2","Bork, Chef","Bork, Chef"\r\n'))
self.assertEqual(output.getvalue(), should_be)
# call export version that outputs id numbers
output = io.BytesIO()
cl.request = MockNull()
cl.request.wfile = output
actions.ExportCSVWithIdAction(cl).handle()
should_be = s2b('"id","title","status","keyword","assignedto","nosy"\r\n'
"\"2\",\"bar2\",\"1\",\"['1', '2']\",\"3\",\"['3']\"\r\n")
self.assertEqual(output.getvalue(), should_be)
class FormTestCase(FormTestParent, StringFragmentCmpHelper, testCsvExport, unittest.TestCase):
def setUp(self):
FormTestParent.setUp(self)
tx_Source_init(self.db)
test = self.instance.backend.Class(self.db, "test",
string=hyperdb.String(), number=hyperdb.Number(),
intval=hyperdb.Integer(), boolean=hyperdb.Boolean(),
link=hyperdb.Link('test'), multilink=hyperdb.Multilink('test'),
date=hyperdb.Date(), messages=hyperdb.Multilink('msg'),
interval=hyperdb.Interval(), pw=hyperdb.Password() )
# compile the labels re
classes = '|'.join(self.db.classes.keys())
self.FV_SPECIAL = re.compile(FormParser.FV_LABELS%classes,
re.VERBOSE)
#
# form label extraction
#
def tl(self, s, c, i, a, p):
m = self.FV_SPECIAL.match(s)
self.assertNotEqual(m, None)
d = m.groupdict()
self.assertEqual(d['classname'], c)
self.assertEqual(d['id'], i)
for action in 'required add remove link note file'.split():
if a == action:
self.assertNotEqual(d[action], None)
else:
self.assertEqual(d[action], None)
self.assertEqual(d['propname'], p)
def testLabelMatching(self):
self.tl('', None, None, None, '')
self.tl(':required', None, None, 'required', None)
self.tl(':confirm:', None, None, 'confirm', '')
self.tl(':add:', None, None, 'add', '')
self.tl(':remove:', None, None, 'remove', '')
self.tl(':link:', None, None, 'link', '')
self.tl('test1:', 'test', '1', None, '')
self.tl('test1:required', 'test', '1', 'required', None)
self.tl('test1:add:', 'test', '1', 'add', '')
self.tl('test1:remove:', 'test', '1', 'remove', '')
self.tl('test1:link:', 'test', '1', 'link', '')
self.tl('test1:confirm:', 'test', '1', 'confirm', '')
self.tl('test-1:', 'test', '-1', None, '')
self.tl('test-1:required', 'test', '-1', 'required', None)
self.tl('test-1:add:', 'test', '-1', 'add', '')
self.tl('test-1:remove:', 'test', '-1', 'remove', '')
self.tl('test-1:link:', 'test', '-1', 'link', '')
self.tl('test-1:confirm:', 'test', '-1', 'confirm', '')
self.tl(':note', None, None, 'note', None)
self.tl(':file', None, None, 'file', None)
#
# Empty form
#
def testNothing(self):
self.assertEqual(self.parseForm({}), ({('test', None): {}}, []))
def testNothingWithRequired(self):
self.assertRaises(FormError, self.parseForm, {':required': 'string'})
self.assertRaises(FormError, self.parseForm,
{':required': 'title,status', 'status':'1'}, 'issue')
self.assertRaises(FormError, self.parseForm,
{':required': ['title','status'], 'status':'1'}, 'issue')
self.assertRaises(FormError, self.parseForm,
{':required': 'status', 'status':''}, 'issue')
self.assertRaises(FormError, self.parseForm,
{':required': 'nosy', 'nosy':''}, 'issue')
self.assertRaises(FormError, self.parseForm,
{':required': 'msg-1@content', 'msg-1@content':''}, 'issue')
self.assertRaises(FormError, self.parseForm,
{':required': 'msg-1@content'}, 'issue')
#
# Nonexistant edit
#
def testEditNonexistant(self):
self.assertRaises(FormError, self.parseForm, {'boolean': ''},
'test', '1')
#
# String
#
def testEmptyString(self):
self.assertEqual(self.parseForm({'string': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'string': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'string': ['', '']})
def testSetString(self):
self.assertEqual(self.parseForm({'string': 'foo'}),
({('test', None): {'string': 'foo'}}, []))
self.assertEqual(self.parseForm({'string': 'a\r\nb\r\n'}),
({('test', None): {'string': 'a\nb'}}, []))
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': 'foo'}, 'issue', nodeid),
({('issue', nodeid): {}}, []))
def testEmptyStringSet(self):
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': ''}, 'issue', nodeid),
({('issue', nodeid): {'title': None}}, []))
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': ' '}, 'issue', nodeid),
({('issue', nodeid): {'title': None}}, []))
def testStringLinkId(self):
self.db.status.set('1', name='2')
self.db.status.set('2', name='1')
issue = self.db.issue.create(title='i1-status1', status='1')
self.assertEqual(self.db.issue.get(issue,'status'),'1')
self.assertEqual(self.db.status.lookup('1'),'2')
self.assertEqual(self.db.status.lookup('2'),'1')
self.assertEqual(self.db.issue.get('1','tx_Source'),'web')
form = cgi.FieldStorage()
cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
cl.classname = 'issue'
cl.nodeid = issue
cl.db = self.db
cl.language = ('en',)
item = HTMLItem(cl, 'issue', issue)
self.assertEqual(item.status.id, '1')
self.assertEqual(item.status.name, '2')
def testStringMultilinkId(self):
id = self.db.keyword.create(name='2')
self.assertEqual(id,'1')
id = self.db.keyword.create(name='1')
self.assertEqual(id,'2')
issue = self.db.issue.create(title='i1-status1', keyword=['1'])
self.assertEqual(self.db.issue.get(issue,'keyword'),['1'])
self.assertEqual(self.db.keyword.lookup('1'),'2')
self.assertEqual(self.db.keyword.lookup('2'),'1')
self.assertEqual(self.db.issue.get(issue,'tx_Source'),'web')
form = cgi.FieldStorage()
cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
cl.classname = 'issue'
cl.nodeid = issue
cl.db = self.db
cl.language = ('en',)
cl.userid = '1'
item = HTMLItem(cl, 'issue', issue)
for keyword in item.keyword:
self.assertEqual(keyword.id, '1')
self.assertEqual(keyword.name, '2')
def testFileUpload(self):
file = FileUpload('foo', 'foo.txt')
self.assertEqual(self.parseForm({'content': file}, 'file'),
({('file', None): {'content': 'foo', 'name': 'foo.txt',
'type': 'text/plain'}}, []))
def testSingleFileUpload(self):
file = FileUpload('foo', 'foo.txt')
self.assertEqual(self.parseForm({'@file': file}, 'issue'),
({('file', '-1'): {'content': 'foo', 'name': 'foo.txt',
'type': 'text/plain'},
('issue', None): {}},
[('issue', None, 'files', [('file', '-1')])]))
def testMultipleFileUpload(self):
f1 = FileUpload('foo', 'foo.txt')
f2 = FileUpload('bar', 'bar.txt')
f3 = FileUpload('baz', 'baz.txt')
files = FileList('@file', f1, f2, f3)
self.assertEqual(self.parseForm(files, 'issue'),
({('file', '-1'): {'content': 'foo', 'name': 'foo.txt',
'type': 'text/plain'},
('file', '-2'): {'content': 'bar', 'name': 'bar.txt',
'type': 'text/plain'},
('file', '-3'): {'content': 'baz', 'name': 'baz.txt',
'type': 'text/plain'},
('issue', None): {}},
[ ('issue', None, 'files', [('file', '-1')])
, ('issue', None, 'files', [('file', '-2')])
, ('issue', None, 'files', [('file', '-3')])
]))
def testEditFileClassAttributes(self):
self.assertEqual(self.parseForm({'name': 'foo.txt',
'type': 'application/octet-stream'},
'file'),
({('file', None): {'name': 'foo.txt',
'type': 'application/octet-stream'}},[]))
#
# Link
#
def testEmptyLink(self):
self.assertEqual(self.parseForm({'link': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'link': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'link': ['', '']})
self.assertEqual(self.parseForm({'link': '-1'}),
({('test', None): {}}, []))
def testSetLink(self):
self.assertEqual(self.parseForm({'status': 'unread'}, 'issue'),
({('issue', None): {'status': '1'}}, []))
self.assertEqual(self.parseForm({'status': '1'}, 'issue'),
({('issue', None): {'status': '1'}}, []))
nodeid = self.db.issue.create(status='unread')
self.assertEqual(self.parseForm({'status': 'unread'}, 'issue', nodeid),
({('issue', nodeid): {}}, []))
self.assertEqual(self.db.issue.get(nodeid,'tx_Source'),'web')
def testUnsetLink(self):
nodeid = self.db.issue.create(status='unread')
self.assertEqual(self.parseForm({'status': '-1'}, 'issue', nodeid),
({('issue', nodeid): {'status': None}}, []))
self.assertEqual(self.db.issue.get(nodeid,'tx_Source'),'web')
def testInvalidLinkValue(self):
# XXX This is not the current behaviour - should we enforce this?
# self.assertRaises(IndexError, self.parseForm,
# {'status': '4'}))
self.assertRaises(FormError, self.parseForm, {'link': 'frozzle'})
self.assertRaises(FormError, self.parseForm, {'status': 'frozzle'},
'issue')
#
# Multilink
#
def testEmptyMultilink(self):
self.assertEqual(self.parseForm({'nosy': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'nosy': ' '}),
({('test', None): {}}, []))
def testSetMultilink(self):
self.assertEqual(self.parseForm({'nosy': '1'}, 'issue'),
({('issue', None): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({'nosy': 'admin'}, 'issue'),
({('issue', None): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({'nosy': ['1','2']}, 'issue'),
({('issue', None): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue'),
({('issue', None): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
({('issue', None): {'nosy': ['1','2']}}, []))
def testMixedMultilink(self):
form = cgi.FieldStorage()
form.list.append(cgi.MiniFieldStorage('nosy', '1,2'))
form.list.append(cgi.MiniFieldStorage('nosy', '3'))
cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
cl.classname = 'issue'
cl.nodeid = None
cl.db = self.db
cl.language = ('en',)
self.assertEqual(cl.parsePropsFromForm(create=1),
({('issue', None): {'nosy': ['1','2', '3']}}, []))
def testEmptyMultilinkSet(self):
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid),
({('issue', nodeid): {'nosy': []}}, []))
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({'nosy': ' '}, 'issue', nodeid),
({('issue', nodeid): {'nosy': []}}, []))
self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue', nodeid),
({('issue', nodeid): {}}, []))
def testInvalidMultilinkValue(self):
# XXX This is not the current behaviour - should we enforce this?
# self.assertRaises(IndexError, self.parseForm,
# {'nosy': '4'}))
self.assertRaises(FormError, self.parseForm, {'nosy': 'frozzle'},
'issue')
self.assertRaises(FormError, self.parseForm, {'nosy': '1,frozzle'},
'issue')
self.assertRaises(FormError, self.parseForm, {'multilink': 'frozzle'})
def testMultilinkAdd(self):
nodeid = self.db.issue.create(nosy=['1'])
# do nothing
self.assertEqual(self.parseForm({':add:nosy': ''}, 'issue', nodeid),
({('issue', nodeid): {}}, []))
# do something ;)
self.assertEqual(self.parseForm({':add:nosy': '2'}, 'issue', nodeid),
({('issue', nodeid): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({':add:nosy': '2,mary'}, 'issue',
nodeid), ({('issue', nodeid): {'nosy': ['1','2','4']}}, []))
self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue',
nodeid), ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
def testMultilinkAddNew(self):
self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue'),
({('issue', None): {'nosy': ['2','3']}}, []))
def testMultilinkRemove(self):
nodeid = self.db.issue.create(nosy=['1','2'])
# do nothing
self.assertEqual(self.parseForm({':remove:nosy': ''}, 'issue', nodeid),
({('issue', nodeid): {}}, []))
# do something ;)
self.assertEqual(self.parseForm({':remove:nosy': '1'}, 'issue',
nodeid), ({('issue', nodeid): {'nosy': ['2']}}, []))
self.assertEqual(self.parseForm({':remove:nosy': 'admin,2'},
'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
self.assertEqual(self.parseForm({':remove:nosy': ['1','2']},
'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
# add and remove
self.assertEqual(self.parseForm({':add:nosy': ['3'],
':remove:nosy': ['1','2']},
'issue', nodeid), ({('issue', nodeid): {'nosy': ['3']}}, []))
# remove one that doesn't exist?
self.assertRaises(FormError, self.parseForm, {':remove:nosy': '4'},
'issue', nodeid)
def testMultilinkRetired(self):
self.db.user.retire('2')
self.assertEqual(self.parseForm({'nosy': ['2','3']}, 'issue'),
({('issue', None): {'nosy': ['2','3']}}, []))
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({':remove:nosy': '2'}, 'issue',
nodeid), ({('issue', nodeid): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({':add:nosy': '3'}, 'issue', nodeid),
({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
def testAddRemoveNonexistant(self):
self.assertRaises(FormError, self.parseForm, {':remove:foo': '2'},
'issue')
self.assertRaises(FormError, self.parseForm, {':add:foo': '2'},
'issue')
#
# Password
#
def testEmptyPassword(self):
self.assertEqual(self.parseForm({'password': ''}, 'user'),
({('user', None): {}}, []))
self.assertEqual(self.parseForm({'password': ''}, 'user'),
({('user', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'password': ['', '']},
'user')
self.assertRaises(FormError, self.parseForm, {'password': 'foo',
':confirm:password': ['', '']}, 'user')
def testSetPassword(self):
self.assertEqual(self.parseForm({'password': 'foo',
':confirm:password': 'foo'}, 'user'),
({('user', None): {'password': 'foo'}}, []))
def testSetPasswordConfirmBad(self):
self.assertRaises(FormError, self.parseForm, {'password': 'foo'},
'user')
self.assertRaises(FormError, self.parseForm, {'password': 'foo',
':confirm:password': 'bar'}, 'user')
def testEmptyPasswordNotSet(self):
nodeid = self.db.user.create(username='1',
password=password.Password('foo'))
self.assertEqual(self.parseForm({'password': ''}, 'user', nodeid),
({('user', nodeid): {}}, []))
nodeid = self.db.user.create(username='2',
password=password.Password('foo'))
self.assertEqual(self.parseForm({'password': '',
':confirm:password': ''}, 'user', nodeid),
({('user', nodeid): {}}, []))
def testPasswordMigration(self):
chef = self.db.user.lookup('Chef')
form = dict(__login_name='Chef', __login_password='foo')
cl = self._make_client(form)
# assume that the "best" algorithm is the first one and doesn't
# need migration, all others should be migrated.
cl.db.config.WEB_LOGIN_ATTEMPTS_MIN = 200
cl.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 10000
# The third item always fails. Regardless of what is there.
# ['plaintext', 'SHA', 'crypt', 'MD5']:
print(password.Password.deprecated_schemes)
for scheme in password.Password.deprecated_schemes:
print(scheme)
cl.db.Otk = self.db.Otk
if scheme == 'crypt' and os.name == 'nt':
continue # crypt is not available on Windows
pw1 = password.Password('foo', scheme=scheme)
print(pw1)
self.assertEqual(pw1.needs_migration(config=cl.db.config), True)
self.db.user.set(chef, password=pw1)
self.db.commit()
actions.LoginAction(cl).handle()
pw = cl.db.user.get(chef, 'password')
print(pw)
self.assertEqual(pw, 'foo')
self.assertEqual(pw.needs_migration(config=cl.db.config), False)
cl.db.Otk = self.db.Otk
pw1 = pw
self.assertEqual(pw1.needs_migration(config=cl.db.config), False)
scheme = password.Password.known_schemes[0]
self.assertEqual(scheme, pw1.scheme)
actions.LoginAction(cl).handle()
pw = cl.db.user.get(chef, 'password')
self.assertEqual(pw, 'foo')
self.assertEqual(pw, pw1)
# migrate if rounds has increased above rounds was 10000
# below will be 100000
cl.db.Otk = self.db.Otk
pw1 = pw
# do not use the production number of PBKDF2
os.environ["PYTEST_USE_CONFIG"] = "True"
cl.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 100000
self.assertEqual(pw1.needs_migration(config=cl.db.config), True)
scheme = password.Password.known_schemes[0]
self.assertEqual(scheme, pw1.scheme)
actions.LoginAction(cl).handle()
pw = cl.db.user.get(chef, 'password')
self.assertEqual(pw, 'foo')
del(os.environ["PYTEST_USE_CONFIG"])
# do not assert self.assertEqual(pw, pw1) as pw is a 100,000
# cycle while pw1 is only 10,000. They won't compare equally.
cl.db.close()
def testPasswordConfigOption(self):
chef = self.db.user.lookup('Chef')
form = dict(__login_name='Chef', __login_password='foo')
cl = self._make_client(form)
self.db.config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = 1000
pw1 = password.Password('foo', scheme='MD5')
self.assertEqual(pw1.needs_migration(config=cl.db.config), True)
self.db.user.set(chef, password=pw1)
self.db.commit()
actions.LoginAction(cl).handle()
pw = self.db.user.get(chef, 'password')
self.assertEqual('PBKDF2', pw.scheme)
self.assertEqual(1000, password.pbkdf2_unpack(pw.password)[0])
cl.db.close()
#
# Boolean
#
def testEmptyBoolean(self):
self.assertEqual(self.parseForm({'boolean': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'boolean': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'boolean': ['', '']})
def testSetBoolean(self):
self.assertEqual(self.parseForm({'boolean': 'yes'}),
({('test', None): {'boolean': 1}}, []))
self.assertEqual(self.parseForm({'boolean': 'a\r\nb\r\n'}),
({('test', None): {'boolean': 0}}, []))
nodeid = self.db.test.create(boolean=1)
self.assertEqual(self.parseForm({'boolean': 'yes'}, 'test', nodeid),
({('test', nodeid): {}}, []))
nodeid = self.db.test.create(boolean=0)
self.assertEqual(self.parseForm({'boolean': 'no'}, 'test', nodeid),
({('test', nodeid): {}}, []))
def testEmptyBooleanSet(self):
nodeid = self.db.test.create(boolean=0)
self.assertEqual(self.parseForm({'boolean': ''}, 'test', nodeid),
({('test', nodeid): {'boolean': None}}, []))
nodeid = self.db.test.create(boolean=1)
self.assertEqual(self.parseForm({'boolean': ' '}, 'test', nodeid),
({('test', nodeid): {'boolean': None}}, []))
def testRequiredBoolean(self):
self.assertRaises(FormError, self.parseForm, {'boolean': '',
':required': 'boolean'})
try:
self.parseForm({'boolean': 'no', ':required': 'boolean'})
except FormError:
self.fail('boolean "no" raised "required missing"')
#
# Number
#
def testEmptyNumber(self):
self.assertEqual(self.parseForm({'number': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'number': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'number': ['', '']})
def testInvalidNumber(self):
self.assertRaises(FormError, self.parseForm, {'number': 'hi, mum!'})
def testSetNumber(self):
self.assertEqual(self.parseForm({'number': '1'}),
({('test', None): {'number': 1}}, []))
self.assertEqual(self.parseForm({'number': '0'}),
({('test', None): {'number': 0}}, []))
self.assertEqual(self.parseForm({'number': '\n0\n'}),
({('test', None): {'number': 0}}, []))
def testSetNumberReplaceOne(self):
nodeid = self.db.test.create(number=1)
self.assertEqual(self.parseForm({'number': '1'}, 'test', nodeid),
({('test', nodeid): {}}, []))
self.assertEqual(self.parseForm({'number': '0'}, 'test', nodeid),
({('test', nodeid): {'number': 0}}, []))
def testSetNumberReplaceZero(self):
nodeid = self.db.test.create(number=0)
self.assertEqual(self.parseForm({'number': '0'}, 'test', nodeid),
({('test', nodeid): {}}, []))
def testSetNumberReplaceNone(self):
nodeid = self.db.test.create()
self.assertEqual(self.parseForm({'number': '0'}, 'test', nodeid),
({('test', nodeid): {'number': 0}}, []))
self.assertEqual(self.parseForm({'number': '1'}, 'test', nodeid),
({('test', nodeid): {'number': 1}}, []))
def testEmptyNumberSet(self):
nodeid = self.db.test.create(number=0)
self.assertEqual(self.parseForm({'number': ''}, 'test', nodeid),
({('test', nodeid): {'number': None}}, []))
nodeid = self.db.test.create(number=1)
self.assertEqual(self.parseForm({'number': ' '}, 'test', nodeid),
({('test', nodeid): {'number': None}}, []))
def testRequiredNumber(self):
self.assertRaises(FormError, self.parseForm, {'number': '',
':required': 'number'})
try:
self.parseForm({'number': '0', ':required': 'number'})
except FormError:
self.fail('number "no" raised "required missing"')
#
# Integer
#
def testEmptyInteger(self):
self.assertEqual(self.parseForm({'intval': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'intval': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'intval': ['', '']})
def testInvalidInteger(self):
self.assertRaises(FormError, self.parseForm, {'intval': 'hi, mum!'})
def testSetInteger(self):
self.assertEqual(self.parseForm({'intval': '1'}),
({('test', None): {'intval': 1}}, []))
self.assertEqual(self.parseForm({'intval': '0'}),
({('test', None): {'intval': 0}}, []))
self.assertEqual(self.parseForm({'intval': '\n0\n'}),
({('test', None): {'intval': 0}}, []))
def testSetIntegerReplaceOne(self):
nodeid = self.db.test.create(intval=1)
self.assertEqual(self.parseForm({'intval': '1'}, 'test', nodeid),
({('test', nodeid): {}}, []))
self.assertEqual(self.parseForm({'intval': '0'}, 'test', nodeid),
({('test', nodeid): {'intval': 0}}, []))
def testSetIntegerReplaceZero(self):
nodeid = self.db.test.create(intval=0)
self.assertEqual(self.parseForm({'intval': '0'}, 'test', nodeid),
({('test', nodeid): {}}, []))
def testSetIntegerReplaceNone(self):
nodeid = self.db.test.create()
self.assertEqual(self.parseForm({'intval': '0'}, 'test', nodeid),
({('test', nodeid): {'intval': 0}}, []))
self.assertEqual(self.parseForm({'intval': '1'}, 'test', nodeid),
({('test', nodeid): {'intval': 1}}, []))
def testEmptyIntegerSet(self):
nodeid = self.db.test.create(intval=0)
self.assertEqual(self.parseForm({'intval': ''}, 'test', nodeid),
({('test', nodeid): {'intval': None}}, []))
nodeid = self.db.test.create(intval=1)
self.assertEqual(self.parseForm({'intval': ' '}, 'test', nodeid),
({('test', nodeid): {'intval': None}}, []))
def testRequiredInteger(self):
self.assertRaises(FormError, self.parseForm, {'intval': '',
':required': 'intval'})
try:
self.parseForm({'intval': '0', ':required': 'intval'})
except FormError:
self.fail('intval "no" raised "required missing"')
#
# Date
#
def testEmptyDate(self):
self.assertEqual(self.parseForm({'date': ''}),
({('test', None): {}}, []))
self.assertEqual(self.parseForm({'date': ' '}),
({('test', None): {}}, []))
self.assertRaises(FormError, self.parseForm, {'date': ['', '']})
def testInvalidDate(self):
self.assertRaises(FormError, self.parseForm, {'date': '12'})
def testSetDate(self):
self.assertEqual(self.parseForm({'date': '2003-01-01'}),
({('test', None): {'date': date.Date('2003-01-01')}}, []))
nodeid = self.db.test.create(date=date.Date('2003-01-01'))
self.assertEqual(self.parseForm({'date': '2003-01-01'}, 'test',
nodeid), ({('test', nodeid): {}}, []))
def testEmptyDateSet(self):
nodeid = self.db.test.create(date=date.Date('.'))
self.assertEqual(self.parseForm({'date': ''}, 'test', nodeid),
({('test', nodeid): {'date': None}}, []))
nodeid = self.db.test.create(date=date.Date('1970-01-01.00:00:00'))
self.assertEqual(self.parseForm({'date': ' '}, 'test', nodeid),
({('test', nodeid): {'date': None}}, []))
#
# Test multiple items in form
#
def testMultiple(self):
self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'}),
({('test', None): {'string': 'a'},
('issue', '-1'): {'title': 'b'}
}, []))
def testMultipleExistingContext(self):
nodeid = self.db.test.create()
self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'},
'test', nodeid),({('test', nodeid): {'string': 'a'},
('issue', '-1'): {'title': 'b'}}, []))
def testLinking(self):
self.assertEqual(self.parseForm({
'string': 'a',
'issue-1@add@nosy': '1',
'issue-2@link@superseder': 'issue-1',
}),
({('test', None): {'string': 'a'},
('issue', '-1'): {'nosy': ['1']},
},
[('issue', '-2', 'superseder', [('issue', '-1')])
]
)
)
def testMessages(self):
self.assertEqual(self.parseForm({
'msg-1@content': 'asdf',
'msg-2@content': 'qwer',
'@link@messages': 'msg-1, msg-2'}),
({('test', None): {},
('msg', '-2'): {'content': 'qwer'},
('msg', '-1'): {'content': 'asdf'}},
[('test', None, 'messages', [('msg', '-1'), ('msg', '-2')])]
)
)
def testLinkBadDesignator(self):
self.assertRaises(FormError, self.parseForm,
{'test-1@link@link': 'blah'})
self.assertRaises(FormError, self.parseForm,
{'test-1@link@link': 'issue'})
def testLinkNotLink(self):
self.assertRaises(FormError, self.parseForm,
{'test-1@link@boolean': 'issue-1'})
self.assertRaises(FormError, self.parseForm,
{'test-1@link@string': 'issue-1'})
def testBackwardsCompat(self):
res = self.parseForm({':note': 'spam'}, 'issue')
date = res[0][('msg', '-1')]['date']
self.assertEqual(res, ({('issue', None): {}, ('msg', '-1'):
{'content': 'spam', 'author': '1', 'date': date}},
[('issue', None, 'messages', [('msg', '-1')])]))
file = FileUpload('foo', 'foo.txt')
self.assertEqual(self.parseForm({':file': file}, 'issue'),
({('issue', None): {}, ('file', '-1'): {'content': 'foo',
'name': 'foo.txt', 'type': 'text/plain'}},
[('issue', None, 'files', [('file', '-1')])]))
def testErrorForBadTemplate(self):
form = {}
cl = self.setupClient(form, 'issue', '1', template="broken",
env_addon = {'HTTP_REFERER': 'http://whoami.com/path/'})
out = []
out = cl.renderContext()
self.assertEqual(out, 'No template file exists for templating "issue" with template "broken" (neither "issue.broken" nor "_generic.broken")')
self.assertEqual(cl.response_code, 400)
def testFormValuePreserveOnError(self):
page_template = """
""".strip ()
self.db.keyword.create (name = 'key1')
self.db.keyword.create (name = 'key2')
nodeid = self.db.issue.create (title = 'Title', priority = '1',
status = '1', nosy = ['1'], keyword = ['1'])
self.db.commit ()
form = {':note': 'msg-content', 'title': 'New title',
'priority': '2', 'status': '2', 'nosy': '1,2', 'keyword': '',
'superseder': '5000', ':action': 'edit'}
cl = self.setupClient(form, 'issue', '1',
env_addon = {'HTTP_REFERER': 'http://whoami.com/path/'})
pt = RoundupPageTemplate()
pt.pt_edit(page_template, 'text/html')
out = []
def wh(s):
out.append(s)
cl.write_html = wh
# Enable the following if we get a templating error:
#def send_error (*args, **kw):
# import pdb; pdb.set_trace()
#cl.send_error_to_admin = send_error
# Need to rollback the database on error -- this usually happens
# in web-interface (and for other databases) anyway, need it for
# testing that the form values are really used, not the database!
# We do this together with the setup of the easy template above
def load_template(x):
cl.db.rollback()
return pt
cl.instance.templates.load = load_template
cl.selectTemplate = MockNull()
cl.determine_context = MockNull ()
def hasPermission(s, p, classname=None, d=None, e=None, **kw):
return True
actions.Action.hasPermission = hasPermission
e1 = _HTMLItem.is_edit_ok
_HTMLItem.is_edit_ok = lambda x : True
e2 = HTMLProperty.is_edit_ok
HTMLProperty.is_edit_ok = lambda x : True
cl.inner_main()
# The original self.db has been changed. Assign the new
# cl.db to self.db so it gets closed at the end of the test.
self.db = cl.db
_HTMLItem.is_edit_ok = e1
HTMLProperty.is_edit_ok = e2
self.assertEqual(len(out), 1)
self.assertEqual(out [0].strip (), """
Edit Error: issue has no node 5000
New title
urgent
deferred
admin, anonymous
""".strip ())
def testXMLTemplate(self):
page_template = """"""
pt = RoundupPageTemplate()
pt.pt_edit(page_template, 'application/xml')
cl = self.setupClient({ }, 'issue',
env_addon = {'HTTP_REFERER': 'http://whoami.com/path/'})
out = pt.render(cl, 'issue', MockNull())
self.assertEqual(out, '\n')
def testHttpProxyStrip(self):
os.environ['HTTP_PROXY'] = 'http://bad.news/here/'
cl = self.setupClient({ }, 'issue',
env_addon = {'HTTP_PROXY': 'http://bad.news/here/'})
out = []
def wh(s):
out.append(s)
cl.write_html = wh
cl.main()
self.db = cl.db # to close new db handle from main() at tearDown
self.assertFalse('HTTP_PROXY' in cl.env)
self.assertFalse('HTTP_PROXY' in os.environ)
def testCsrfProtectionHtml(self):
# need to set SENDMAILDEBUG to prevent
# downstream issue when email is sent on successful
# issue creation. Also delete the file afterwards
# just to make sure that some other test looking for
# SENDMAILDEBUG won't trip over ours.
if 'SENDMAILDEBUG' not in os.environ:
os.environ['SENDMAILDEBUG'] = 'mail-test1.log'
SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
page_template = """
""".strip ()
self.db.keyword.create (name = 'key1')
self.db.keyword.create (name = 'key2')
nodeid = self.db.issue.create (title = 'Title', priority = '1',
status = '1', nosy = ['1'], keyword = ['1'])
self.db.commit ()
form = {':note': 'msg-content', 'title': 'New title',
'priority': '2', 'status': '2', 'nosy': '1,2', 'keyword': '',
':action': 'edit'}
cl = self.setupClient(form, 'issue', '1')
pt = RoundupPageTemplate()
pt.pt_edit(page_template, 'text/html')
out = []
def wh(s):
out.append(s)
cl.write_html = wh
# Enable the following if we get a templating error:
#def send_error (*args, **kw):
# import pdb; pdb.set_trace()
#cl.send_error_to_admin = send_error
# Need to rollback the database on error -- this usually happens
# in web-interface (and for other databases) anyway, need it for
# testing that the form values are really used, not the database!
# We do this together with the setup of the easy template above
def load_template(x):
cl.db.rollback()
return pt
cl.instance.templates.load = load_template
cl.selectTemplate = MockNull()
cl.determine_context = MockNull ()
def hasPermission(s, p, classname=None, d=None, e=None, **kw):
return True
actions.Action.hasPermission = hasPermission
orig_HTMLItem_is_edit_ok = _HTMLItem.is_edit_ok
e1 = _HTMLItem.is_edit_ok
_HTMLItem.is_edit_ok = lambda x : True
e2 = HTMLProperty.is_edit_ok
orig_HTMLProperty_is_edit_ok = HTMLProperty.is_edit_ok
HTMLProperty.is_edit_ok = lambda x : True
# test with no headers. Default config requires that 1 header
# is present and passes checks.
cl.main()
match_at=out[0].find('Unable to verify sufficient headers')
print("result of subtest 1:", out[0])
self.assertNotEqual(match_at, -1)
del(out[0])
# all the rest of these allow at least one header to pass
# and the edit happens with a redirect back to issue 1
cl.env['HTTP_REFERER'] = 'http://whoami.com/path/'
cl.main()
match_at=out[0].find('Redirecting to alert(1)'
cl.main()
match_at=out[0].find('