This is an automated email from the ASF dual-hosted git repository. gstein pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/steve.git
commit 7664ed4a6e42b26686d8c9728d54051ce7941231 Author: Greg Stein <[email protected]> AuthorDate: Thu Sep 25 15:44:04 2025 -0500 Factor out the Person database. Persons are independent from Elections, so split their code and handling into separate modules. * steve/persondb.py: new module to interact with the "persons" table. Note: remove self.is_editable() from some methods, as the editability of an Election is not applicable. * steve/election.py: shift code/queries over to new module. Take a PDB argument, when we need information from the Persons database. * test/check_coverage.py: track API changes. --- v3/steve/election.py | 61 +++++++------------------------------ v3/steve/persondb.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++ v3/test/check_coverage.py | 20 ++++++------ 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/v3/steve/election.py b/v3/steve/election.py index 02c7638..59715e2 100644 --- a/v3/steve/election.py +++ b/v3/steve/election.py @@ -35,6 +35,7 @@ class Election: S_CLOSED = 'closed' def __init__(self, db_fname, eid): + ### switch to asfpy.db self.db = db.DB(db_fname) self.eid = eid @@ -55,16 +56,8 @@ class Election: type=excluded.type, kv=excluded.kv ''') - self.c_add_person = self.db.add_statement( - '''INSERT INTO PERSON VALUES (?, ?, ?) - ON CONFLICT DO UPDATE SET - name=excluded.name, - email=excluded.email - ''') self.c_delete_issue = self.db.add_statement( 'DELETE FROM ISSUES WHERE iid = ?') - self.c_delete_person = self.db.add_statement( - 'DELETE FROM PERSON WHERE pid = ?') self.c_add_vote = self.db.add_statement( 'INSERT INTO VOTES VALUES (NULL, ?, ?)') self.c_add_mayvote = self.db.add_statement( @@ -89,12 +82,8 @@ class Election: 'SELECT * FROM ELECTIONS WHERE eid = ?') self.q_issues = self.db.add_query('issues', 'SELECT * FROM ISSUES WHERE eid = ? ORDER BY iid') - self.q_person = self.db.add_query('person', - 'SELECT * FROM PERSON ORDER BY pid') self.q_get_issue = self.db.add_query('issues', 'SELECT * FROM ISSUES WHERE iid = ?') - self.q_get_person = self.db.add_query('person', - 'SELECT * FROM PERSON WHERE pid = ?') self.q_get_mayvote = self.db.add_query('mayvote', 'SELECT * FROM MAYVOTE WHERE pid = ? AND iid = ?') self.q_tally = self.db.add_query('mayvote', @@ -148,7 +137,7 @@ class Election: self.db.conn.execute('COMMIT') - def open(self): + def open(self, pdb): # Double-check the Election is in the editing state. assert self.is_editable() @@ -157,7 +146,7 @@ class Election: # happens before we move to the "opened" state. self.add_salts() - edata = self.gather_election_data() + edata = self.gather_election_data(pdb) print('EDATA:', edata) salt = crypto.gen_salt() opened_key = crypto.gen_opened_key(edata, salt) @@ -166,7 +155,7 @@ class Election: print('KEY:', opened_key) self.c_open.perform((salt, opened_key, self.eid)) - def gather_election_data(self): + def gather_election_data(self, pdb): "Gather a definition of this election for keying and anti-tamper." # NOTE: separators and other zero-entropy constant chars are @@ -183,9 +172,11 @@ class Election: idata = ''.join(f'{i.iid}{i.title}{i.description}{i.type}{i.kv}' for i in self.q_issues.fetchall()) - self.q_person.perform() - pdata = ''.join(p.pid + p.email - for p in self.q_person.fetchall()) + ### we don't want all people. Just those who are allowed to + ### vote in this Election. Examine the "mayvote" table. + ### list_persons returns 3-tuples of (PID,NAME,EMAIL). We only + ### want pid/email in PDATA. + pdata = ''.join(p[0] + p[2] for p in pdb.list_persons()) return (mdata + idata + pdata).encode() @@ -263,36 +254,6 @@ class Election: self.q_issues.perform((self.eid,)) return [ extract_issue(row) for row in self.q_issues.fetchall() ] - def get_person(self, pid): - "Return NAME, EMAIL for Person identified by PID." - - # NEVER return person.salt - person = self.q_get_person.first_row((pid,)) - return person.name, person.email - - def add_person(self, pid, name, email): - "Add or update a Person designated by PID." - assert self.is_editable() - - # If we ADD, then SALT will be NULL. If we UPDATE, then it will not - # be touched (it should be NULL). - self.c_add_person.perform((pid, name, email,)) - - def delete_person(self, pid): - "Delete the Person designated by PID." - - # Can only delete Persons before the Election is OPEN. - assert self.is_editable() - - self.c_delete_person.perform((pid,)) - - def list_persons(self): - "Return ordered (PID, NAME, EMAIL) for each Person." - - # NOTE: the SALT column is omitted. It should never be exposed. - self.q_person.perform() - return [ row[:3] for row in self.q_person.fetchall() ] - def add_voter(self, pid: str, iid: str | None = None) -> None: "Add PID (Person) to Issue IID, or to all Issues (None)." @@ -394,7 +355,7 @@ class Election: return voted_upon - def is_tampered(self): + def is_tampered(self, pdb): # The Election should be open. assert self.is_open() @@ -402,7 +363,7 @@ class Election: md = self.q_metadata.first_row((self.eid,)) # Compute an opened_key based on the current data. - edata = self.gather_election_data() + edata = self.gather_election_data(pdb) opened_key = crypto.gen_opened_key(edata, md.salt) print('EDATA:', edata) diff --git a/v3/steve/persondb.py b/v3/steve/persondb.py new file mode 100644 index 0000000..2caebf2 --- /dev/null +++ b/v3/steve/persondb.py @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ---- +# +# ### TBD: DOCCO +# + +from . import db + +import asfpy.db + + +class PersonDB: + + def __init__(self, db_fname): + ### switch to asfpy.db + self.db = db.DB(db_fname) + + self.c_add_person = self.db.add_statement( + '''INSERT INTO PERSON VALUES (?, ?, ?) + ON CONFLICT DO UPDATE SET + name=excluded.name, + email=excluded.email + ''') + self.c_delete_person = self.db.add_statement( + 'DELETE FROM PERSON WHERE pid = ?') + + self.q_person = self.db.add_query('person', + 'SELECT * FROM PERSON ORDER BY pid') + self.q_get_person = self.db.add_query('person', + 'SELECT * FROM PERSON WHERE pid = ?') + + def get_person(self, pid): + "Return NAME, EMAIL for Person identified by PID." + + # NEVER return person.salt + person = self.q_get_person.first_row((pid,)) + return person.name, person.email + + def add_person(self, pid, name, email): + "Add or update a Person designated by PID." + + # If we ADD, then SALT will be NULL. If we UPDATE, then it will not + # be touched (it should be NULL). + self.c_add_person.perform((pid, name, email,)) + + def delete_person(self, pid): + "Delete the Person designated by PID." + + # NOTE: if this person has ever been involved in a vote + # (ie. the PID exists in a "mayvote" row), then this will + # throw a referential integrity error. + # + ### maybe we just don't delete a person, ever? + + self.c_delete_person.perform((pid,)) + + def list_persons(self): + "Return ordered (PID, NAME, EMAIL) for each Person." + + # NOTE: the SALT column is omitted. It should never be exposed. + self.q_person.perform() + return [ row[:3] for row in self.q_person.fetchall() ] diff --git a/v3/test/check_coverage.py b/v3/test/check_coverage.py index 5ea39f5..611ccc7 100755 --- a/v3/test/check_coverage.py +++ b/v3/test/check_coverage.py @@ -44,6 +44,7 @@ def touch_every_line(): # Do the imports *WITHIN* the coverage test. import steve.election import steve.crypto + import steve.persondb eid = steve.election.new_eid() @@ -63,13 +64,14 @@ def touch_every_line(): _ = e.get_metadata() # while EDITABLE - e.add_person('alice', 'Alice', '[email protected]') - e.add_person('bob', None, '[email protected]') - e.add_person('carlos', 'Carlos', '[email protected]') - e.add_person('david', None, '[email protected]') - _ = e.list_persons() - e.delete_person('david') - _ = e.get_person('alice') + pdb = steve.persondb.PersonDB(TESTING_DB) + pdb.add_person('alice', 'Alice', '[email protected]') + pdb.add_person('bob', None, '[email protected]') + pdb.add_person('carlos', 'Carlos', '[email protected]') + pdb.add_person('david', None, '[email protected]') + _ = pdb.list_persons() + pdb.delete_person('david') + _ = pdb.get_person('alice') i1 = steve.crypto.create_id() i2 = steve.crypto.create_id() @@ -96,7 +98,7 @@ def touch_every_line(): e.add_voter('bob') e.add_voter('carlos', i1) - e.open() + e.open(pdb) _ = e.get_metadata() # while OPEN e.add_vote('alice', i1, 'y') e.add_vote('bob', i1, 'n') @@ -104,7 +106,7 @@ def touch_every_line(): e.add_vote('alice', i2, 'bc') e.add_vote('bob', i2, 'ad') _ = e.has_voted_upon('alice') - _ = e.is_tampered() + _ = e.is_tampered(pdb) e.close() _ = e.get_metadata() # while CLOSED
