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-6067] Update VC Feature Flags #3186

Merged
merged 8 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions services/common/src/utils/featureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ export enum Feature {
ESUP_PERMIT_AMENDMENT = "esup_permit_amendment",
FLAGSMITH = "flagsmith",
TSF_V2 = "tsf_v2",
VERIFIABLE_CREDENTIALS = "verifiable_credentials",
VERIFIABLE_CREDENTIALS_2 = "verifiable_credentials_2.0",
VC_ANONCREDS_CORE = "vc_anoncreds_core",
VC_ANONCREDS_MINESPACE = "vc_anoncreds_minespace",
VC_W3C = "vc_w3c",
MINESPACE_ESUPS = "minespace_esups",
REPORT_ERROR = "report_error",
MAJOR_PROJECT_LINK_PROJECTS = "major_project_link_projects",
Expand Down
7 changes: 4 additions & 3 deletions services/core-api/app/api/utils/feature_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ class Feature(Enum):
MINE_APPLICATION_FILE_UDPATE_ALERTS = 'mine_application_file_update_alerts'
TRACTION_VERIFIABLE_CREDENTIALS = 'verifiable_credentials'
#if enabled the credential offer will be the current development state of all the 2.0 changes, Q1 2024
VC_MINES_ACT_PERMIT_20 = 'vc_mines_act_permit_20'
VC_ANONCREDS_20 = 'vc_mines_act_permit_20' # pending anoncred content differences.
VC_ANONCREDS_CORE = "vc_anoncreds_core"
VC_ANONCREDS_MINESPACE = "vc_anoncreds_minespace"
VC_W3C = "vc_w3c"
CODE_REQUIRED_REPORTS = 'code_required-reports'
PERMIT_DOCUMENT_KEYWORD_SEARCH = 'permit_document_keyword_search'
AMS_AGENT = 'ams_agent'
#technical features exist, governance is not yet in place
JSONLD_MINES_ACT_PERMIT = 'jsonld_mines_act_permit'

def __str__(self):
return self.value
Expand Down
35 changes: 12 additions & 23 deletions services/core-api/app/api/verifiable_credentials/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ def process_all_untp_map_for_orgbook():

""").fetchall()

current_app.logger.warning("Num of results from query to process:" +
str(len(permit_amendment_query_results)))
task_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 @@ -151,13 +151,12 @@ def process_all_untp_map_for_orgbook():
for row in permit_amendment_query_results:
pa = PermitAmendment.find_by_permit_amendment_guid(row[0], unsafe=True)
if not pa:
current_app.logger.warning(
f"Permit Amendment not found for permit_amendment_guid={row[0]}")
task_logger.warning(f"Permit Amendment not found for permit_amendment_guid={row[0]}")
continue

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

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

current_app.logger.warning(f"public_verkey={public_verkey}")
task_logger.info(f"public_verkey={public_verkey}")
# send to traction to be signed
for cred_payload, record in records:
signed_cred = traction_service.sign_jsonld_credential_deprecated(
Expand All @@ -195,7 +194,7 @@ def process_all_untp_map_for_orgbook():
", for permit_amendment_guid=" + str(row[0]))
current_app.logger.warning("unsigned_hash=" + str(record.unsigned_payload_hash))

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

return [record for payload, record in records]

Expand Down Expand Up @@ -269,7 +268,7 @@ def collect_attributes_for_mines_act_permit_111(
credential_attrs["mine_no"] = permit_amendment.mine.mine_no
credential_attrs["issue_date"] = int(
permit_amendment.issue_date.strftime("%Y%m%d")) if is_feature_enabled(
Feature.VC_MINES_ACT_PERMIT_20) else permit_amendment.issue_date
Feature.VC_ANONCREDS_20) else permit_amendment.issue_date
# https://github.com/hyperledger/aries-rfcs/tree/main/concepts/0441-present-proof-best-practices#dates-and-predicates
credential_attrs["latitude"] = permit_amendment.mine.latitude
credential_attrs["longitude"] = permit_amendment.mine.longitude
Expand All @@ -294,10 +293,6 @@ def collect_attributes_for_mines_act_permit_111(

return attributes

@classmethod
def revoke_all_credentials_for_permit(cls, permit_guid: str):
pass

@classmethod
def produce_map_01_credential_payload(cls, did: str, permit_amendment: PermitAmendment):
#use w3c vcdm issue_date, not as an attribute in the credential
Expand Down Expand Up @@ -342,17 +337,11 @@ def produce_untp_cc_map_payload(cls, did: str, permit_amendment: PermitAmendment
return None

untp_party_cpo = base.Party(
name="Chief Permitting Officer",
name="Chief Permitting Officer of Mines",
identifiers=[
base.Identifier(
scheme=ANONCRED_SCHEME,
identifierValue="did:indy:candy:A2UZSmrL9N5FDZGPu68wy",
identifierURI="https://candyscan.idlab.org/tx/CANDY_PROD/domain/321",
verificationEvidence=base.Evidence(
format=codes.EvidenceFormat.W3C_VC,
credentialReference=
"did:web:untp.traceability.site:parties:regulators:CHIEF-PERMITTING-OFFICER" #this is an anoncred
))
identifierValue=
"did:web:untp.traceability.site:parties:regulators:CHIEF-PERMITTING-OFFICER")
])
orgbook_cred_url = f"https://orgbook.gov.bc.ca/entity/{orgbook_entity.registration_id}/credential/{orgbook_entity.credential_id}"

Expand Down Expand Up @@ -386,7 +375,7 @@ def produce_untp_cc_map_payload(cls, did: str, permit_amendment: PermitAmendment
referenceRegulation=cc.Regulation(
id="https://www.bclaws.gov.bc.ca/civix/document/id/complete/statreg/96293_01",
name="BC Mines Act",
issuingBody=base.Party(name="BC Government"),
issuingBody=base.Party(name="Government of British Columbia"),
effectiveDate=datetime(2024, 5, 14, tzinfo=ZoneInfo("UTC")).isoformat()),
# Is there a did:web that attests to that legistlation?
subjectFacilities=[facility],
Expand All @@ -399,7 +388,7 @@ def produce_untp_cc_map_payload(cls, did: str, permit_amendment: PermitAmendment
tzinfo=ZoneInfo("UTC")).isoformat()

cred = cc.ConformityAttestation(
id="http://example.com/attestation/123",
id="http://example.com/govdomain/minesactpermit/123",
assessmentLevel=codes.AssessmentAssuranceCode.GovtApproval,
type=codes.AttestationType.Certification,
description=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@
from flask import current_app, request
from werkzeug.exceptions import Forbidden
from flask_restx import Resource
from app.api.utils.include.user_info import User

from app.config import Config
from app.extensions import api

from app.api.utils.resources_mixins import UserMixin
from app.api.services.traction_service import TractionService

from app.api.verifiable_credentials.models.connection import PartyVerifiableCredentialConnection
from app.api.verifiable_credentials.models.credentials import PartyVerifiableCredentialMinesActPermit
from app.api.verifiable_credentials.aries_constants import DIDExchangeRequesterState, IssueCredentialIssuerState

from app.api.utils.feature_flag import Feature, is_feature_enabled

PRESENT_PROOF = "present_proof"
CONNECTIONS = "connections"
CREDENTIAL_OFFER = "issue_credential"
Expand All @@ -28,9 +24,6 @@ class TractionWebhookResource(Resource, UserMixin):

@api.doc(description='Endpoint to recieve webhooks from Traction.', params={})
def post(self, topic):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()

#custom auth for traction
if request.headers.get("x-api-key") != Config.TRACTION_WEBHOOK_X_API_KEY:
return Forbidden("bad x-api-key")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from flask import current_app
from flask_restx import Resource
from werkzeug.exceptions import NotFound, NotImplemented
from werkzeug.exceptions import NotFound, ServiceUnavailable
from app.extensions import api
from app.api.utils.access_decorators import requires_any_of, EDIT_PARTY, MINESPACE_PROPONENT

Expand All @@ -17,8 +16,8 @@ class VerifiableCredentialConnectionInvitationsResource(Resource, UserMixin):
@api.doc(description='Create a connection invitation for a party by guid', params={})
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
def post(self, party_guid: str):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
if not is_feature_enabled(Feature.VC_ANONCREDS_MINESPACE):
raise ServiceUnavailable()
party = Party.find_by_party_guid(party_guid)
if not party:
raise NotFound(f"party not found with party_guid {party_guid}")
Expand All @@ -32,19 +31,23 @@ def post(self, party_guid: str):
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
@api.marshal_with(PARTY_VERIFIABLE_CREDENTIAL_CONNECTION, code=200, envelope='records')
def get(self, party_guid: str):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
if not is_feature_enabled(Feature.VC_ANONCREDS_MINESPACE):
raise ServiceUnavailable()

party_vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(
party_guid=party_guid)

return party_vc_conn

@api.doc(description="Delete a connection for a party by guid", params={})
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
def delete(self, party_guid):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
if not is_feature_enabled(Feature.VC_ANONCREDS_MINESPACE):
raise ServiceUnavailable()

party_vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid)
if not party_vc_conn:
raise NotFound(f"party_vc_conn not found with party_guid {party_guid}")

party_vc_conn.delete()
party_vc_conn.save()
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
from app.extensions import api
from app.api.utils.access_decorators import requires_any_of, EDIT_PARTY, MINESPACE_PROPONENT

from app.api.parties.party.models.party import Party
from app.api.verifiable_credentials.models.connection import PartyVerifiableCredentialConnection
from app.api.services.traction_service import TractionService
from app.api.verifiable_credentials.response_models import PARTY_VERIFIABLE_CREDENTIAL_CONNECTION
from app.api.utils.resources_mixins import UserMixin
from app.api.utils.feature_flag import Feature, is_feature_enabled


class VerifiableCredentialConnectionResource(Resource, UserMixin):
Expand All @@ -20,7 +17,6 @@ def delete(self, party_guid: str):

active_conn = PartyVerifiableCredentialConnection.find_active_by_party_guid(party_guid)
conns = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid)
current_app.logger.warning(conns)
if not active_conn:
raise BadRequest(f"party has no active connection party_guid={party_guid}")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import current_app, request
from flask_restx import Resource, reqparse
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequest, ServiceUnavailable
from app.extensions import api
from app.config import Config

Expand All @@ -19,23 +19,31 @@

class VerifiableCredentialMinesActPermitResource(Resource, UserMixin):
parser = reqparse.RequestParser(trim=True)
parser.add_argument(
'permit_amendment_guid', location='json', type=str, store_missing=False)

parser.add_argument('permit_amendment_guid', location='json', type=str, store_missing=False)

@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
@api.marshal_with(PARTY_VERIFIABLE_CREDENTIAL_MINES_ACT_PERMIT, code=200, envelope='records', as_list=True)
@api.marshal_with(
PARTY_VERIFIABLE_CREDENTIAL_MINES_ACT_PERMIT, code=200, envelope='records', as_list=True)
def get(self, party_guid):
if not party_guid:
raise BadRequest("party_guid required")
party_credential_exchanges = PartyVerifiableCredentialMinesActPermit.find_by_party_guid(party_guid)
party_credential_exchanges = PartyVerifiableCredentialMinesActPermit.find_by_party_guid(
party_guid)

return party_credential_exchanges

@api.doc(description="Create a connection invitation for a party by guid", params={"party_guid":"guid for party with wallet connection","permit_amendment_guid":"parmit_amendment that will be offered as a credential to the indicated party"})
@api.doc(
description="Create a connection invitation for a party by guid",
params={
"party_guid":
"guid for party with wallet connection",
"permit_amendment_guid":
"parmit_amendment that will be offered as a credential to the indicated party"
})
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
def post(self, party_guid):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
if not is_feature_enabled(Feature.VC_ANONCREDS_MINESPACE):
raise ServiceUnavailable()
data = self.parser.parse_args()
current_app.logger.warning(data)
permit_amendment_guid = data["permit_amendment_guid"]
Expand All @@ -45,38 +53,49 @@ def post(self, party_guid):
if not party:
raise BadRequest(f"party not found with party_guid {party_guid}")


permit_amendment = PermitAmendment.find_by_permit_amendment_guid(permit_amendment_guid)
if not (permit_amendment):
raise BadRequest(f"permit_amendment not found")

existing_cred_exch = PartyVerifiableCredentialMinesActPermit.find_by_permit_amendment_guid(permit_amendment_guid=permit_amendment_guid) or []


existing_cred_exch = PartyVerifiableCredentialMinesActPermit.find_by_permit_amendment_guid(
permit_amendment_guid=permit_amendment_guid) or []

# If a user has deleted the credential from their wallet, they will need another copy so only limit on pending for UX reasons
pending_creds = [e for e in existing_cred_exch if e.cred_exch_state in IssueCredentialIssuerState.pending_credential_states]
pending_creds = [
e for e in existing_cred_exch
if e.cred_exch_state in IssueCredentialIssuerState.pending_credential_states
]

#https://github.com/hyperledger/aries-rfcs/tree/main/features/0036-issue-credential#states-for-issuer
if pending_creds:
raise BadRequest(f"There is a pending credential offer, accept or delete that offer first, cred_exch_id={existing_cred_exch.cred_exch_id}, cred_exch_state={existing_cred_exch.cred_exch_state}")
raise BadRequest(
f"There is a pending credential offer, accept or delete that offer first, cred_exch_id={existing_cred_exch.cred_exch_id}, cred_exch_state={existing_cred_exch.cred_exch_state}"
)

if permit_amendment.permit.mines_act_permit_vc_locked:
raise BadRequest(f"This permit cannot be offered as a credential")

attributes = VerifiableCredentialManager.collect_attributes_for_mines_act_permit_111(permit_amendment)

attributes = VerifiableCredentialManager.collect_attributes_for_mines_act_permit_111(
permit_amendment)

vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid)
active_connections = [con for con in vc_conn if con.connection_state in ["active","completed"]]
active_connections = [
con for con in vc_conn if con.connection_state in ["active", "completed"]
]

if not active_connections:
current_app.logger.warning("NO ACTIVE CONNECTION credential not offered")
current_app.logger.warning(vc_conn)
current_app.logger.warning(attributes)
raise BadRequest("Party does not have an active Digital Wallet connection")
else:
else:
traction_svc = TractionService()
response = traction_svc.offer_mines_act_permit_111(active_connections[0].connection_id, attributes)
map_vc = PartyVerifiableCredentialMinesActPermit(cred_exch_id = response["credential_exchange_id"],party_guid = party_guid, permit_amendment_guid=permit_amendment_guid)
response = traction_svc.offer_mines_act_permit_111(active_connections[0].connection_id,
attributes)
map_vc = PartyVerifiableCredentialMinesActPermit(
cred_exch_id=response["credential_exchange_id"],
party_guid=party_guid,
permit_amendment_guid=permit_amendment_guid)
map_vc.save()

return response

Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@

class VerifiableCredentialCredentialExchangeResource(Resource, UserMixin):
parser = reqparse.RequestParser(trim=True)
parser.add_argument(
'permit_amendment_guid', location='json', type=str, store_missing=False)

parser.add_argument('permit_amendment_guid', location='json', type=str, store_missing=False)

@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
def get(self, party_guid, cred_exch_id):
if not party_guid:
raise BadRequest("party_guid required")
party_credential_exchanges = PartyVerifiableCredentialMinesActPermit.find_by_party_guid(party_guid)

party_credential_exchanges = PartyVerifiableCredentialMinesActPermit.find_by_party_guid(
party_guid)
current_app.logger.info(party_credential_exchanges)
assert cred_exch_id in [str(x.cred_exch_id) for x in party_credential_exchanges], f"cred_exch_id={cred_exch_id} not found"

assert cred_exch_id in [str(x.cred_exch_id) for x in party_credential_exchanges
], f"cred_exch_id={cred_exch_id} not found"

cred_exch_details = TractionService().fetch_credential_exchange(cred_exch_id)

return cred_exch_details
return cred_exch_details
Loading
Loading