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 Nov 30, 2022
2 parents 3a8ad6e + 3ddd33b commit 3ab120d
Show file tree
Hide file tree
Showing 9 changed files with 552 additions and 11 deletions.
2 changes: 2 additions & 0 deletions languages/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -6408,11 +6408,13 @@
'Vehicles are assets with some extra details.': 'Fahrzeuge sind Anlagen, die mit einigen speziellen Funktionen ausgestattet sind',
'Vehicles': 'Fahrzeuge',
'Venue': 'Örtlichkeit',
'Verification Code': 'Verifizierungscode',
'Verification Details': 'Verifizierungsdetails',
'Verification Email sent - please check your email to validate. If you do not receive this email please check your junk email or spam filters': 'Bestätigungs-Email gesendet - Bitte prüfen Sie ihren Posteingang zur Bestätigung. Falls Sie die Email nicht erhalten haben, überprüfen Sie bitte Ihrem SPAM-Ordner bzw. -Filter.',
'Verification Status': 'Prüfstatus',
'Verified': 'Verifiziert',
'Verified?': 'Geprüft?',
'Verify Commission': 'Beauftragung prüfen',
'Verify Password': 'Passwort-Kontrolle',
'Verify password': 'Passwortprüfung',
'Very Good': 'Sehr gut',
Expand Down
277 changes: 272 additions & 5 deletions modules/templates/RLPPTM/commission.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import base64
import datetime
import hashlib
import json
import os
import secrets

Expand All @@ -46,11 +47,11 @@
from reportlab.lib.utils import ImageReader
from reportlab.platypus import BaseDocTemplate, Frame, KeepTogether, PageTemplate, Paragraph

from gluon import current
from gluon import current, BUTTON, DIV, INPUT, URL
from core import CRUDMethod, CustomController, ICON, JSONERRORS, S3QRInput, \
s3_str

from core import s3_str

# Fonts we use in this layout
# Fonts used in CommissionDocTemplate
NORMAL = "Helvetica"
BOLD = "Helvetica-Bold"

Expand Down Expand Up @@ -94,6 +95,7 @@ def commission(self):
table.date,
table.end_date,
table.status,
table.status_date,
table.cnote,
table.vhash,
limitby = (0, 1),
Expand Down Expand Up @@ -356,7 +358,7 @@ def pdf(self):
# -------------------------------------------------------------------------
def issue_note(self):
"""
Issue a commissioning note as PDF document in the record, and
Issues a commissioning note as PDF document in the record, and
store the hash for verification of the document
"""

Expand All @@ -376,6 +378,104 @@ def issue_note(self):
vhash = self.vhash,
)

# -------------------------------------------------------------------------
@staticmethod
def parse_vcode(vcode):
"""
Parses a commission note verification code (counterpart to vcode)
Args:
vcode - the verification code (str|bytes)
Returns:
Details of the code for verification purposes, including
verification hash, as dict
Raises:
ValueError - for invalid verification codes
"""

msg = "invalid verification code"

if isinstance(vcode, str):
vcode = vcode.encode("utf-8")

try:
parsed = base64.b64decode(vcode).decode("utf-8")
except Exception as e:
raise ValueError(msg) from e

parsed = parsed.split("|")
if not parsed or len(parsed) != 5:
raise ValueError(msg)

parse_date = current.calendar.parse_date
try:
start, end = parse_date(parsed[2]), parse_date(parsed[3])
except Exception:
start, end = None, None

try:
uuid = UUID(parsed[1]).urn
except ValueError:
uuid = parsed[1]

return {"provider_id": parsed[0],
"uuid": uuid,
"start": start,
"end": end,
"vhash": hashlib.sha256(vcode).hexdigest().lower()
}

# -------------------------------------------------------------------------
def json(self, represent=False):
"""
Returns the commission details as JSON-serializable dict,
for use in verification methods
Args:
represent: include represented data
Returns:
dict
"""

record = self.commission

dtfmt = lambda dt: dt.isoformat() if dt else "--"
output = {"data": {"organisation": self.organisation_name,
"organisation_id": self.provider_id,
"start": dtfmt(record.date),
"end": dtfmt(record.end_date),
"status": record.status,
"status_date": dtfmt(record.status_date),
},
}

if represent:
represented = {"organisation": self.organisation_name,
"organisation_id": self.provider_id,
}

# Record details
fields = {"start": "date",
"end": "end_date",
"status": "status",
"status_date": "status_date",
}
table = current.s3db.org_commission
for k, fn in fields.items():
field = table[fn]
value = field.represent(record[fn])
if hasattr(value, "xml") and callable(value.xml):
value = s3_str(value.xml())
else:
value = s3_str(value) if value else "--"
represented[k] = value
output["repr"] = represented

return output

