-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #238 from blockchain-certificates/feat/validate_me…
…tadata Feat/validate metadata
- Loading branch information
Showing
11 changed files
with
382 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = '3.1.0' | ||
__version__ = '3.2.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import json | ||
from abc import abstractmethod | ||
from cert_issuer.config import ESTIMATE_NUM_INPUTS | ||
from cert_issuer.models.verifiable_credential import verify_credential, verify_presentation, validate_type, validate_context | ||
from cert_issuer.models.metadata import validate_metadata_structure | ||
|
||
class BatchHandler(object): | ||
def __init__(self, secret_manager, certificate_handler, merkle_tree, config): | ||
self.certificate_handler = certificate_handler | ||
self.secret_manager = secret_manager | ||
self.merkle_tree = merkle_tree | ||
self.config = config | ||
|
||
@abstractmethod | ||
def pre_batch_actions(self, config): | ||
pass | ||
|
||
@abstractmethod | ||
def post_batch_actions(self, config): | ||
pass | ||
|
||
def set_certificates_in_batch(self, certificates_to_issue): | ||
self.certificates_to_issue = certificates_to_issue | ||
|
||
|
||
class CertificateHandler(object): | ||
@abstractmethod | ||
def validate_certificate(self, certificate_metadata): | ||
validate_type(certificate_metadata['type']) | ||
validate_context(certificate_metadata['@context'], certificate_metadata['type']) | ||
|
||
if 'metadata' in certificate_metadata: | ||
validate_metadata_structure(json.loads(certificate_metadata['metadata'])) | ||
|
||
if (certificate_metadata['type'][0] == 'VerifiableCredential'): | ||
verify_credential(certificate_metadata) | ||
|
||
if (certificate_metadata['type'][0] == 'VerifiablePresentation'): | ||
verify_presentation(certificate_metadata) | ||
|
||
pass | ||
|
||
@abstractmethod | ||
def sign_certificate(self, signer, certificate_metadata): | ||
pass | ||
|
||
@abstractmethod | ||
def get_byte_array_to_issue(self, certificate_metadata): | ||
pass | ||
|
||
@abstractmethod | ||
def add_proof(self, certificate_metadata, merkle_proof): | ||
pass | ||
|
||
|
||
class ServiceProviderConnector(object): | ||
@abstractmethod | ||
def get_balance(self, address): | ||
pass | ||
|
||
def broadcast_tx(self, tx): | ||
pass | ||
|
||
|
||
class Signer(object): | ||
""" | ||
Abstraction for a component that can sign. | ||
""" | ||
|
||
def __init__(self): | ||
pass | ||
|
||
@abstractmethod | ||
def sign_message(self, wif, message_to_sign): | ||
pass | ||
|
||
@abstractmethod | ||
def sign_transaction(self, wif, transaction_to_sign): | ||
pass | ||
|
||
|
||
class SecretManager(object): | ||
def __init__(self, signer): | ||
self.signer = signer | ||
self.wif = None | ||
|
||
@abstractmethod | ||
def start(self): | ||
pass | ||
|
||
@abstractmethod | ||
def stop(self): | ||
pass | ||
|
||
def sign_message(self, message_to_sign): | ||
return self.signer.sign_message(self.wif, message_to_sign) | ||
|
||
def sign_transaction(self, transaction_to_sign): | ||
return self.signer.sign_transaction(self.wif, transaction_to_sign) | ||
|
||
|
||
class TransactionHandler(object): | ||
@abstractmethod | ||
def ensure_balance(self): | ||
pass | ||
|
||
@abstractmethod | ||
def issue_transaction(self, blockchain_bytes): | ||
pass | ||
|
||
|
||
class MockTransactionHandler(TransactionHandler): | ||
def ensure_balance(self): | ||
pass | ||
|
||
def issue_transaction(self, op_return_bytes): | ||
return 'This has not been issued on a blockchain and is for testing only' | ||
|
||
|
||
class TransactionCreator(object): | ||
@abstractmethod | ||
def estimate_cost_for_certificate_batch(self, tx_cost_constants, num_inputs=ESTIMATE_NUM_INPUTS): | ||
pass | ||
|
||
@abstractmethod | ||
def create_transaction(self, tx_cost_constants, issuing_address, inputs, op_return_value): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import logging | ||
|
||
from jsonschema import validate | ||
from copy import copy | ||
|
||
|
||
def validate_metadata_structure(metadata): | ||
if 'schema' in metadata: | ||
try: | ||
json_object = copy(metadata) | ||
del json_object['schema'] | ||
validate(instance=json_object, schema=metadata['schema']) | ||
except Exception as e: | ||
print(e) | ||
raise Exception('Certificate.metadata object does not match its provided schema') | ||
else: | ||
logging.warning(""" | ||
The metadata object provided with the certificate does not include a `schema` property. | ||
Not defining such property will result in errors in the rendering of the metadata property in the UI projects. | ||
""") | ||
|
||
if 'displayOrder' not in metadata: | ||
logging.warning(""" | ||
The metadata object provided with the certificate does not include a `displayOrder` property. | ||
Not defining such property will result in errors in the rendering of the metadata property in the UI projects. | ||
""") | ||
return | ||
else: | ||
verify_display_order_properties(metadata) | ||
|
||
|
||
def verify_display_order_properties(metadata): | ||
display_order = metadata['displayOrder'] | ||
checked_groups = [] | ||
for item in display_order: | ||
path = item.split('.') | ||
group = path[0] | ||
if group not in metadata: | ||
if group not in checked_groups: | ||
# \033[1m%s\033[0m: display property name in bold | ||
logging.warning( | ||
"`metadata.displayOrder` property references a group named: \033[1m%s\033[0m which does not exist in metadata object.", | ||
group | ||
) | ||
checked_groups.append(group) | ||
else: | ||
property = path[1] | ||
if property not in metadata[group]: | ||
logging.warning( | ||
"`metadata.displayOrder` property references a property named: \033[1m%s\033[0m which does not exist in group: \033[1m%s\033[0m.", | ||
property, | ||
group | ||
) | ||
else: | ||
verify_title_is_set(property, group, metadata) | ||
|
||
pass | ||
|
||
|
||
def verify_title_is_set(property, group, metadata): | ||
if 'schema' not in metadata: | ||
return | ||
schema = metadata['schema'] | ||
|
||
if 'title' not in schema['properties'][group]['properties'][property]: | ||
logging.warning( | ||
"""No title has been defined for property: \x1b[1m{0}\x1b[0m in group: \x1b[1m{1}\x1b[0m. | ||
Title should be defined under path `schema.properties.{1}.properties.{0}.title`""".format(property, group) | ||
) | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
tests/models/test_integration_verify_credential_metadata.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import unittest | ||
import mock | ||
import copy | ||
|
||
from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV3Handler | ||
from cert_issuer.models import CertificateHandler | ||
|
||
credential_example = { | ||
"@context": [ | ||
"https://www.w3.org/2018/credentials/v1", | ||
"https://www.w3.org/2018/credentials/examples/v1", | ||
"https://www.blockcerts.org/schema/3.0/context.json" | ||
], | ||
"id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", | ||
"type": [ | ||
"VerifiableCredential", | ||
"BlockcertsCredential" | ||
], | ||
"issuer": "https://raw.githubusercontent.com/AnthonyRonning/https-github.com-labnol-files/master/issuer-eth.json", | ||
"issuanceDate": "2010-01-01T19:33:24Z", | ||
"metadata": "{\"schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"type\":\"object\",\"properties\":{\"displayOrder\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"certificate\":{\"order\":[],\"type\":\"object\",\"properties\":{\"issuingInstitution\":{\"title\":\"Issuing Institution\",\"type\":\"string\",\"default\":\"Learning Machine Technologies, Inc.\"}}},\"recipient\":{}}},\"certificate\":{\"issuingInstitution\":\"Learning Machine Technologies, Inc.\"},\"recipient\":{},\"displayOrder\":{\"certificate.issuingInstitution\":1}}", | ||
"credentialSubject": { | ||
"id": "did:key:z6Mkq3L1jEDDZ5R7eT523FMLxC4k6MCpzqD7ff1CrkWpoJwM", | ||
"alumniOf": { | ||
"id": "did:example:c276e12ec21ebfeb1f712ebc6f1" | ||
} | ||
} | ||
} | ||
|
||
class TestIntegrationCredentialMetadata (unittest.TestCase): | ||
def test_verify_metadata_invalid (self): | ||
handler = CertificateBatchHandler( | ||
secret_manager=mock.Mock(), | ||
certificate_handler=MockCertificateV3Handler(credential_example), | ||
merkle_tree=mock.Mock(), | ||
config=mock.Mock() | ||
) | ||
handler.certificates_to_issue = {'metadata': mock.Mock()} | ||
|
||
try: | ||
handler.prepare_batch() | ||
except Exception as e: | ||
self.assertEqual(str(e), 'Certificate.metadata object does not match its provided schema') | ||
return | ||
|
||
assert False | ||
|
||
class MockCertificateV3Handler(CertificateV3Handler): | ||
def __init__(self, test_certificate): | ||
self.test_certificate = test_certificate | ||
print(self.test_certificate) | ||
def _get_certificate_to_issue(self, data): | ||
return self.test_certificate | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
Oops, something went wrong.