Skip to content

Commit

Permalink
[FIX] - Update to match Orgbook Publisher Changes (#3312)
Browse files Browse the repository at this point in the history
Couple minor housekeeping items.

current_app.logger pipes to the core-api-celery pod, so we don't need task logger.
update sql query to identify permittees based on start/end date and issue_date.
orgbook publisher publish endpoint updating, changes here to match
oauth on the publisher.
  • Loading branch information
Jsyro authored Nov 21, 2024
1 parent e8f4bef commit dd35fa6
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 57 deletions.
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

0 comments on commit dd35fa6

Please sign in to comment.