Skip to content

[FIX] - Update to match Orgbook Publisher Changes #3312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 64 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
03e7aa8
data typing issues and clean up
Jsyro Nov 6, 2024
962f5e9
better logging
Jsyro Nov 7, 2024
1af241d
better logging of errors
Jsyro Nov 7, 2024
66e22c4
list of tuples
Jsyro Nov 7, 2024
4537f18
add celery decorator
Jsyro Nov 7, 2024
fb883b5
rename var to match envvar
Jsyro Nov 7, 2024
6f49d48
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 7, 2024
687e03e
credential per mine_party_appt produces duplicates.
Jsyro Nov 7, 2024
6cd313b
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 7, 2024
be179c2
wrong class
Jsyro Nov 7, 2024
2652d0f
don't invoke if being used by celery
Jsyro Nov 7, 2024
7ccd3c0
query must excluded deleted permit_amendments
Jsyro Nov 7, 2024
6ce7067
add check if cred not created
Jsyro Nov 8, 2024
a6e44a3
improve logging of problem states
Jsyro Nov 8, 2024
a95574b
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 8, 2024
4ae3465
make date a date, formatting
Jsyro Nov 8, 2024
9e89270
revered received date, update issue_Date validator
Jsyro Nov 8, 2024
6758491
all datetimes, never dates
Jsyro Nov 8, 2024
f68c378
typo
Jsyro Nov 8, 2024
5f23ae3
names are important
Jsyro Nov 8, 2024
6f05801
only use datetime objects, no date objects
Jsyro Nov 8, 2024
410ddcc
better typing
Jsyro Nov 8, 2024
28b35f7
ensure second is non-zero for datetime
Jsyro Nov 8, 2024
27fff76
oops
Jsyro Nov 8, 2024
2a4a118
brackets matter
Jsyro Nov 8, 2024
9bd7762
what is happening?
Jsyro Nov 8, 2024
7b901f2
scoping
Jsyro Nov 8, 2024
8546322
factories making dates, should be datetimes.
Jsyro Nov 8, 2024
46aeab5
idk why that today generator suck
Jsyro Nov 8, 2024
c7e04f0
pass callable
Jsyro Nov 8, 2024
2332d50
what
Jsyro Nov 8, 2024
d32a875
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 8, 2024
ec05069
use jank
Jsyro Nov 8, 2024
ce25b82
use work
Jsyro Nov 8, 2024
14ab519
huh?
Jsyro Nov 8, 2024
a1d0b46
typing dates as dates. some db columns are dates, others aren'
Jsyro Nov 8, 2024
7af67e9
dates
Jsyro Nov 8, 2024
114a987
handling incoming datetime for date column
Jsyro Nov 8, 2024
39042c2
probably failing on date comparison
Jsyro Nov 9, 2024
ffed5f6
type hinting is good
Jsyro Nov 9, 2024
6f117b0
clean up logging
Jsyro Nov 12, 2024
8f34192
columns are dates
Jsyro Nov 12, 2024
0093b9c
received date is parsed to datetime
Jsyro Nov 12, 2024
1202685
expand on type hinting
Jsyro Nov 12, 2024
d963492
pass function, not value
Jsyro Nov 12, 2024
8b332e6
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 12, 2024
e482e66
do not produce id until after hashing
Jsyro Nov 14, 2024
179f65c
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 14, 2024
9f2e333
didn't save and commit this file. oops
Jsyro Nov 14, 2024
10886e1
type checking would have caught this.
Jsyro Nov 15, 2024
73ddff5
enable basic type checking in vscode.
Jsyro Nov 15, 2024
23b5c12
operator change
Jsyro Nov 15, 2024
08351e1
typing model values
Jsyro Nov 15, 2024
62882dc
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 15, 2024
1301a37
use a 'forward reference'
Jsyro Nov 18, 2024
cea5568
current app logger pipe to the celery manager.
Jsyro Nov 20, 2024
cb345cb
use authorization end date if set
Jsyro Nov 20, 2024
f7d15f2
remove valid until date, manage with 'refresh' status.
Jsyro Nov 20, 2024
3638832
remove enumerate
Jsyro Nov 20, 2024
534d63d
Merge branch 'develop' into feature/issue-to-orgbook-w-publisher
Jsyro Nov 20, 2024
3b0cd6d
remove old coments
Jsyro Nov 20, 2024
d112eb5
update query.
Jsyro Nov 20, 2024
280f6b0
new endpoint shape on orgbook publisher
Jsyro Nov 21, 2024
fcf8515
new env var, new service file, oauth and endpoints
Jsyro Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion services/core-api/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ UNTP_DIGITAL_CONFORMITY_CREDENTIAL_CONTEXT=https://test.uncefact.org/vocabulary/
UNTP_DIGITAL_CONFORMITY_CREDENTIAL_SCHEMA=https://test.uncefact.org/vocabulary/untp/dcc/untp-dcc-schema-0.5.0.json
UNTP_BC_MINES_ACT_PERMIT_CONTEXT=https://bcgov.github.io/digital-trust-toolkit/contexts/BCMinesActPermit/v1.jsonld
ORGBOOK_CREDENTIAL_BASE_URL=https://dev.orgbook.traceability.site/credentials
ORGBOOK_PUBLISHER_API_KEY=ORGBOOK_PUBLISHER_API_KEY
ORGBOOK_PUBLISHER_CLIENT_SECRET=ORGBOOK_PUBLISHER_CLIENT_SECRET
# Permit Search Service
PERMITS_ENDPOINT=http://haystack
PERMITS_CLIENT_ID=mds-core-api-internal-5194
Expand Down
30 changes: 30 additions & 0 deletions services/core-api/app/api/services/orgbook_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import requests

