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

Fix reliance on temp tails file #590

Merged
merged 4 commits into from
Jul 9, 2020
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
44 changes: 44 additions & 0 deletions aries_cloudagent/indy/tests/test_indy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from os import makedirs
from pathlib import Path
from shutil import rmtree

from asynctest import TestCase as AsyncTestCase, mock as async_mock

import indy.blob_storage

from .. import create_tails_reader, create_tails_writer
from .. import util as test_module_util


class TestIndyUtils(AsyncTestCase):
TAILS_HASH = "8UW1Sz5cqoUnK9hqQk7nvtKK65t7Chu3ui866J23sFyJ"

def tearDown(self):
tails_dir = test_module_util.indy_client_dir("tails", create=False)
rmtree(tails_dir, ignore_errors=True)

async def test_tails_reader(self):
tails_dir = test_module_util.indy_client_dir("tails", create=True)
tails_local = f"{tails_dir}/{TestIndyUtils.TAILS_HASH}"

with open(tails_local, "a") as f:
print("1234123412431234", file=f)

with async_mock.patch.object(
indy.blob_storage, "open_reader", async_mock.CoroutineMock()
) as mock_blob_open_reader:
result = await create_tails_reader(tails_local)
assert result == mock_blob_open_reader.return_value

rmtree(tails_dir, ignore_errors=True)
with self.assertRaises(FileNotFoundError):
await create_tails_reader(tails_local)

async def test_tails_writer(self):
tails_dir = test_module_util.indy_client_dir("tails", create=True)
assert await create_tails_writer(tails_dir)

rmtree(tails_dir, ignore_errors=True)

async def test_nonce(self):
assert await test_module_util.generate_pr_nonce()
39 changes: 0 additions & 39 deletions aries_cloudagent/indy/tests/test_utils.py

This file was deleted.

32 changes: 32 additions & 0 deletions aries_cloudagent/indy/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
"""Libindy utility functions."""

from os import getenv, makedirs
from os.path import isdir, join
from pathlib import Path
from platform import system

from indy.anoncreds import generate_nonce


async def generate_pr_nonce():
"""Generate a nonce for a proof request."""
return await generate_nonce()


def indy_client_dir(subpath: str = None, create: bool = False) -> str:
"""
Return '/'-terminated subdirectory of indy-client directory.

Args:
subpath: subpath within indy-client structure
create: whether to create subdirectory if absent
"""

home = Path.home()
target_dir = join(
home,
"Documents"
if isdir(join(home, "Documents"))
else getenv("EXTERNAL_STORAGE", "")
if system() == "Linux"
else "",
".indy_client",
subpath if subpath else "",
"", # set trailing separator
)
if create:
makedirs(target_dir, exist_ok=True)

