Skip to content
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

[MDS-5697] Add method to forward UNTP DCC's through Orgbook Publisher #3289

Merged
merged 45 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c1337f5
attestation type and use published vocab
Jsyro Sep 5, 2024
b4e993b
need tupe
Jsyro Sep 9, 2024
71bf73e
update dcc construction and library to dcc 0.3.10
Jsyro Sep 11, 2024
7ccca5c
update test
Jsyro Sep 11, 2024
c82d290
test
Jsyro Sep 11, 2024
52d8ad3
remove/update logging
Jsyro Sep 12, 2024
c731603
ensure query doesn't include regional mines (yet
Jsyro Sep 13, 2024
5cd78ed
make context files configurable.
Jsyro Oct 8, 2024
996b6f7
real context extension
Jsyro Oct 9, 2024
429d9f1
remove type not defined in context files.
Jsyro Oct 10, 2024
a53415c
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Oct 10, 2024
ae03533
add schema file and config, updated to match 0.1.0 of models package
Jsyro Oct 11, 2024
894306c
nullable but not defaulted.
Jsyro Oct 15, 2024
dd076cb
same with product
Jsyro Oct 15, 2024
cac3b45
same for ca
Jsyro Oct 15, 2024
9b00243
link to credential, not orgbook identifer.
Jsyro Oct 15, 2024
f6d72cf
real tdw registry, and schema extension
Jsyro Oct 16, 2024
babbf36
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Oct 18, 2024
d4a1ea6
Define verification method, strip did
Jsyro Oct 18, 2024
e10d96d
add permit_number, remove name.
Jsyro Oct 18, 2024
a703dcb
extended class needs extended type
Jsyro Oct 18, 2024
01fc4f5
name is required in model (But not in spec)
Jsyro Oct 18, 2024
583be0b
bc prefix is removed
Jsyro Oct 18, 2024
209a9f9
vcdm 2.0 updates.
Jsyro Oct 21, 2024
01f5e37
update to use new DataIntegrity Proof
Jsyro Oct 22, 2024
08241e7
merge
Jsyro Oct 22, 2024
f250661
remove dup line from merge
Jsyro Oct 22, 2024
5fb9575
remove dup lines from merge.
Jsyro Oct 22, 2024
19d8867
key 1 is different, signing key is now key-02-multikey
Jsyro Oct 22, 2024
794d4d1
use mine_no ad factility identifier
Jsyro Oct 23, 2024
985fea9
remove unused urls
Jsyro Oct 23, 2024
b23d3d2
new env var for target url and generate id.
Jsyro Oct 24, 2024
a15e78b
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Oct 24, 2024
b6f3cfa
remove slash from env var
Jsyro Oct 24, 2024
989595e
optional id's are allowed in package
Jsyro Oct 24, 2024
e0d700d
return tuple in error case
Jsyro Oct 24, 2024
2a91951
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Oct 24, 2024
ae5371b
add columns and publish all pending records
Jsyro Oct 29, 2024
b781aee
keep error response if success isn't there.
Jsyro Oct 29, 2024
8e962eb
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Oct 30, 2024
d4d00b9
better names, going to implement both processes
Jsyro Oct 31, 2024
f4bad0c
new command to push to publisher for construction and publishing
Jsyro Oct 31, 2024
a9ea2ba
trying to sync
Jsyro Oct 31, 2024
0c23028
prettier instead of eslint in vscode
Jsyro Oct 31, 2024
e0a66c5
Merge branch 'develop' into feature/untp-dcc-cleanup
Jsyro Nov 4, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE permit_amendment_orgbook_publish_status
ADD COLUMN IF NOT EXISTS permit_number VARCHAR(50),
ADD COLUMN IF NOT EXISTS orgbook_entity_id VARCHAR(50),
ADD COLUMN IF NOT EXISTS error_msg VARCHAR;
125 changes: 99 additions & 26 deletions services/core-api/app/api/verifiable_credentials/manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# for midware/business level actions between requests and data access
import json
import requests
from datetime import date, datetime, timedelta