from flask import current_app
from app.config import Config

token_url = f"{Config.ORGBOOK_PUBLISHER_BASE_URL}/auth/token"
cred_publish_url = f"{Config.ORGBOOK_PUBLISHER_BASE_URL}/credentials/publish"


class OrgbookPublisherService():
### class to manage API calls to the Orgbook Publisher, it's a service that will sign and publish data to Orgbook. The data is currently UNTP Digital Conformity Credentials that prove business have mines act permits.
token: str

def __init__(self):
self.token = self.get_new_token()

def get_headers(self):
return {"Authorization": f"Bearer {self.token}"}

def get_new_token(self):
payload = {
"client_id": Config.CHIEF_PERMITTING_OFFICER_DID_WEB,
"client_secret": Config.ORGBOOK_PUBLISHER_CLIENT_SECRET
}
token_resp = requests.post(token_url, json=payload)
token_resp.raise_for_status()
return token_resp.json()["access_token"]

def publish_cred(self, payload: dict) -> requests.Response:
return requests.post(cred_publish_url, json=payload, headers=self.get_headers())
107 changes: 57 additions & 50 deletions services/core-api/app/api/verifiable_credentials/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from time import sleep
from typing import List
from flask import current_app
from celery.utils.log import get_task_logger

from app.tasks.celery import celery

Expand All @@ -29,11 +28,10 @@
from app.api.verifiable_credentials.models.connection import PartyVerifiableCredentialConnection
from app.api.verifiable_credentials.models.orgbook_publish_status import PermitAmendmentOrgBookPublish
from app.api.services.traction_service import TractionService
from app.api.services.orgbook_publisher import OrgbookPublisherService

from untp_models import codes, base, conformity_credential as cc

task_logger = get_task_logger(__name__)


class UNTPCCMinesActPermit(cc.ConformityAttestation):
type: List[str] = ["ConformityAttestation", "MinesActPermit"]
Expand All @@ -43,8 +41,8 @@ class UNTPCCMinesActPermit(cc.ConformityAttestation):
W3C_CRED_ID_PREFIX = f"{Config.ORGBOOK_PUBLISHER_BASE_URL}/credentials/"