return target_dir
7 changes: 4 additions & 3 deletions aries_cloudagent/protocols/issue_credential/v1_0/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,7 @@ async def store_credential(

if revoc_reg_def:
revoc_reg = RevocationRegistry.from_definition(revoc_reg_def, True)
if not revoc_reg.has_local_tails_file(self.context):
await revoc_reg.retrieve_tails(self.context)

await revoc_reg.get_or_fetch_local_tails_path()
try:
credential_id = await holder.store_credential(
credential_definition,
Expand Down Expand Up @@ -726,6 +724,9 @@ async def revoke_credential(
)

if publish:
rev_reg = await revoc.get_ledger_registry(rev_reg_id)
await rev_reg.get_or_fetch_local_tails_path()

# pick up pending revocations on input revocation registry
crids = list(set(registry_record.pending_pub + [cred_rev_id]))
(delta_json, _) = await issuer.revoke_credentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1186,8 +1186,7 @@ async def test_store_credential(self):

mock_rev_reg.from_definition = async_mock.MagicMock(
return_value=async_mock.MagicMock(
has_local_tails_file=async_mock.MagicMock(return_value=False),
retrieve_tails=async_mock.CoroutineMock(return_value=None),
get_or_fetch_local_tails_path=async_mock.CoroutineMock()
)
)
ret_exchange, ret_cred_ack = await self.manager.store_credential(
Expand Down Expand Up @@ -1379,9 +1378,15 @@ async def test_revoke_credential_publish(self):
publish_registry_entry=async_mock.CoroutineMock(),
clear_pending=async_mock.CoroutineMock(),
)
mock_rev_reg = async_mock.MagicMock(
get_or_fetch_local_tails_path=async_mock.CoroutineMock()
)
revoc.return_value.get_issuer_rev_reg_record = async_mock.CoroutineMock(
return_value=mock_issuer_rev_reg_record
)
revoc.return_value.get_ledger_registry = async_mock.CoroutineMock(
return_value=mock_rev_reg
)

issuer = async_mock.MagicMock(BaseIssuer, autospec=True)
issuer.revoke_credentials = async_mock.CoroutineMock(
Expand Down
2 changes: 1 addition & 1 deletion aries_cloudagent/protocols/present_proof/v1_0/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ async def create_presentation(
revocation_states[rev_reg_id] = {}

rev_reg = revocation_registries[rev_reg_id]
tails_local_path = await rev_reg.get_or_fetch_local_tails_path(self.context)
tails_local_path = await rev_reg.get_or_fetch_local_tails_path()

try:
revocation_states[rev_reg_id][delta_timestamp] = json.loads(
Expand Down
15 changes: 12 additions & 3 deletions aries_cloudagent/revocation/models/issuer_rev_reg_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import logging
import uuid

from os.path import join
from shutil import move
from typing import Any, Sequence
from urllib.parse import urlparse

from marshmallow import fields, validate

from ...config.injection_context import InjectionContext
from ...indy.util import indy_client_dir
from ...issuer.base import BaseIssuer, IssuerError
from ...messaging.models.base_record import BaseRecord, BaseRecordSchema
from ...messaging.valid import (
Expand Down Expand Up @@ -132,7 +135,7 @@ def _check_url(self, url) -> None:
if not (parsed.scheme and parsed.netloc and parsed.path):
raise RevocationError("URI {} is not a valid URL".format(url))

async def generate_registry(self, context: InjectionContext, base_dir: str):
async def generate_registry(self, context: InjectionContext):
"""Create the credential registry definition and tails file."""
if not self.tag:
self.tag = self._id or str(uuid.uuid4())
Expand All @@ -145,6 +148,7 @@ async def generate_registry(self, context: InjectionContext, base_dir: str):
)

issuer: BaseIssuer = await context.inject(BaseIssuer)
tails_hopper_dir = indy_client_dir(join("tails", ".hopper"), create=True)

LOGGER.debug("create revocation registry with size:", self.max_cred_num)

Expand All @@ -159,7 +163,7 @@ async def generate_registry(self, context: InjectionContext, base_dir: str):
self.revoc_def_type,
self.tag,
self.max_cred_num,
base_dir,
tails_hopper_dir,
self.issuance_type,
)
except IssuerError as err:
Expand All @@ -170,7 +174,12 @@ async def generate_registry(self, context: InjectionContext, base_dir: str):
self.revoc_reg_entry = json.loads(revoc_reg_entry_json)
self.state = IssuerRevRegRecord.STATE_GENERATED
self.tails_hash = self.revoc_reg_def["value"]["tailsHash"]
self.tails_local_path = self.revoc_reg_def["value"]["tailsLocation"]

tails_dir = indy_client_dir(join("tails", self.revoc_reg_id), create=True)
tails_path = join(tails_dir, self.tails_hash)
move(join(tails_hopper_dir, self.tails_hash), tails_path)
self.tails_local_path = tails_path

await self.save(context, reason="Generated registry")

async def set_tails_file_public_uri(
Expand Down
41 changes: 18 additions & 23 deletions aries_cloudagent/revocation/models/revocation_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import logging
import re

from os.path import join
from pathlib import Path
from tempfile import gettempdir

from requests import Session
from requests.exceptions import RequestException

from ...config.injection_context import InjectionContext
from ...utils.temp import get_temp_dir
from ...indy.util import indy_client_dir

from ..error import RevocationError
import hashlib
Expand Down Expand Up @@ -53,6 +52,8 @@ def from_definition(
cls, revoc_reg_def: dict, public_def: bool
) -> "RevocationRegistry":
"""Initialize a revocation registry instance from a definition."""
rev_reg = None

reg_id = revoc_reg_def["id"]
tails_location = revoc_reg_def["value"]["tailsLocation"]
issuer_did_match = re.match(r"^.*?([^:]*):3:CL:.*", revoc_reg_def["credDefId"])
Expand All @@ -68,16 +69,13 @@ def from_definition(
}
if public_def:
init["tails_public_uri"] = tails_location
rev_reg = cls(reg_id, **init) # currently ignored: def version, public keys
rev_reg.tails_local_path = rev_reg.get_receiving_tails_local_path()
else:
init["tails_local_path"] = tails_location
rev_reg = cls(reg_id, **init) # currently ignored: def version, public keys

# currently ignored - definition version, public keys
return cls(reg_id, **init)

@classmethod
def get_temp_dir(cls) -> str:
"""Accessor for the temp directory."""
return get_temp_dir("revoc")
return rev_reg

@property
def cred_def_id(self) -> str:
Expand Down Expand Up @@ -139,23 +137,20 @@ def tails_public_uri(self, new_uri: str):
"""Setter for the tails file public URI."""
self._tails_public_uri = new_uri

def get_receiving_tails_local_path(self, context: InjectionContext):
def get_receiving_tails_local_path(self):
"""Make the local path to the tails file we download from remote URI."""
if self._tails_local_path:
return self._tails_local_path

tails_file_dir = context.settings.get(
"holder.revocation.tails_files.path",
Path(gettempdir(), "indy", "revocation", "tails_files"),
)
return str(Path(tails_file_dir).joinpath(self._tails_hash))
tails_dir = indy_client_dir(join("tails", self.registry_id), create=False)
return join(tails_dir, self._tails_hash)

def has_local_tails_file(self, context: InjectionContext) -> bool:
def has_local_tails_file(self) -> bool:
"""Test if the tails file exists locally."""
tails_file_path = Path(self.get_receiving_tails_local_path(context))
tails_file_path = Path(self.get_receiving_tails_local_path())
return tails_file_path.is_file()

async def retrieve_tails(self, context: InjectionContext):
async def retrieve_tails(self):
"""Fetch the tails file from the public URI."""
if not self._tails_public_uri:
raise RevocationError("Tails file public URI is empty")
Expand All @@ -165,7 +160,7 @@ async def retrieve_tails(self, context: InjectionContext):
self.registry_id,
)

tails_file_path = Path(self.get_receiving_tails_local_path(context))
tails_file_path = Path(self.get_receiving_tails_local_path())
tails_file_dir = tails_file_path.parent
if not tails_file_dir.exists():
tails_file_dir.mkdir(parents=True)
Expand All @@ -192,12 +187,12 @@ async def retrieve_tails(self, context: InjectionContext):
self.tails_local_path = tails_file_path
return self.tails_local_path

async def get_or_fetch_local_tails_path(self, context: InjectionContext):
async def get_or_fetch_local_tails_path(self):
"""Get the local tails path, retrieving from the remote if necessary."""
tails_file_path = self.get_receiving_tails_local_path(context)
tails_file_path = self.get_receiving_tails_local_path()
if Path(tails_file_path).is_file():
return tails_file_path
return await self.retrieve_tails(context)
return await self.retrieve_tails()

def __repr__(self) -> str:
"""Return a human readable representation of this class."""
Expand Down
Loading