from uuid import uuid4, UUID
from sqlalchemy.exc import IntegrityError
Expand Down Expand Up @@ -37,6 +39,26 @@ class UNTPCCMinesActPermit(cc.ConformityAttestation):
permitNumber: str


permit_amendments_for_orgbook_query = """
select pa.permit_amendment_guid, poe.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
inner join permit pmt on pmt.permit_id = mpa.permit_id
inner join permit_amendment pa on pa.permit_id = pmt.permit_id
inner join mine m on pa.mine_guid = m.mine_guid

where mpa.permit_id is not null
and mpa.mine_party_appt_type_code = 'PMT'
and mpa.deleted_ind = false
and m.major_mine_ind = true

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
order by pmt.permit_no, pa.issue_date;
"""


#this should probably be imported from somewhere.
class W3CCred(BaseModel):
#based on VCDM 2.0. https://www.w3.org/TR/vc-data-model-2.0/
Expand Down Expand Up @@ -129,25 +151,8 @@ def process_all_untp_map_for_orgbook():

# https://metabase-4c2ba9-prod.apps.silver.devops.gov.bc.ca/question/2937-permit-amendments-for-each-party-orgbook-entity

permit_amendment_query_results = db.session.execute("""
select pa.permit_amendment_guid, poe.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
inner join permit pmt on pmt.permit_id = mpa.permit_id
inner join permit_amendment pa on pa.permit_id = pmt.permit_id
inner join mine m on pa.mine_guid = m.mine_guid

where mpa.permit_id is not null
and mpa.mine_party_appt_type_code = 'PMT'
and mpa.deleted_ind = false
and m.major_mine_ind = true

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
order by pmt.permit_no, pa.issue_date;

""").fetchall()
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)))
Expand Down Expand Up @@ -189,6 +194,8 @@ def process_all_untp_map_for_orgbook():
permit_amendment_guid=row[0],
party_guid=row[1],
unsigned_payload_hash=payload_hash,
permit_number=pa_cred.credentialSubject.permitNumber,
orgbook_entity_id=pa_cred.credentialSubject.issuedToParty.registeredId,
orgbook_credential_id=new_id,
)
records.append((pa_cred, paob))
Expand All @@ -215,16 +222,82 @@ def process_all_untp_map_for_orgbook():
return [record for payload, record in records]


def publish_all_pending_vc_to_orgbook():
def forward_all_pending_untp_vc_to_orgbook():
"""STUB for celery job to publis all pending vc to orgbook."""
## Orgbook doesn't have this functionality yet.
## CORE signs and structures the credential, the publisher just validates and forwards it.
records_to_forward = PermitAmendmentOrgBookPublish.find_all_unpublished(unsafe=True)
ORGBOOK_W3C_CRED_FORWARD = f"{Config.ORGBOOK_CREDENTIAL_BASE_URL}/forward"

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

for record in records_to_forward:
current_app.logger.warning(f"publishing record={json.loads(record.signed_credential)}")
payload = {
"verifiableCredential": json.loads(record.signed_credential),
"options": {
"entityId": record.orgbook_entity_id,
"resourceId": record.permit_number,
"credentialId": record.orgbook_credential_id,
"credentialType": "BCMinesActPermitCredential"
}
}
resp = requests.post(ORGBOOK_W3C_CRED_FORWARD, json=payload)
if resp.status_code == 201:
record.publish_state = True
else:
record.error_msg = resp.text
record.save()


@celery.task()
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_CREDENTIAL_BASE_URL}/publish"

records_to_publish = PermitAmendmentOrgBookPublish.find_all_unpublished(unsafe=True)
permit_amendment_query_results = db.session.execute(
permit_amendments_for_orgbook_query).fetchall()
for row in permit_amendment_query_results:
pa = PermitAmendment.find_by_permit_amendment_guid(row[0], unsafe=True)
pa_cred, new_id = VerifiableCredentialManager.produce_untp_cc_map_payload(
Config.CHIEF_PERMITTING_OFFICER_DID_WEB, pa)

