Skip to content

Commit

Permalink
Merge branch 'master' of github.com:nursix/eden-asp
Browse files Browse the repository at this point in the history
  • Loading branch information
nursix committed Jul 19, 2022
2 parents 9cc5efb + 958f1c4 commit 1531df1
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 39 deletions.
10 changes: 6 additions & 4 deletions modules/core/aaa/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4355,14 +4355,16 @@ def s3_remove_role(self, user_id, group_id, for_pe=DEFAULT):
# Archive the memberships
for m in memberships:
deleted_fk = {"user_id": m.user_id,
"group_id": m.group_id}
if for_pe:
deleted_fk["pe_id"] = for_pe
"group_id": m.group_id,
}
if m.pe_id:
deleted_fk["pe_id"] = m.pe_id
deleted_fk = json.dumps(deleted_fk)
m.update_record(deleted = True,
deleted_fk = deleted_fk,
user_id = None,
group_id = None)
group_id = None,
)

# Update roles for current user if required
if self.user and str(user_id) == str(self.user.id):
Expand Down
4 changes: 3 additions & 1 deletion modules/core/msg/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,9 @@ def send_email(self,
attachments = [attachments]
from email.header import Header
for attachment in attachments:
filename = attachment.my_filename.decode("utf-8")
filename = attachment.my_filename
if isinstance(filename, bytes):
filename = filename.decode("utf-8")
header = Header('attachment; filename="%s"' % Header(filename, "utf-8").encode())
attachment.replace_header("Content-Disposition", header)

Expand Down
223 changes: 191 additions & 32 deletions modules/templates/RLPPTM/customise/org.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from ..helpers import workflow_tag_represent

SITE_WORKFLOW = ("MPAV", "HYGIENE", "LAYOUT", "STATUS", "PUBLIC")
SITE_WORKFLOW = ("MPAV", "HYGIENE", "LAYOUT", "STATUS", "PUBLIC", "DHASH")
SITE_REVIEW = ("MPAV", "HYGIENE", "LAYOUT")

# -------------------------------------------------------------------------
Expand Down Expand Up @@ -44,6 +44,36 @@ def add_org_tags():
),
)

# -------------------------------------------------------------------------
# Helper functions for approval workflows
#
def get_dhash(*values):
"""
Produce a data verification hash from the values
Args:
values: an (ordered) iterable of values
Returns:
the verification hash as string
"""

import hashlib
dstr = "#".join([str(v) if v else "***" for v in values])

return hashlib.sha256(dstr.encode("utf-8")).hexdigest().lower()

def reset_all(tags, value="N/A"):
"""
Set all given workflow tags to initial status
Args:
tags: the tag Rows
value: the initial value
"""

for tag in tags:
tag.update_record(value=value)