# =============================================================================
class CommissionDocTemplate(BaseDocTemplate):
"""
Expand Down Expand Up @@ -795,4 +895,171 @@ def draw_page_number(self, page_count):
"%d / %d" % (self._pageNumber, page_count),
)

# =============================================================================
class VerifyCommission(CRUDMethod):
""" Method to verify commission note signatures """

def apply_method(self, r, **attr):
"""
Handles requests for this method
Args:
r: the CRUDRequest instance
attr: controller attributes
"""

if r.http == "GET":
output = self.form()

elif r.http == "POST":
if r.representation == "json":
output = self.verify(r, **attr)
else:
r.error(415, current.ERROR.BAD_FORMAT)
else:
r.error(405, current.ERROR.BAD_METHOD)

return output

# -------------------------------------------------------------------------
@staticmethod
def form():
"""
Provides an interactive UI for commission note signature
verification
Returns:
QR widget for the view
Note:
- uses templates/RLPPTM/views/verify.html
- injects the required JS for QR scanning
"""

ajax_url = URL(c="org", f="facility", args=["verify.json"], vars={"repr": 1})

hidden_input = INPUT(_type = "hidden",
_name = "vcode",
_id = "vcode",
data = {"url": ajax_url},
)
scan_button = BUTTON(ICON("fa fa-qrcode"),
"",
_title = current.T("Scan QR Code"),
_type = "button",
_class = "primary button qrscan-btn wide-button",
)
widget = DIV(hidden_input, scan_button, _class="qrinput")
S3QRInput.inject_script("vcode", {})

CustomController._view("RLPPTM", "verify.html")
return {"widget": widget}

# -------------------------------------------------------------------------
def verify(self, r, **attr):
"""
Handles requests to verify a commission note signature
Args:
r: the CRUDRequest instance
attr: controller attributes
Returns:
a JSON response, format
{"signature": VALID|INVALID|RECORD NOT FOUND|RECORD DELETED|PROVIDER MISMATCH,
"data": {
"organisation": organisation name,
"organisation_id": provider ID (value of the OrgID tag),
"start": start date of commission,
"end": end date of commission,
"status": current commission status,
"status_date": date of the status,
},
"repr": {
- same as data, but with represented/localized values
- only with ?repr=1 URL parameter
}
}
"""

# Read the body JSON of the request
# Expected format {"vcode": "...", "id": "..."}, with id optional
body = r.body
body.seek(0)
try:
s = body.read().decode("utf-8")
except (ValueError, AttributeError, UnicodeDecodeError):
r.error(400, current.ERROR.BAD_REQUEST)
try:
ref = json.loads(s)
except JSONERRORS:
r.error(400, current.ERROR.BAD_REQUEST)

vcode = ref.get("vcode")
if not vcode:
r.error(400, current.ERROR.BAD_REQUEST)

provider_id = ref.get("id")
represent = r.get_vars.get("repr") == "1"

response = current.response
if response:
response.headers["Content-Type"] = "application/json; charset=utf-8"

return json.dumps(self._verify(vcode,
provider_id = provider_id,
represent = represent,
),
separators = (",", ":"),
ensure_ascii = False,
)

# -------------------------------------------------------------------------
@staticmethod
def _verify(vcode, provider_id=None, represent=False):
"""
Verifies a commission note signature
Args:
vcode: the signature (QR code)
provider_id: verify in the context of this provider
represent: return represented/localized data
Returns:
the verification result including commission data,
as a JSON-serializable dict
"""

commission, output = None, {}
try:
parsed = ProviderCommission.parse_vcode(vcode)
except ValueError:
signature = "INVALID"
else:
table = current.s3db.org_commission
record = current.db(table.uuid == parsed["uuid"]).select(table.id,
table.deleted,
limitby = (0, 1),
).first()
if not record:
signature = "RECORD NOT FOUND"
elif record.deleted:
signature = "RECORD DELETED"
else:
signature = "VALID"
commission = ProviderCommission(record.id)

if commission:
# Verify the hash
if commission.commission.vhash != parsed["vhash"]:
signature = "INVALID"
elif provider_id and provider_id != commission.provider_id:
signature = "PROVIDER MISMATCH"
else:
output.update(commission.json(represent=represent))

output["signature"] = signature

return output

# END =========================================================================
7 changes: 7 additions & 0 deletions modules/templates/RLPPTM/customise/org.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,13 @@ def org_facility_resource(r, tablename):
report_options = report_options,
)

# Custom method to verify commissions
from ..commission import VerifyCommission
s3db.set_method("org_facility",
method = "verify",
action = VerifyCommission,
)

# Custom method to produce KV report
from ..helpers import TestFacilityInfo
s3db.set_method("org_facility",
Expand Down
Loading

0 comments on commit 3ab120d

Please sign in to comment.