#only one assessment per credential
publish_payload = {
"type": "BCMinesActPermitCredential",
"coreData": {
"entityId": pa_cred.credentialSubject.issuedToParty.registeredId,
"resourceId": pa_cred.credentialSubject.permitNumber,
"validFrom": pa_cred.validFrom,
"validUntil": date.fromisoformat(pa_cred.validFrom) + timedelta(years=5)
},
"subjectData": {
"permitNumber": pa_cred.credentialSubject.permitNumber
},
"untpData": {
"assessedFacility": pa_cred.credentialSubject.assessment[0].model_dump(),
"assessedProduct": pa_cred.credentialSubject.assessment[0].model_dump(),
}
}
payload_hash = md5(json.dumps(publish_payload).encode('utf-8')).hexdigest()

current_app.logger.warning(f"publishing record={publish_payload}")
post_resp = requests.post(Config.ORGBOOK_W3C_CRED_PUBLISH, json=publish_payload)

publish_record = PermitAmendmentOrgBookPublish(
payload_hash=payload_hash,
permit_amendment_guid=row[0],
party_guid=row[1],
signed_credential="Produced by publisher",
publish_state=post_resp.ok,
permitNumber=pa_cred.credentialSubject.permitNumber,
orgbook_entity_id=pa_cred.credentialSubject.issuedToParty.registeredId,
orgbook_credential_id=new_id,
error_msg=post_resp.text if not post_resp.ok else None)

for record in records_to_publish:
current_app.logger.warning("NOT sending cred to orgbook")
current_app.logger.warning(record.signed_credential)
# resp = requests.post(ORGBOOK_W3C_CRED_POST, record.signed_credential)
# assert resp.status_code == 200, f"resp={resp.json()}"
publish_record.save()


class VerifiableCredentialManager():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.schema import FetchedValue
from sqlalchemy.sql import true
from app.extensions import db
from typing import List
from app.api.utils.models_mixins import AuditMixin, Base
Expand All @@ -18,8 +19,11 @@ class PermitAmendmentOrgBookPublish(AuditMixin, Base):
signed_credential = db.Column(db.String, nullable=True)
publish_state = db.Column(
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
error_msg = db.Column(db.String, nullable=True)

def __repr__(self):
return f'<PermitAmendmentOrgBookPublishStatus unsigned_payload_hash={self.unsigned_payload_hash}, permit_amendment_guid={self.permit_amendment_guid}, sign_date={self.sign_date}, publish_state={self.publish_state}>'
Expand All @@ -35,4 +39,5 @@ def find_by_unsigned_payload_hash(cls,
@classmethod
def find_all_unpublished(cls, *, unsafe: bool = False) -> List["PermitAmendmentOrgBookPublish"]:
query = cls.query.unbound_unsafe() if unsafe else cls.query
return query.filter(PermitAmendmentOrgBookPublish.publish_state != True).all()
results: List["PermitAmendmentOrgBookPublish"] = query.filter().all()
return [r for r in results if r.publish_state is not True]
29 changes: 17 additions & 12 deletions services/core-api/app/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ def notify_expiring_party_appointments():
def notify_and_update_expired_party_appointments():
from app import auth
from app.api.parties.party_appt import (
notify_and_update_expired_party_appointments,
)
notify_and_update_expired_party_appointments, )
auth.apply_security = False

with current_app.app_context():
Expand All @@ -147,8 +146,7 @@ def notify_and_update_expired_party_appointments():
def revoke_mines_act_permits_for_permit(credential_exchange_id, permit_guid):
from app import auth
from app.api.verifiable_credentials.manager import (
revoke_all_credentials_for_permit,
)
revoke_all_credentials_for_permit, )
auth.apply_security = False
with current_app.app_context():
permit = Permit.query.unbound_unsafe().filter_by(permit_guid=permit_guid).first()
Expand All @@ -160,21 +158,28 @@ def revoke_mines_act_permits_for_permit(credential_exchange_id, permit_guid):
def process_all_untp_map_for_orgbook():
from app import auth
from app.api.verifiable_credentials.manager import (
process_all_untp_map_for_orgbook,
)
process_all_untp_map_for_orgbook, )
auth.apply_security = False
with current_app.app_context() as app:
result = process_all_untp_map_for_orgbook.apply_async()
result = process_all_untp_map_for_orgbook()

