Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch audit-uname Excluding Merge-Ins
This is equivalent to a diff from 75bacf6ac9 to f0726c4306
|
2022-02-04
| ||
| 17:49 | Merged the audit-uname branch - audit username read from a custom variable instead of a temporary table. check-in: baf3590749 user: ppdb tags: trunk | |
| 11:47 | Added test to check that the logged in username is set in an UPDATE audit record. Closed-Leaf check-in: f0726c4306 user: ppdb tags: audit-uname | |
| 08:38 | Use a custom variable to pass the logged in username to the audit procedure instaed of a temporary table. check-in: ea1c578197 user: ppdb tags: audit-uname | |
| 08:28 | Create new branch named "audit-uname" check-in: 1ea7e3fbb4 user: ppdb tags: audit-uname | |
| 08:26 | Update messages for the 2022-01-31 tables check-in: 75bacf6ac9 user: ppdb tags: trunk | |
|
2022-02-03
| ||
| 14:55 | Updated data conversion for the 20220131 tables. check-in: 6b2f530baf user: ppdb tags: trunk | |
Changes to ppdb/const.py.
| ︙ | ︙ | |||
24 25 26 27 28 29 30 | """ The absolute path to the package. Initialised at app startup by ppd.py """ JINJA_ENV = None """ The Jinja2 templating environment. Initialised at app startup by ppd.py """ VERSION = '0.93.0' VERSION__doc = """ The PPDB code version """ | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | """ The absolute path to the package. Initialised at app startup by ppd.py """ JINJA_ENV = None """ The Jinja2 templating environment. Initialised at app startup by ppd.py """ VERSION = '0.93.0' VERSION__doc = """ The PPDB code version """ SCHEMA = '34.0' SCHEMA__doc = """ The PPDB database schema version """ BREED_ID_CHAR = 'Z' USER_FUNC_MEMSEC = 'M' USER_FUNC_REGSEC = 'R' |
| ︙ | ︙ |
Changes to ppdb/lib/pglib.py.
| ︙ | ︙ | |||
203 204 205 206 207 208 209 |
cherrypy.thread_data.dbconn.close()
cherrypy.thread_data.dbconn = None
db_connect()
curs = cherrypy.thread_data.dbconn.cursor()
else:
raise DBAPIError(inspect.stack()[0][3], "Connection retry failed")
curs.execute("SET search_path TO %s,public;" % schema)
| < | | 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
cherrypy.thread_data.dbconn.close()
cherrypy.thread_data.dbconn = None
db_connect()
curs = cherrypy.thread_data.dbconn.cursor()
else:
raise DBAPIError(inspect.stack()[0][3], "Connection retry failed")
curs.execute("SET search_path TO %s,public;" % schema)
curs.execute("set ppdb.username to 'nologin';")
cherrypy.thread_data.dbconn.commit()
except psycopg2.OperationalError as exc:
raise DBAPIOperationalError(inspect.stack()[0][3], exc)
except psycopg2.Error as exc:
raise DBAPIError(inspect.stack()[0][3], exc)
except psycopg2.Warning as exc:
raise DBAPIWarning(inspect.stack()[0][3], exc)
|
| ︙ | ︙ |
Changes to ppdb/lib/sheep_schema.py.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 |
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# Disable constant name case warnings
# pylint: disable=C0103
# The schema version
| | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# Disable constant name case warnings
# pylint: disable=C0103
# The schema version
schema_version = "34.0"
#: The table creation order - parent and look-up tables must be created and populated
#: before their child tables.
table_list = ["version", "audit_history", "mem_dates",
"pmt_method", "mem_source", "nonren_reason", "phone_type", "role_type", "county",
"adj_county", "country", "region", "in_region", "post_area", "adj_post_area",
"mem_class", "member", "person", "mem_pmts", "privacy_options",
|
| ︙ | ︙ | |||
133 134 135 136 137 138 139 |
if { [info exists NEW(change_reason)] } {
set reason $NEW(change_reason)
} else {
set reason "New Record"
}
spi_exec "INSERT INTO audit_history VALUES (DEFAULT, '$TG_op',
| | | 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
if { [info exists NEW(change_reason)] } {
set reason $NEW(change_reason)
} else {
set reason "New Record"
}
spi_exec "INSERT INTO audit_history VALUES (DEFAULT, '$TG_op',
LOCALTIMESTAMP(3), (SELECT current_setting('ppdb.username')), '$reason',
'$TG_table_name', '$NEW($1)', '$row_select_1', '{' || '$old_data' || '}',
'{' || '$new_data' || '}');"
return SKIP
} else {
if {$TG_op eq "UPDATE" || $TG_op eq "DELETE"} {
if {$TG_op eq "UPDATE"} {
set pairs ""
|
| ︙ | ︙ | |||
165 166 167 168 169 170 171 |
set old_data [join $pairs ","]
if {$2 ne ""} {
set row_select_1 $OLD($2)
} else {
set row_select_1 ""
}
spi_exec "INSERT INTO audit_history VALUES (DEFAULT, '$TG_op',
| | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
set old_data [join $pairs ","]
if {$2 ne ""} {
set row_select_1 $OLD($2)
} else {
set row_select_1 ""
}
spi_exec "INSERT INTO audit_history VALUES (DEFAULT, '$TG_op',
LOCALTIMESTAMP(3), (SELECT current_setting('ppdb.username')), '$reason',
'$TG_table_name', '$OLD($1)', '$row_select_1',
'{' || '$old_data' || '}', '{' || '$new_data' || '}');"
return SKIP;
} else {
return SKIP;
}
}
|
| ︙ | ︙ |
Changes to ppdb/lib/tools.py.
| ︙ | ︙ | |||
179 180 181 182 183 184 185 |
method_roles = userlib.get_url_roles(url_roles, cherrypy.request.method)
for role in login_roles:
if role in method_roles:
cherrypy.request.loginroles = login_roles
with pglib.get_conn():
with pglib.cursor() as curs:
| < | | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
method_roles = userlib.get_url_roles(url_roles, cherrypy.request.method)
for role in login_roles:
if role in method_roles:
cherrypy.request.loginroles = login_roles
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("set ppdb.username to ?;", (username,))
break
else:
raise util.APIError('403 Forbidden',
'Your login does not allow you to do that')
else:
cherrypy.request.loginroles = userlib.get_user_roles(cherrypy.request.login)
elif cherrypy.request.path_info != '/':
|
| ︙ | ︙ |
Changes to ppdb/test/__init__.py.
| ︙ | ︙ | |||
74 75 76 77 78 79 80 |
if refresh:
refresh_test_database(dbname)
pglib.initialise(dbconf, False)
pglib.make_db_connection(dbparm)
with pglib.get_conn():
with pglib.cursor() as curs:
| < < < | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
if refresh:
refresh_test_database(dbname)
pglib.initialise(dbconf, False)
pglib.make_db_connection(dbparm)
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("set ppdb.username to 'tester';")
print("\nTesting %s with PostgreSQL database %s" % (classname, dbname))
def start_ppd(test_pg_pool=False, test_db=True, refresh=False, auth=True, app='db'):
""" Start PPD using the CherryPy web server
- Optionally modify the configuration from config to use the test database.
|
| ︙ | ︙ | |||
244 245 246 247 248 249 250 |
""" Return lists of the available areas sorted the way Javascript does"""
return make_request(cls.host, 'GET', "/test/rptdata/jsareas", 200)['data']
def set_cp_domainbase(domainbase):
""" Set domainbase to CherryPy's config """
cherrypy.config['domainbase'] = domainbase
| < < < < < < < | 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
""" Return lists of the available areas sorted the way Javascript does"""
return make_request(cls.host, 'GET', "/test/rptdata/jsareas", 200)['data']
def set_cp_domainbase(domainbase):
""" Set domainbase to CherryPy's config """
cherrypy.config['domainbase'] = domainbase
class SelWeb():
""" Class to contain custom methods """
TEST_MEM_NO = ""
TEST_PERS_INFO = ""
TEST_PERS_EMAIL = ""
|
| ︙ | ︙ |
Changes to ppdb/test/appinit_test.py.
| ︙ | ︙ | |||
64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
else:
# This is required to run tests on HTTP
self.uri_tree._cp_config['tools.sessions.secure'] = False
# Add new test branch. These uris are used to get test data and delete test
# resources
self.uri_tree.test = testuri.Test(self.appconf)
self.uri_tree.test.sheep = testuri.Sheep(self.appconf)
self.uri_tree.test.transfer = testuri.Transfer(self.appconf)
self.uri_tree.test.member = testuri.Member(self.appconf)
self.uri_tree.test.person = testuri.Person(self.appconf)
self.uri_tree.test.meminfo = testuri.MemInfo(self.appconf)
self.uri_tree.test.role = testuri.Role(self.appconf)
self.uri_tree.test.rptdata = testuri.ReportData(self.appconf)
| > | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
else:
# This is required to run tests on HTTP
self.uri_tree._cp_config['tools.sessions.secure'] = False
# Add new test branch. These uris are used to get test data and delete test
# resources
self.uri_tree.test = testuri.Test(self.appconf)
self.uri_tree.test.audit = testuri.Audit(self.appconf)
self.uri_tree.test.sheep = testuri.Sheep(self.appconf)
self.uri_tree.test.transfer = testuri.Transfer(self.appconf)
self.uri_tree.test.member = testuri.Member(self.appconf)
self.uri_tree.test.person = testuri.Person(self.appconf)
self.uri_tree.test.meminfo = testuri.MemInfo(self.appconf)
self.uri_tree.test.role = testuri.Role(self.appconf)
self.uri_tree.test.rptdata = testuri.ReportData(self.appconf)
|
| ︙ | ︙ |
Changes to ppdb/test/selweb/test_logon.py.
| ︙ | ︙ | |||
58 59 60 61 62 63 64 |
""" CC - Click Logout. Check the user is logged out and the root page is displayed """
self.click("sidenav-open-btn")
elem = self.wait_for_link_text("Logout User 'admin'")
elem.click()
self.wait_for_element_by_id("title")
self.assert_equal(self.get_text("title"), "Please log in to continue")
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | > | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
""" CC - Click Logout. Check the user is logged out and the root page is displayed """
self.click("sidenav-open-btn")
elem = self.wait_for_link_text("Logout User 'admin'")
elem.click()
self.wait_for_element_by_id("title")
self.assert_equal(self.get_text("title"), "Please log in to continue")
def test_DA(self): # DA - Get login again as admin
self.set_input_field("uname", "admin", True)
self.set_input_field("passwd", "admin#", True)
self.click("loginbtn")
self.wait_for_element_by_id("sidenav-open-btn")
def test_DB(self): # DB - Open the member data page
self.wait_for_element_by_id("sidenav-open-btn").click()
elem = self.wait_for_link_text("Membership").click()
elem = self.wait_for_link_text("Member Data").click()
def test_DC(self): # DC - Make a change to the payments
self.wait_for_element_by_id("edit_pmts_btn").click()
self.set_input_field("pmt_ref", "test reference", True)
self.click("pmts_submit")
self.wait_for_visibility("payments_dialog", False)
def test_DD(self): # DD Check that the audit record user is correct
resp = test.make_request(self.host, 'GET', "/test/audit/mem_pmts", 200)
self.assert_equal('admin', resp["data"])
def test_DE(self): # DE logout to return to the login page
self.click("sidenav-open-btn")
elem = self.wait_for_link_text("Logout User 'admin'")
elem.click()
self.wait_for_element_by_id("title")
self.assert_equal(self.get_text("title"), "Please log in to continue")
def test_EA(self):
""" EA - Click the 'reset password' link -"""
elem = self.wait_for_element_by_selector(".pseudo_link")
self.assert_equal("Forgotten your password?",
elem.get_attribute("innerHTML").lstrip())
elem.click()
self.wait_for_visibility("reset_box", True)
self.wait_for_element_by_id("reset_title")
self.assert_equal(self.get_text("reset_title"), "Request Password Reset")
def test_EB(self):
""" EB - Test a user id with no login record """
self.set_input_field("username_field", "2098", True)
self.click("resetpw_submit")
self.wait_for_text("resetpw_msg", "User 2098 is not authorised to log in")
def test_EC(self):
""" EC - Test a missing email """
self.set_input_field("username_field", '424', True)
self.click("resetpw_submit")
self.wait_for_text("resetpw_msg", "You must specify an email address")
def test_ED(self):
""" ED - Test a bad email address """
self.set_input_field("username_field", '424', True)
self.set_input_field("mem_email_addr", "womble@localhost", True)
self.click("resetpw_submit")
self.wait_for_text("resetpw_msg", "email address womble@localhost is not"
" registered to user " + '424')
def test_EE(self):
""" EE - Test a good user and email address """
# Empty the local mail file
#~ os.system('> /var/mail/" + self.logname')
self.set_input_field("username_field", '424', True)
self.set_input_field("mem_email_addr", self.logname + "@localhost", True)
self.click("resetpw_submit")
self.wait_for_visibility("login_box", True)
self.wait_for_text("login_msg", "An email has been sent to {}@localhost with "
"instructions<br>for resetting the password for"
" user {}".format(self.logname, '424'))
def test_FA(self):
""" FA - Read the link from the email and go to it """
with open("/var/mail/" + self.logname, "r") as mail:
for line in mail:
#~ for line in reversed(list(mail)):
ix = line.find("localhost:8")
if ix != -1:
break
url = "http:{}".format(line[ix:])
self.driver.get("http:{}".format(line[ix:]))
self.wait_for_title("Shetland Sheep Society - Database - Change Password")
self.assert_equal(self.get_text("title"), "Change Password for '424'")
def test_FB(self):
""" FB - Enter a poor password and ensure the correct message is displayed """
self.set_input_field("new_passwd", "", True)
self.wait_for_text("score", "You must enter a new password!")
self.assert_equal(self.get_class("score"), "err_msg")
self.set_input_field("new_passwd", "Somborne", True)
self.wait_for_text_starts_with("score", "Strength: 0. Add another word or two. U")
self.assert_equal(self.get_class("score"), "err_msg")
def test_FC(self):
""" FC - Enter a good password and ensure the correct message is displayed """
self.set_input_field("new_passwd", "sombornebellalamb", True)
self.wait_for_text("score", "Strength: 3 - strong enough.")
self.assert_equal(self.get_class("score"), "ok_msg")
def test_FD(self):
""" FD - test the conf_passwd field """
self.set_input_field("conf_passwd", "", True)
self.wait_for_text("score", "The passwords do not match")
self.assert_equal(self.get_class("score"), "err_msg")
self.set_input_field("conf_passwd", "sombornebelllamb", True)
self.wait_for_text("score", "The passwords do not match")
self.assert_equal(self.get_class("score"), "err_msg")
self.set_input_field("conf_passwd", "sombornebellalamb", True)
self.wait_for_text("score", "The passwords match")
self.assert_equal(self.get_class("score"), "ok_msg")
def test_FE(self):
""" FE: Get a demo password and paste into the new_passwd and conf_passwd fields """
self.click("genpw_btn")
self.pause()
newpw = self.get_text("suggested_pw")
print("suggested password: {}".format(newpw))
self.set_input_field("new_passwd", newpw, True)
self.wait_for_text("score", "Strength: 3 - strong enough.")
self.assert_equal(self.get_class("score"), "ok_msg")
self.set_input_field("conf_passwd", newpw, True)
self.wait_for_text("score", "The passwords match")
self.assert_equal(self.get_class("score"), "ok_msg")
def test_FF(self):
""" FF - Change the password """
self.click("link_change_btn")
self.wait_for_visibility("info_dialog", True)
self.check_info_dialog("Password has been changed")
self.wait_for_visibility("login_box", True)
@attr('firefox', 'local', 'logon')
class TestLoginPGFF(TCases):
@classmethod
def setup_class(cls):
test.setup_class(cls, 'login', 'Firefox', refresh=True, auth=True)
|
| ︙ | ︙ |
Changes to ppdb/test/testuri.py.
| ︙ | ︙ | |||
50 51 52 53 54 55 56 |
@cherrypy.expose
class Test(TestBase):
""" The test URI """
def GET(self):
""" A GET to the Test URI """
return "You requested some test function"
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
@cherrypy.expose
class Test(TestBase):
""" The test URI """
def GET(self):
""" A GET to the Test URI """
return "You requested some test function"
@cherrypy.expose
class Audit(TestBase):
""" The test URI """
@cherrypy.tools.json_out()
def GET(self, tblname=None):
""" Return the 'app_user' from the latest audit record for the specified table """
cherrypy.response.headers['content-type'] = 'application/json'
status = xhrc.XHR_NOERR
if not tblname:
cherrypy.response.status = 400
return {"rcode": xhrc.XHR_ERROR, "data": "A table name must be specified"}
with pglib.get_conn(True):
with pglib.cursor() as curs:
curs.execute("select app_user from audit_history where tablename = ? "
"order by action_tstamp desc limit 1;", (tblname,))
row = curs.fetchone()
if not row:
cherrypy.response.status = 400
return {"rcode": xhrc.XHR_ERROR,
"data": f"Table {tblname} has no audit records"}
cherrypy.response.status = 200
return {"rcode": xhrc.XHR_NOERR, "data": row[0]}
@cherrypy.expose
class Sheep(TestBase):
""" The Sheep test URI """
@cherrypy.tools.json_out()
def GET(self, regn_no=None, move=''):
""" Return the regn_no of the sheep described by 'regn_no' and 'move' """
|
| ︙ | ︙ |
Changes to ppdb/test/utests/test_audit.py.
| ︙ | ︙ | |||
71 72 73 74 75 76 77 |
def clear_history():
""" Clear the history table """
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("delete from audit_history;")
| < < < < < < | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
def clear_history():
""" Clear the history table """
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("delete from audit_history;")
def drop_tmp_tables():
""" Drop the test tables """
with pglib.get_conn():
with pglib.cursor() as curs:
for tblname in AUDIT_TEST_TABLES:
curs.execute("drop table if exists %s;" % tblname)
|
| ︙ | ︙ | |||
171 172 173 174 175 176 177 |
@attr('A')
def test_aa1(self):
""" AA1 - INSERT to a table with autoincrement key and set_ts trigger """
TCases.old_reason = 'test_aa1'
TCases.gts_min = util.isots_now(True)
with pglib.get_conn():
with pglib.cursor() as curs:
| | | | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
@attr('A')
def test_aa1(self):
""" AA1 - INSERT to a table with autoincrement key and set_ts trigger """
TCases.old_reason = 'test_aa1'
TCases.gts_min = util.isots_now(True)
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("select current_setting('ppdb.username');")
row = curs.fetchone()
print(row[0])
with pglib.get_conn():
with pglib.cursor() as curs:
curs.execute("insert into audit_test_a (field_1, field_2, "
"field_3, change_reason, last_changed) "
"values(3, 'value', 'extra', ?, '2012-10-09 00:01:00');",
(self.old_reason,))
TCases.gts_max = util.isots_now(True)
|
| ︙ | ︙ |
Changes to ppdrole.py.
| ︙ | ︙ | |||
49 50 51 52 53 54 55 |
if not row:
print(f"No such role '{role}'")
return
with dbconn:
with dbconn.cursor() as curs:
| < < < | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
if not row:
print(f"No such role '{role}'")
return
with dbconn:
with dbconn.cursor() as curs:
curs.execute("set ppdb.username to 'super';")
if action == 'add':
action = 'added'
curs.execute("insert into user_role values "
"(%s, %s, 'superuser', %s);", (user, role, util.isots_now()))
else:
action = 'deleted'
curs.execute("delete from user_role where username = %s and "
|
| ︙ | ︙ |