permit_amendments_for_orgbook_query = """
select pa.permit_amendment_guid, poe.party_guid

select pa.permit_amendment_guid, p.party_guid
from party_orgbook_entity poe
inner join party p on poe.party_guid = p.party_guid
inner join mine_party_appt mpa on p.party_guid = mpa.party_guid
Expand All @@ -55,10 +53,13 @@ class UNTPCCMinesActPermit(cc.ConformityAttestation):
where mpa.permit_id is not null
and mpa.mine_party_appt_type_code = 'PMT'
and mpa.deleted_ind = false
and mpa.start_date <= pa.issue_date
and mpa.end_date > pa.issue_date
and m.major_mine_ind = true
and pa.deleted_ind = false

group by pa.permit_amendment_guid, pa.description, pa.issue_date, pa.permit_amendment_status_code, mpa.deleted_ind, pmt.permit_no, mpa.permit_id, poe.party_guid, p.party_name, poe.name_text, poe.registration_id
and pmt.permit_status_code = 'O'

group by pa.permit_amendment_guid, p.party_guid, pa.description, pa.issue_date, pa.permit_amendment_status_code, pmt.permit_no, mpa.permit_id, poe.party_guid, p.party_name, poe.name_text, poe.registration_id, m.mine_name, mine_party_appt_type_code
order by pmt.permit_no, pa.issue_date;
"""

