from flask_language import Language, current_language
import gettext
import click
+ import re
def get_db():
db = getattr(g, '_database', None)
prefix = ConfigProxy("GROUP_PREFIX")
times = ConfigProxy("DURATION")
debuguser = ConfigProxy("DEBUGUSER")
+motion_wait_minutes = ConfigProxy("MOTION_WAIT_MINUTES")
max_proxy=app.config.get("MAX_PROXY")
db.prepare("ALTER TABLE \"voter\" ALTER COLUMN \"host\" SET NOT NULL")()
db.prepare("UPDATE \"schema_version\" SET \"version\"=6")()
+ if ver < 7:
+ with app.open_resource('sql/from_6.sql', mode='r') as f:
+ db.execute(f.read())
+ db.prepare("UPDATE \"schema_version\" SET \"version\"=7")()
+
init_db()
+def is_in_ratelimit(group):
+ rv = get_db().prepare("SELECT EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - posed)) AS timedifference FROM motion WHERE type=$1 AND host=$2 ORDER BY posed DESC LIMIT 1")(group, request.host)
+ if len(rv) == 0:
+ return True
+ rate_limit = motion_wait_minutes.per_host
+ if rate_limit is None:
+ rate_limit = 0
+ if rv[0]['timedifference'] > rate_limit*60:
+ return True
+ else:
+ return _('Error, time between last motion to short. The current setting is %s minute(s).') % (str(rate_limit))
@app.route("/")
def main():
r.autocorrect_location_header = False
return r
+ def write_proxy_log(userid, action, comment):
+ get_db().prepare("INSERT INTO adminlog(user_id, action, comment, action_user_id) VALUES($1, $2, $3, $4)")(userid, action, comment, g.voter)
+
+ def write_masking_log(comment):
+ get_db().prepare("INSERT INTO adminlog(user_id, action, comment, action_user_id) VALUES($1, 'motionmasking', $2, $1)")(0, comment)
+
@app.route("/motion", methods=['POST'])
def put_motion():
cat=request.form.get("category", "")
content=content.strip()
if content =='':
return _('Error, missing content'), 400
+ ratelimit = is_in_ratelimit(cat)
+ if ratelimit is not True:
+ return ratelimit, 400
db = get_db()
with db.xact():
if rv[0].get("c") is None or rv[0].get("c") >= max_proxy:
return _("Error, Max proxy for '%s' reached.") % (proxy), 400
rv = get_db().prepare("INSERT INTO proxy(voter_id, proxy_id, granted_by) VALUES ($1,$2,$3)")(voterid, proxyid, g.voter)
+ write_proxy_log(voterid, 'proxygranted', 'proxy: '+str(proxyid))
return rel_redirect("/proxy")
@app.route("/proxy/revoke", methods=['POST'])
return _('Forbidden'), 403
id=request.form.get("id", "")
rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE id=$2")(g.voter, int(id))
+ write_proxy_log(int(id), 'proxyrevoked', '')
return rel_redirect("/proxy")
@app.route("/proxy/revokeall", methods=['POST'])
if not may_admin("proxyadmin"):
return _('Forbidden'), 403
rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE revoked IS NULL")(g.voter)
+ write_proxy_log(g.voter, 'proxyrevokedall', '')
return rel_redirect("/proxy")
@app.route("/language/<string:language>")
db.prepare("INSERT INTO voter(\"email\", \"host\") VALUES($1, $2)")(email, host)
messagetext=_("User '%s' inserted to %s.") % (email, host)
click.echo(messagetext)
+
+ @app.cli.command("motion-masking")
+ @click.argument("motion")
+ @click.argument("motionreason")
+ @click.argument("host")
+ def motion_masking(motion, motionreason, host):
+ if re.search(r"[%_\\]", motion):
+ messagetext = _("No wildcards allowed for motion entry '%s'.") % (motion)
+ click.echo(messagetext)
+ else:
+ db = get_db()
+ with db.xact():
+ rv = db.prepare("SELECT id FROM motion WHERE identifier LIKE $1 AND host = $2")(motion+"%", host)
+ count = len(rv)
+ messagetext = _("%s record(s) affected by masking of '%s'.") % (count, motion)
+ click.echo(messagetext)
+ if len(rv) != 0:
+ rv = db.prepare("SELECT id FROM motion WHERE content LIKE $1 AND host = $2")('%'+motionreason+"%", host)
+ rv = db.prepare("UPDATE motion SET name=$3, content=$4 WHERE identifier LIKE $1 AND host = $2 RETURNING id ")(motion+"%", host, _("Motion masked"), _("Motion masked on base of motion [%s](%s) on %s") % (motionreason, motionreason, datetime.now().strftime("%Y-%m-%d")))
+ messagetext = _("%s record(s) updated by masking of '%s'.") % (len(rv), motion)
+ write_masking_log(_("%s motion(s) masked on base of motion %s with motion identifier '%s' on host %s") %(len(rv), motionreason, motion, host))
+ click.echo(messagetext)
from datetime import datetime
from tests.test_basics import BasicTest
-import postgresql
from motion import app
# no specific rights required
self.assertEqual(response.status_code, 403)
self.assertIn(str.encode('Error, out of time'), response.data)
+ def test_createMotionWait(self):
+ # test no limit given
+ self.db_sampledata()
+ title='My Motion'
+ content='My body'
+ response = self.createMotion(user, title, content, '3', 'group1')
+ self.assertEqual(response.status_code, 302)
+
+ # test different host
+ app.config.update(MOTION_WAIT_MINUTES={'127.0.0.1:5001':1})
+ response = self.createMotion(user, title, content, '3', 'group1')
+ self.assertEqual(response.status_code, 302)
+
+ # test 3 minutes
+ app.config.update(MOTION_WAIT_MINUTES={'127.0.0.1:5000':3})
+ response = self.createMotion(user, title, content, '3', 'group1')
+ self.assertIn(str.encode('Error, time between last motion to short. The current setting is 3 minute(s).'), response.data)
+
class AuditMotionTests(BasicTest):
def setUp(self):
+ '\n <div>User C: no</div>\n </div>\n</div>\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
self.assertIn(str.encode(testtext), result.data)
- class ProxyManagementTests(BasicTest):
-
- def setUp(self):
- self.init_test()
- global user
- user='testuser/proxyadmin:*'
- global userid
- userid=4
- self.db_sampledata()
-
- def tearDown(self):
- pass
-
- def test_see_proxy(self):
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= 'div class="container">\n<form action="/proxy/add" method="POST">'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= '<select class="float form-control" name="voter">\n '\
- + '<option>User A</option>\n <option>User B</option>\n '\
- + '<option>User C</option>\n '\
- + '<option>testuser</option>\n '\
- + '</select>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<select class="float form-control" name="proxy">\n '\
- + '<option>User A</option>\n '\
- + '<option>User B</option>\n '\
- + '<option>User C</option>\n '\
- + '<option>testuser</option>\n '\
- + '</select>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '</table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<a class="nav-link" href="/proxy">Proxy management</a>'
- self.assertIn(str.encode(testtext), result.data)
-
- def test_add_proxy(self):
- voter=''
- proxy=''
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, voter equals proxy.'), response.data)
-
- voter='User A'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy not found.'), response.data)
-
- voter='User Z'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, voter not found.'), response.data)
-
- voter=''
- proxy='User B'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, voter not found.'), response.data)
-
- voter='User B'
- proxy='User B'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, voter equals proxy.'), response.data)
-
- voter='User A'
- proxy='User Z'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy not found.'), response.data)
-
- voter='User A'
- proxy='User B'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<form action="/proxy/revoke" method="POST">'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n '\
- + '<th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
-
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy allready given.'), response.data)
-
- voter='User A'
- proxy='User C'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy allready given.'), response.data)
-
- voter='User C'
- proxy='User B'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n '\
- + '<th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User C</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="2">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
-
- voter='testuser'
- proxy='User B'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, Max proxy for \'User B\' reached.'), response.data)
-
- voter='testuser'
- proxy='User A'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>testuser</td>\n <td>User A</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="3">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User C</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="2">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to: User A\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
-
- voter='User B'
- proxy='testuser'
- response = self.addProxy(user, voter, proxy)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>testuser</td>\n <td>User A</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="3">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User B</td>\n <td>testuser</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="4">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User C</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="2">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to: User A\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of: User B\n'
- self.assertIn(str.encode(testtext), result.data)
-
- response = self.revokeProxy(user, userid)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>testuser</td>\n <td>User A</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="3">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User C</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="2">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to: User A\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
-
- response = self.revokeProxy(user, 3)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n '\
- + '<tr>\n <td>User A</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="1">Revoke</button></td>\n </tr>\n '\
- + '<tr>\n <td>User C</td>\n <td>User B</td>\n '\
- + '<td><button type="submit" class="btn btn-danger" name="id" value="2">Revoke</button></td>\n '\
- + '</tr>\n </table>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
-
- result = self.app.post('proxy/revokeall', environ_base={'USER_ROLES': user}, follow_redirects=True)
- self.assertEqual(response.status_code, 302)
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<table>\n '\
- + '<thead>\n '\
- + '<th>Voter</th>\n <th>Proxy</th>\n <th></th>\n </thead>\n'\
- + '</table>\n'
- self.assertNotIn(str.encode(testtext), result.data)
-
- proxytest="proxytest"
- with self.open_DB() as db:
- db.prepare("INSERT INTO voter(\"email\", \"host\") VALUES($1, $2)")(proxytest, '127.0.0.1:5001')
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
-
- response = self.addProxy(user, proxytest, 'testuser')
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, voter not found.'), response.data)
-
- response = self.addProxy(user, 'testuser', proxytest)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy not found.'), response.data)
-
- def test_see_proxy_host_only(self):
- proxytest="proxytest"
- with self.open_DB() as db:
- db.prepare("INSERT INTO voter(\"email\", \"host\") VALUES($1, $2)")(proxytest, '127.0.0.1:5001')
- result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= 'div class="container">\n<form action="/proxy/add" method="POST">'
- self.assertIn(str.encode(testtext), result.data)
- testtext= 'proxy granted to:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= 'holds proxy of:'
- self.assertNotIn(str.encode(testtext), result.data)
- testtext= '<select class="float form-control" name="voter">\n '\
- + '<option>User A</option>\n <option>User B</option>\n '\
- + '<option>User C</option>\n '\
- + '<option>testuser</option>\n '\
- + '</select>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<select class="float form-control" name="proxy">\n '\
- + '<option>User A</option>\n '\
- + '<option>User B</option>\n '\
- + '<option>User C</option>\n '\
- + '<option>testuser</option>\n '\
- + '</select>\n'
- self.assertIn(str.encode(testtext), result.data)
- self.assertNotIn(str.encode(proxytest), result.data)
-
- class ProxyVoteTests(BasicTest):
-
- def setUp(self):
- self.init_test()
- global user
- user='testuser/vote:* proxyadmin:*'
- self.db_sampledata()
-
- def tearDown(self):
- pass
-
- def test_proxy_vote(self):
- voter='testuser'
- proxy='User B'
- proxyid=2
- proxyuser='User B/vote:*'
-
- response = self.addProxy(user, proxy, voter)
- self.assertEqual(response.status_code, 302)
-
- motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes', proxyid)
- self.assertEqual(response.status_code, 302)
-
- # testuser view
- result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
- # own vote without change
- testtext= '<form action="/motion/g1.20200402.004/vote/4" method="POST">\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>'
- self.assertIn(str.encode(testtext), result.data)
- # proxy vote with change
- testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
- + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>\n'
- self.assertIn(str.encode(testtext), result.data)
-
- # User B view
- result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': proxyuser}, follow_redirects=True)
- # own vote without change
- testtext= '<h3>My vote</h3>\nGiven by testuser\n'\
- + '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
- + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>'
- self.assertIn(str.encode(testtext), result.data)
-
- # change vote
- response = self.createVote(user, motion, 'no', proxyid)
- self.assertEqual(response.status_code, 302)
-
- result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
- + '<button type="submit" class="btn btn-success" name="vote" value="no" id="vote-no">No</button>\n'\
- + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>\n'
- self.assertIn(str.encode(testtext), result.data)
-
- def test_proxy_vote_no_proxy(self):
- voter='testuser'
- proxy='User B'
- # wrong proxy id
- proxyid=3
-
- response = self.addProxy(user, proxy, voter)
- self.assertEqual(response.status_code, 302)
-
- motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes', proxyid)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy not found'), response.data)
-
- # non existing id
- proxyid=10000
-
- motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes', proxyid)
- self.assertEqual(response.status_code, 400)
- self.assertIn(str.encode('Error, proxy not found'), response.data)
-
- def test_proxy_vote_no_voter(self):
- voter='User A'
- proxy='User B'
- proxyid=2
-
- response = self.addProxy(user, proxy, voter)
- self.assertEqual(response.status_code, 302)
-
- user1='testuser1/'
- motion='g1.20200402.004'
- response = self.createVote(user1, motion, 'yes', proxyid)
- self.assertEqual(response.status_code, 403)
- self.assertIn(str.encode('Forbidden'), response.data)
-
-
if __name__ == "__main__":
unittest.main()