# -------------------------------------------------------------------------
def mgrinfo_opts():
"""
Expand Down Expand Up @@ -122,6 +152,7 @@ def update_mgrinfo(organisation_id):
reg_tag = httable.with_alias("reg_tag")
crc_tag = httable.with_alias("crc_tag")
scp_tag = httable.with_alias("scp_tag")
dsh_tag = httable.with_alias("dsh_tag")

join = ptable.on(ptable.id == htable.person_id)
left = [reg_tag.on((reg_tag.human_resource_id == htable.id) & \
Expand All @@ -133,17 +164,28 @@ def update_mgrinfo(organisation_id):
scp_tag.on((scp_tag.human_resource_id == htable.id) & \
(scp_tag.tag == "SCP") & \
(scp_tag.deleted == False)),
dsh_tag.on((dsh_tag.human_resource_id == htable.id) & \
(dsh_tag.tag == "DHASH") & \
(dsh_tag.deleted == False)),
]

query = (htable.organisation_id == organisation_id) & \
(htable.org_contact == True) & \
(htable.status == 1) & \
(htable.deleted == False)

rows = db(query).select(ptable.pe_id,
rows = db(query).select(htable.id,
ptable.pe_id,
ptable.first_name,
ptable.last_name,
ptable.date_of_birth,
dsh_tag.id,
dsh_tag.value,
reg_tag.id,
reg_tag.value,
crc_tag.id,
crc_tag.value,
scp_tag.id,
scp_tag.value,
join = join,
left = left,
Expand All @@ -152,39 +194,73 @@ def update_mgrinfo(organisation_id):
# No managers selected
status = "N/A"
else:
# Managers selected => check completeness of data/documentation
# Managers selected => check data/documentation
status = "REVISE"
ctable = s3db.pr_contact

for row in rows:

# Check that all documentation tags are set as approved
doc_tags = True
for t in (reg_tag, crc_tag, scp_tag):
if row[t.value] != "APPROVED":
doc_tags = False
break
if not doc_tags:
continue

# Check DoB
if not row.pr_person.date_of_birth:
continue

# Check that there is at least one contact details
# of phone/email type
query = (ctable.pe_id == row.pr_person.pe_id) & \
(ctable.contact_method in ("SMS", "HOME_PHONE", "WORK_PHONE", "EMAIL")) & \
(ctable.value != None) & \
(ctable.deleted == False)
contact = db(query).select(ctable.id, limitby=(0, 1)).first()
if not contact:
continue

# All that given, the manager-data status of the organisation
# can be set as complete
status = "COMPLETE"
break
person = row.pr_person
dob = person.date_of_birth
vhash = get_dhash(person.first_name,
person.last_name,
dob.isoformat() if dob else None,
)
doc_tags = [row[t._tablename] for t in (reg_tag, crc_tag, scp_tag)]

# Do we have a verification hash (after previous approval)?
dhash = row.dsh_tag
verified = bool(dhash.id)
accepted = True

# Check completeness/integrity of data

# Must have DoB
if accepted and not dob:
# No documentation can be approved without DoB
reset_all(doc_tags)
accepted = False

# Must have at least one contact detail of the email/phone type
if accepted:
query = (ctable.pe_id == row.pr_person.pe_id) & \
(ctable.contact_method in ("SMS", "HOME_PHONE", "WORK_PHONE", "EMAIL")) & \
(ctable.value != None) & \
(ctable.deleted == False)
contact = db(query).select(ctable.id, limitby=(0, 1)).first()
if not contact:
accepted = False

# Do the data (still) match the verification hash?
if accepted and verified:
if dhash.value != vhash:
if current.auth.s3_has_role("ORG_GROUP_ADMIN"):
# Data changed by OrgGroupAdmin => update hash
# (authorized change has no influence on approval)
dhash.update_record(value=vhash)
else:
# Data changed by someone else => previous
# approval of documentation no longer valid
reset_all(doc_tags)
accepted = False

# Check approval status for documentation
if accepted and all(tag.value == "APPROVED" for tag in doc_tags):
if not verified:
# Set the verification hash
dsh_tag.insert(human_resource_id = row[htable.id],
tag = "DHASH",
value = vhash,
)

# If at least one record is acceptable, the manager-data
# status of the organisation can be set as complete
status = "COMPLETE"
else:
# Remove the verification hash, if any (unapproved records
# do not need to be integrity-checked)
if verified:
dhash.delete_record()

# Update or add MGRINFO-tag with status
ottable = s3db.org_organisation_tag
Expand Down Expand Up @@ -828,6 +904,8 @@ def add_facility_default_tags(facility_id, approve=False):
default = "REVIEW"
else:
default = "APPROVED" if public else "REVIEW"
elif tag == "DHASH":
default = None
else:
default = "APPROVED" if public else "REVISE"
ttable.insert(site_id = site_id,
Expand Down Expand Up @@ -878,6 +956,72 @@ def set_facility_code(facility_id):

return code

# -----------------------------------------------------------------------------
def facility_approval_hash(tags, site_id, location_id):
"""
Compute and check the verification hash for facility details
Args:
tags: the current facility workflow tags (including existing hash)
site_id: the facility site ID
location_id: the facility location ID
Returns:
tuple (update, vhash), where
- update is a dict with workflow tag updates
- vhash is the computed verification hash
Notes:
- the verification hash encodes certain facility details, so
if those details are changed after approval, then the hash
becomes invalid and any previous approval is overturned
(=reduced to review-status)
- if the user is OrgGroupAdmin or Admin, the approval workflow
status is kept as-is (i.e. Admins can change details without
that impacting the current workflow status)
"""

db = current.db
s3db = current.s3db

dhash = tags.get("DHASH")
approved = tags.get("STATUS") == "APPROVED"

# Extract the location, and compute the hash
ltable = s3db.gis_location
query = (ltable.id == location_id) & \
(ltable.deleted == False)
location = db(query).select(ltable.id,
ltable.parent,
ltable.addr_street,
ltable.addr_postcode,
limitby = (0, 1),
).first()
if location:
vhash = get_dhash(location.id,
location.parent,
location.addr_street,
location.addr_postcode,
)
else:
vhash = get_dhash(None, None, None, None)

if approved and dhash and dhash != vhash and \
not current.auth.s3_has_role("ORG_GROUP_ADMIN"):
update = {"PUBLIC": "N"}
status = "REVIEW"
for t in SITE_REVIEW:
value = tags.get(t)
if value == "APPROVED":
update[t] = "REVIEW"
elif value == "REVISE":
status = "REVISE"
update["STATUS"] = status
else:
update = None

return update, vhash

# -----------------------------------------------------------------------------
def facility_approval_status(tags, mgrinfo):
"""
Expand Down Expand Up @@ -966,6 +1110,7 @@ def facility_approval_workflow(site_id):
query = (ftable.site_id == site_id)
facility = db(query).select(ftable.id,
ftable.organisation_id,
ftable.location_id,
ottable.value,
left = left,
limitby = (0, 1),
Expand Down Expand Up @@ -1001,8 +1146,22 @@ def facility_approval_workflow(site_id):
facility_approval_workflow(site_id)
return

# Update tags
update, notify = facility_approval_status(tags, mgrinfo)
# Verify record integrity and compute the verification hash
update, vhash = facility_approval_hash(tags,
site_id,
facility.org_facility.location_id,
)
notify = False
if not update:
# Integrity check okay => proceed to workflow status
update, notify = facility_approval_status(tags, mgrinfo)

# If the record would be approved, add the verification hash to the
# update, otherwise reset it to None (=>unapproved records do not need
# to be integrity-checked)
status = update["STATUS"] if "STATUS" in update else tags.get("STATUS")
update["DHASH"] = vhash if status == "APPROVED" else None

for row in rows:
key = row.tag
if key in update:
Expand Down
2 changes: 1 addition & 1 deletion static/scripts/S3/s3.ui.datatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -1742,7 +1742,7 @@

if (oSetting) {

var args = 'id=' + self.tableid,
var args = 'id=' + self.tableID,
sSearch = oSetting.oPreviousSearch.sSearch,
aaSort = oSetting.aaSorting,
aaSortFixed = oSetting.aaSortingFixed,
Expand Down
2 changes: 1 addition & 1 deletion static/scripts/S3/s3.ui.datatable.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1531df1

Please sign in to comment.