@app.cli.command('publish_all_pending_vc_to_orgbook')
def publish_all_pending_vc_to_orgbook():
@app.cli.command('forward_all_pending_untp_vc_to_orgbook')
def forward_all_pending_untp_vc_to_orgbook():
from app import auth
from app.api.verifiable_credentials.manager import (
publish_all_pending_vc_to_orgbook,
)
forward_all_pending_untp_vc_to_orgbook, )
auth.apply_security = False
with current_app.app_context():
result = publish_all_pending_vc_to_orgbook()
result = forward_all_pending_untp_vc_to_orgbook()

@app.cli.command('push_untp_map_data_to_publisher')
def push_untp_map_data_to_publisher():
from app import auth
from app.api.verifiable_credentials.manager import (
push_untp_map_data_to_publisher, )
auth.apply_security = False
with current_app.app_context():
result = push_untp_map_data_to_publisher()

@app.cli.command('generate_history_table_migration')
@click.argument('table')
Expand Down
2 changes: 1 addition & 1 deletion services/core-api/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def JWT_ROLE_CALLBACK(jwt_dict):
"UNTP_BC_MINES_ACT_PERMIT_CONTEXT")

ORGBOOK_CREDENTIAL_BASE_URL = os.environ.get(
"ORGBOOK_CREDENTIAL_BASE_URL", "https://dev.orgbook.traceability.site/credentials/")
"ORGBOOK_CREDENTIAL_BASE_URL", "https://dev.orgbook.traceability.site/credentials")


class TestConfig(Config):
Expand Down
5 changes: 1 addition & 4 deletions services/core-web/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import moment from "moment-timezone";
import { reset } from "redux-form";
import { ItemMap } from "@mds/common";


/**
* Helper function to clear redux form after submissions
*
Expand Down Expand Up @@ -107,8 +106,6 @@ export const timeAgo = (dateTime, unit = "day") => {
}
};



export const formatPostalCode = (code) => code && code.replace(/.{3}$/, " $&");

export const formatTitleString = (input) =>
Expand Down Expand Up @@ -593,7 +590,7 @@ export const getHighestConsequence = (tsf) => {

const highestRankedDam = tsf.dams.reduce((prev, current) =>
CONSEQUENCE_CLASSIFICATION_RANK_HASH[prev.consequence_classification] >
CONSEQUENCE_CLASSIFICATION_RANK_HASH[current.consequence_classification]
CONSEQUENCE_CLASSIFICATION_RANK_HASH[current.consequence_classification]
taraepp marked this conversation as resolved.
Show resolved Hide resolved
? prev
: current
);
Expand Down
5 changes: 1 addition & 4 deletions services/minespace-web/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import moment from "moment-timezone";
import { reset } from "redux-form";
import { ItemMap } from "@mds/common";


/**
* Helper function to clear redux form after submissions
*
Expand Down Expand Up @@ -107,8 +106,6 @@ export const timeAgo = (dateTime, unit = "day") => {
}
};



export const formatPostalCode = (code) => code && code.replace(/.{3}$/, " $&");

export const formatTitleString = (input) =>
Expand Down Expand Up @@ -593,7 +590,7 @@ export const getHighestConsequence = (tsf) => {

const highestRankedDam = tsf.dams.reduce((prev, current) =>
CONSEQUENCE_CLASSIFICATION_RANK_HASH[prev.consequence_classification] >
CONSEQUENCE_CLASSIFICATION_RANK_HASH[current.consequence_classification]
CONSEQUENCE_CLASSIFICATION_RANK_HASH[current.consequence_classification]
? prev
: current
);
Expand Down
Loading