Skip to content

Commit

Permalink
Merge branch 'main' into whatscookin/feat/vc-di-proof
Browse files Browse the repository at this point in the history
  • Loading branch information
ianco authored Jun 16, 2024
2 parents e344a1d + 5ad52c1 commit 8208477
Show file tree
Hide file tree
Showing 35 changed files with 1,231 additions and 435 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/integrationtests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defaults:
jobs:
test:
runs-on: ubuntu-latest
if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request')
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request')
outputs:
is_release: ${{ steps.check_if_release.outputs.is_release }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sonar-merge-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: SonarCloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Tests
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/sonar-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download PR number artifact
uses: dawidd6/action-download-artifact@v3
uses: dawidd6/action-download-artifact@v5
with:
workflow: Tests
run_id: ${{ github.event.workflow_run.id }}
Expand All @@ -26,7 +26,7 @@ jobs:
with:
path: ./PR_NUMBER
- name: Download Test Coverage
uses: dawidd6/action-download-artifact@v3
uses: dawidd6/action-download-artifact@v5
with:
workflow: Tests
run_id: ${{ github.event.workflow_run.id }}
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Hyperledger Aries Cloud Agent - Python <!-- omit in toc -->

[![pypi releases](https://img.shields.io/pypi/v/aries_cloudagent)](https://pypi.org/project/aries-cloudagent/)

<!-- ![logo](/doc/assets/aries-cloudagent-python-logo-bw.png) -->
<p float="left">
<a href="https://pypi.org/project/aries-cloudagent/"><img src="https://img.shields.io/pypi/v/aries_cloudagent" width="100" height="20" />
<img src="https://sonarcloud.io/images/project_badges/sonarcloud-white.svg" width="120" height="20" />
<img src="https://sonarcloud.io/api/project_badges/measure?project=hyperledger_aries-cloudagent-python&metric=coverage" width="120" height="20" />
&nbsp;<img src="https://sonarcloud.io/api/project_badges/measure?project=hyperledger_aries-cloudagent-python&metric=security_rating" width="100" height="20" />
&nbsp;<img src="https://sonarcloud.io/api/project_badges/measure?project=hyperledger_aries-cloudagent-python&metric=vulnerabilities" width="120" height="20" />
&nbsp;<img src="https://sonarcloud.io/api/project_badges/measure?project=hyperledger_aries-cloudagent-python&metric=ncloc" width="120" height="20" />
</p>

> An easy to use Aries agent for building SSI services using any language that supports sending/receiving HTTP requests.
Expand Down
20 changes: 10 additions & 10 deletions aries_cloudagent/anoncreds/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,11 @@ async def get_credentials(self, start: int, count: int, wql: dict):

try:
rows = self.profile.store.scan(
CATEGORY_CREDENTIAL,
wql,
start,
count,
self.profile.settings.get("wallet.askar_profile"),
category=CATEGORY_CREDENTIAL,
tag_filter=wql,
offset=start,
limit=count,
profile=self.profile.settings.get("wallet.askar_profile"),
)
async for row in rows:
cred = Credential.load(row.raw_value)
Expand Down Expand Up @@ -424,11 +424,11 @@ async def get_credentials_for_presentation_request_by_referent(
tag_filter = {"$and": [tag_filter, extra_query]}

rows = self.profile.store.scan(
CATEGORY_CREDENTIAL,
tag_filter,
start,
count,
self.profile.settings.get("wallet.askar_profile"),
category=CATEGORY_CREDENTIAL,
tag_filter=tag_filter,
offset=start,
limit=count,
profile=self.profile.settings.get("wallet.askar_profile"),
)
async for row in rows:
if row.name in creds:
Expand Down
95 changes: 86 additions & 9 deletions aries_cloudagent/anoncreds/revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import os
import time
from pathlib import Path
from typing import List, NamedTuple, Optional, Sequence, Tuple
from typing import List, NamedTuple, Optional, Sequence, Tuple, Union
from urllib.parse import urlparse

import base58
Expand All @@ -19,6 +19,7 @@
RevocationRegistryDefinition,
RevocationRegistryDefinitionPrivate,
RevocationStatusList,
W3cCredential,
)
from aries_askar.error import AskarError
from requests import RequestException, Session
Expand Down Expand Up @@ -717,6 +718,7 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef):
backoff=-0.5,
max_attempts=5, # heuristic: respect HTTP timeout
)

if not upload_success:
raise AnonCredsRevocationError(
f"Tails file for rev reg for {rev_reg_def.cred_def_id} "
Expand Down Expand Up @@ -892,16 +894,61 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu

# Credential Operations

async def create_credential_w3c(
self,
w3c_credential_offer: dict,
w3c_credential_request: dict,
w3c_credential_values: dict,
*,
retries: int = 5,
) -> Tuple[str, str, str]:
"""Create a w3c_credential.
Args:
w3c_credential_offer: Credential Offer to create w3c_credential for
w3c_credential_request: Credential request to create w3c_credential for
w3c_credential_values: Values to go in w3c_credential
retries: number of times to retry w3c_credential creation
Returns:
A tuple of created w3c_credential and revocation id
"""
return await self._create_credential_helper(
w3c_credential_offer,
w3c_credential_request,
w3c_credential_values,
W3cCredential,
retries=retries,
)

async def _create_credential(
self,
credential_definition_id: str,
schema_attributes: List[str],
credential_offer: dict,
credential_request: dict,
credential_values: dict,
credential_type: Union[Credential, W3cCredential],
rev_reg_def_id: Optional[str] = None,
tails_file_path: Optional[str] = None,
) -> Tuple[str, str]:
"""Create a credential.
Args:
credential_definition_id: The credential definition ID
schema_attributes: The schema attributes
credential_offer: The credential offer
credential_request: The credential request
credential_values: The credential values
credential_type: The credential type
rev_reg_def_id: The revocation registry definition ID
tails_file_path: The tails file path
Returns:
A tuple of created credential and revocation ID
"""
try:
async with self.profile.session() as session:
cred_def = await session.handle.fetch(
Expand Down Expand Up @@ -1004,14 +1051,13 @@ async def _create_credential(
try:
credential = await asyncio.get_event_loop().run_in_executor(
None,
lambda: Credential.create(
cred_def.raw_value,
cred_def_private.raw_value,
credential_offer,
credential_request,
raw_values,
None,
revoc,
lambda: credential_type.create(
cred_def=cred_def.raw_value,
cred_def_private=cred_def_private.raw_value,
cred_offer=credential_offer,
cred_request=credential_request,
attr_raw_values=raw_values,
revocation_config=revoc,
),
)
except AnoncredsError as err:
Expand Down Expand Up @@ -1039,6 +1085,36 @@ async def create_credential(
Returns:
A tuple of created credential and revocation id
"""
return await self._create_credential_helper(
credential_offer,
credential_request,
credential_values,
Credential,
retries=retries,
)

async def _create_credential_helper(
self,
credential_offer: dict,
credential_request: dict,
credential_values: dict,
credential_type: Union[Credential, W3cCredential],
*,
retries: int = 5,
) -> Tuple[str, str, str]:
"""Create a credential.
Args:
credential_offer: Credential Offer to create credential for
credential_request: Credential request to create credential for
credential_values: Values to go in credential
credential_type: Credential or W3cCredential
retries: number of times to retry credential creation
Returns:
A tuple of created credential and revocation id
"""
issuer = AnonCredsIssuer(self.profile)
anoncreds_registry = self.profile.inject(AnonCredsRegistry)
Expand Down Expand Up @@ -1081,6 +1157,7 @@ async def create_credential(
credential_offer,
credential_request,
credential_values,
credential_type,
rev_reg_def_id,
tails_file_path,
)
Expand Down
88 changes: 88 additions & 0 deletions aries_cloudagent/anoncreds/tests/test_revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
RevocationRegistryDefinitionPrivate,
RevocationStatusList,
Schema,
# AnoncredsError,
# W3cCredential,
# CredentialRevocationConfig,
)
from aries_askar import AskarError, AskarErrorCode
from requests import RequestException, Session
Expand Down Expand Up @@ -1024,6 +1027,7 @@ async def test_create_credential_private_no_rev_reg_or_tails(
"attr1": "value1",
"attr2": "value2",
},
credential_type=Credential,
)
assert mock_create.called

Expand All @@ -1038,6 +1042,7 @@ async def test_create_credential_private_no_rev_reg_or_tails(
credential_offer={},
credential_request={},
credential_values={},
credential_type=Credential,
)

# missing cred def or cred def private
Expand All @@ -1049,6 +1054,7 @@ async def test_create_credential_private_no_rev_reg_or_tails(
credential_offer={},
credential_request={},
credential_values={},
credential_type=Credential,
)
mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None])
with self.assertRaises(test_module.AnonCredsRevocationError):
Expand All @@ -1058,6 +1064,7 @@ async def test_create_credential_private_no_rev_reg_or_tails(
credential_offer={},
credential_request={},
credential_values={},
credential_type=Credential,
)

@mock.patch.object(InMemoryProfileSession, "handle")
Expand Down Expand Up @@ -1086,6 +1093,7 @@ async def call_test_func():
},
rev_reg_def_id="test-rev-reg-def-id",
tails_file_path="tails-file-path",
credential_type=Credential,
)

# missing rev list
Expand Down Expand Up @@ -1380,3 +1388,83 @@ async def test_clear_pending_revocations_with_non_anoncreds_session(self):
await self.revocation.clear_pending_revocations(
self.profile.session(), rev_reg_def_id="test-rev-reg-id"
)

@mock.patch.object(
AnonCredsIssuer, "cred_def_supports_revocation", return_value=True
)
async def test_create_credential_w3c(self, mock_supports_revocation):
self.profile.inject = mock.Mock(
return_value=mock.MagicMock(
get_schema=mock.CoroutineMock(
return_value=GetSchemaResult(
schema_id="CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3",
schema=AnonCredsSchema(
issuer_id="CsQY9MGeD3CQP4EyuVFo5m",
name="MYCO Biomarker:0.0.3",
version="1.0",
attr_names=["attr1", "attr2"],
),
schema_metadata={},
resolution_metadata={},
)
)
)
)
self.revocation.get_or_create_active_registry = mock.CoroutineMock(
return_value=RevRegDefResult(
job_id="test-job-id",
revocation_registry_definition_state=RevRegDefState(
state=RevRegDefState.STATE_FINISHED,
revocation_registry_definition_id="active-reg-reg",
revocation_registry_definition=rev_reg_def,
),
registration_metadata={},
revocation_registry_definition_metadata={},
)
)

# Test private funtion seperately - very large
self.revocation._create_credential = mock.CoroutineMock(
return_value=({"cred": "cred"}, 98)
)

result = await self.revocation.create_credential_w3c(
w3c_credential_offer={
"schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3",
"cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker",
"key_correctness_proof": {},
"nonce": "nonce",
},
w3c_credential_request={},
w3c_credential_values={},
)

assert isinstance(result, tuple)
assert mock_supports_revocation.call_count == 1

@pytest.mark.asyncio
@mock.patch.object(InMemoryProfileSession, "handle")
async def test_create_credential_w3c_keyerror(self, mock_handle):
mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), MockEntry()])
with pytest.raises(test_module.AnonCredsRevocationError) as excinfo:
await self.revocation._create_credential(
credential_definition_id="test-cred-def-id",
schema_attributes=["attr1", "attr2"],
credential_offer={
"schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3",
"cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker",
"key_correctness_proof": {},
"nonce": "nonce",
},
credential_request={},
credential_values={
"X": "value1",
"Y": "value2",
},
credential_type=Credential,
)

assert str(excinfo.value) == (
"Provided credential values are missing a value "
"for the schema attribute 'attr1'"
)
Loading

0 comments on commit 8208477

Please sign in to comment.