Expand All @@ -79,7 +80,6 @@ class W3CCred(BaseModel):
id: str | None
type: List[str]
issuer: Union[str, dict[str, str]]
# TODO: update to `validFrom` for vcdm 2.0 once available in aca-py/traction, which is an optional property
validFrom: str
credentialSubject: UNTPCCMinesActPermit
credentialSchema: List[dict]
Expand Down Expand Up @@ -115,7 +115,7 @@ def revoke_all_credentials_for_permit(permit_guid: str, mine_guid: str, reason:
#problem reports set the state to abandoned in both agents, cannot continue afterwards

info_str = f"revoked all credentials for permit_guid={permit_guid} and mine_guid={mine_guid}"
task_logger.warning(info_str) # not sure where to find this.
current_app.logger.warning(info_str) # not sure where to find this.

return info_str

Expand Down Expand Up @@ -148,7 +148,7 @@ def offer_newest_amendment_to_current_permittee(permit_amendment_guid: str,
map_vc.save()

info_str = f"offer new_cred_exchange{response['credential_exchange_id']} for permit_amendment_guid={newest_amendment.permit_amendment_guid}"
task_logger.warning(info_str) # not sure where to find this.
current_app.logger.warning(info_str) # not sure where to find this.

return info_str

Expand All @@ -162,8 +162,8 @@ def process_all_untp_map_for_orgbook():
permit_amendment_query_results = db.session.execute(
permit_amendments_for_orgbook_query).fetchall()

task_logger.info("Num of results from query to process:" +
str(len(permit_amendment_query_results)))
current_app.logger.info("Num of results from query to process:" +
str(len(permit_amendment_query_results)))

traction_service = TractionService()
public_did_dict = traction_service.fetch_current_public_did()
Expand All @@ -173,20 +173,21 @@ def process_all_untp_map_for_orgbook():
assert public_did.startswith(
"did:web:"
), f"Config.CHIEF_PERMITTING_OFFICER_DID_WEB = {Config.CHIEF_PERMITTING_OFFICER_DID_WEB} is not a did:web"
task_logger.info("public did: " + public_did)
current_app.logger.info("public did: " + public_did)

records: List[Tuple[W3CCred,
PermitAmendmentOrgBookPublish]] = [] # list of tuples [payload, record]

for row in permit_amendment_query_results:
pa = PermitAmendment.find_by_permit_amendment_guid(row[0], unsafe=True)
if not pa:
task_logger.warning(f"Permit Amendment not found for permit_amendment_guid={row[0]}")
current_app.logger.warning(
f"Permit Amendment not found for permit_amendment_guid={row[0]}")
continue

pa_cred = VerifiableCredentialManager.produce_untp_cc_map_payload_without_id(public_did, pa)
if not pa_cred:
task_logger.warning(f"pa_cred could not be created")
current_app.logger.warning(f"pa_cred could not be created")
continue

payload_hash = md5(pa_cred.model_dump_json(by_alias=True).encode('utf-8')).hexdigest()
Expand All @@ -211,7 +212,7 @@ def process_all_untp_map_for_orgbook():
)
records.append((pa_cred, paob))

task_logger.info(f"public_verkey={public_verkey}")
current_app.logger.info(f"public_verkey={public_verkey}")
# send to traction to be signed
for cred_payload, record in records:
signed_cred = traction_service.sign_add_data_integrity_proof(
Expand All @@ -222,13 +223,14 @@ def process_all_untp_map_for_orgbook():
try:
record.save()
except IntegrityError:
task_logger.warning(f"ignoring duplicate={str(record.unsigned_payload_hash)}")
current_app.logger.warning(f"ignoring duplicate={str(record.unsigned_payload_hash)}")
continue
task_logger.info("bcreg_uri=" + str(cred_payload.credentialSubject.issuedToParty.id) +
", for permit_amendment_guid=" + str(row[0]))
task_logger.warning("unsigned_hash=" + str(record.unsigned_payload_hash))
current_app.logger.info("bcreg_uri=" +
str(cred_payload.credentialSubject.issuedToParty.id) +
", for permit_amendment_guid=" + str(row[0]))
current_app.logger.warning("unsigned_hash=" + str(record.unsigned_payload_hash))

task_logger.info("num of records created: " + str(len(records or [])))
current_app.logger.info("num of records created: " + str(len(records or [])))

return [record for payload, record in records]

Expand All @@ -240,10 +242,10 @@ def forward_all_pending_untp_vc_to_orgbook():
records_to_forward = PermitAmendmentOrgBookPublish.find_all_unpublished(unsafe=True)
ORGBOOK_W3C_CRED_FORWARD = f"{Config.ORGBOOK_PUBLISHER_BASE_URL}/credentials/forward"

task_logger.warning(f"going to publish {len(records_to_forward)} records to orgbook")
current_app.logger.warning(f"going to publish {len(records_to_forward)} records to orgbook")

for record in records_to_forward:
task_logger.warning(f"publishing record={json.loads(record.signed_credential)}")
current_app.logger.warning(f"publishing record={json.loads(record.signed_credential)}")
payload = {
"verifiableCredential": json.loads(record.signed_credential),
"options": {
Expand All @@ -266,9 +268,6 @@ def push_untp_map_data_to_publisher():
## This is a different process that passes the data to the publisher.
## the publisher structures the data and sends it to the orgbook.
## the publisher also manages the BitStringStatusLists.
ORGBOOK_W3C_CRED_PUBLISH = f"{Config.ORGBOOK_PUBLISHER_BASE_URL}/credentials/publish"

records_to_publish = PermitAmendmentOrgBookPublish.find_all_unpublished(unsafe=True)
permit_amendment_query_results = db.session.execute(
permit_amendments_for_orgbook_query).fetchall()

Expand All @@ -277,35 +276,44 @@ def push_untp_map_data_to_publisher():

for row in permit_amendment_query_results:
pa = PermitAmendment.find_by_permit_amendment_guid(row[0], unsafe=True)

if pa.permit_no[1] in ("X", "x"):
current_app.logger.warning(
f"exclude exploration permit={pa.permit_no}, they cannot produce goods for sale")
continue

pa_cred = VerifiableCredentialManager.produce_untp_cc_map_payload_without_id(
Config.CHIEF_PERMITTING_OFFICER_DID_WEB, pa)
if not pa_cred:
task_logger.warning(f"pa_cred could not be created for permit_amendment_guid={row[0]}")
current_app.logger.warning(
f"pa_cred could not be created for permit_amendment_guid={row[0]}")
continue
#only one assessment per credential
publish_payload = {
"type": "BCMinesActPermitCredential",
"coreData": {
"entityId": pa_cred.credentialSubject.issuedToParty.registeredId,
"resourceId": pa_cred.credentialSubject.permitNumber,
"credential": {
"type": "BCMinesActPermitCredential",
"validFrom": convert_date_to_iso_datetime(pa.issue_date),
"validUntil": convert_date_to_iso_datetime(pa.issue_date + relativedelta(years=5)),
"credentialSubject": {
"permitNumber": pa_cred.credentialSubject.permitNumber
},
},
"subjectData": {
"permitNumber": pa_cred.credentialSubject.permitNumber
},
"untpData": {
"assessedFacility": [
f.model_dump(exclude_none=True)
for f in pa_cred.credentialSubject.assessment[0].assessedFacility
],
"assessedProduct": [
p.model_dump(exclude_none=True)
for p in pa_cred.credentialSubject.assessment[0].assessedProduct
],
"options": {
"entityId": pa_cred.credentialSubject.issuedToParty.registeredId,
"credentialId": str(pa.permit_amendment_guid),
"cardinalityId": pa_cred.credentialSubject.permitNumber,
"additionalData": {
"assessedFacility": [
f.model_dump(exclude_none=True)
for f in pa_cred.credentialSubject.assessment[0].assessedFacility
],
"assessedProduct": [
p.model_dump(exclude_none=True)
for p in pa_cred.credentialSubject.assessment[0].assessedProduct
],
}
}
}

publisher_service = OrgbookPublisherService()
current_app.logger.warning(f"publishing record={publish_payload}")
payload_hash = md5(json.dumps(publish_payload).encode('utf-8')).hexdigest()
current_app.logger.warning(f"payload hash={payload_hash}")
Expand All @@ -322,16 +330,15 @@ def push_untp_map_data_to_publisher():
error_msg=None)

try:
current_app.logger.info(f"saved publish record locally")
publish_record.save()

post_resp = requests.post(
ORGBOOK_W3C_CRED_PUBLISH,
json=publish_payload,
headers={"X-API-KEY": Config.ORGBOOK_PUBLISHER_API_KEY})
post_resp = publisher_service.publish_cred(publish_payload)

publish_record.publish_state = post_resp.ok
publish_record.error_msg = post_resp.text if not post_resp.ok else None
publish_record.orgbook_credential_id = post_resp.json()["credentialId"]
if post_resp.ok:
publish_record.orgbook_credential_id = post_resp.json()["credentialId"]

publish_record.save()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,17 @@
class PermitAmendmentOrgBookPublish(AuditMixin, Base):
"""Track mines act permit credentials being issued to orgbook"""
__tablename__ = "permit_amendment_orgbook_publish_status"
unsigned_payload_hash = db.Column(db.String, primary_key=True) #string on hex characters
unsigned_payload_hash = db.Column(db.String, primary_key=True)
permit_amendment_guid = db.Column(
UUID(as_uuid=True), db.ForeignKey('permit_amendment.permit_amendment_guid'), nullable=False)
party_guid = db.Column(UUID(as_uuid=True), db.ForeignKey('party.party_guid'), nullable=False)
sign_date = db.Column(db.DateTime, nullable=True)
signed_credential = db.Column(db.String, nullable=True)
publish_state = db.Column(
db.Boolean, nullable=True) # null = not published, true = published, false = failed
db.Boolean, nullable=True) # null = not published, true = published, false = failed
permit_number = db.Column(db.String, nullable=False)
orgbook_entity_id = db.Column(db.String, nullable=False)
orgbook_credential_id = db.Column(
db.String, nullable=False) # not sure this will be able to be populated
orgbook_credential_id = db.Column(db.String, nullable=False)
error_msg = db.Column(db.String, nullable=True)

def __repr__(self):
Expand Down
4 changes: 2 additions & 2 deletions services/core-api/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ def JWT_ROLE_CALLBACK(jwt_dict):

ORGBOOK_PUBLISHER_BASE_URL = os.environ.get("ORGBOOK_PUBLISHER_BASE_URL",
"https://dev.orgbook.traceability.site")
ORGBOOK_PUBLISHER_API_KEY = os.environ.get("ORGBOOK_PUBLISHER_API_KEY",
"ORGBOOK_PUBLISHER_API_KEY")
ORGBOOK_PUBLISHER_CLIENT_SECRET = os.environ.get("ORGBOOK_PUBLISHER_CLIENT_SECRET",
"ORGBOOK_PUBLISHER_CLIENT_SECRET")


class TestConfig(Config):
Expand Down
Loading