Skip to content

Commit cf8f1ac

Browse files
Paul Hewletteccles
authored andcommitted
Example code for sharing an asset
Solution: Example code for sharing an asset using access policies. Functional test for asset sharing. Jupyter notebook for asset sharing. Signed-off-by: Paul Hewlett <[email protected]>
1 parent aa7f0ec commit cf8f1ac

File tree

16 files changed

+1015
-80
lines changed

16 files changed

+1015
-80
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ dist/
88
*,cover
99
htmlcov/
1010
coverage.xml
11+
.ipython/
12+
.jupyter/
1113
.cache/
1214
.local/
1315
.python_history

README.rst

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,40 @@ You can then use the examples code to create assets (see examples directory):
149149
main()
150150
151151
152+
Notebooks
153+
=================
154+
155+
Some jupyter notebooks are available to exercise the examples code. Do the following:
156+
157+
.. code:: text
158+
159+
task builder # only necessary first time checking out repo.
160+
./scripts/notebooks.sh
161+
162+
Something similar to the following will be emitted:
163+
164+
.. code:: text
165+
166+
[I 2022-07-13 15:29:32.102 LabApp] JupyterLab extension loaded from /usr/local/lib/python3.7/site-packages/jupyterlab
167+
[I 2022-07-13 15:29:32.102 LabApp] JupyterLab application directory is /usr/local/share/jupyter/lab
168+
[I 15:29:32.106 NotebookApp] Serving notebooks from local directory: /home/builder/notebooks
169+
[I 15:29:32.107 NotebookApp] Jupyter Notebook 6.4.12 is running at:
170+
[I 15:29:32.107 NotebookApp] http://d77e9e7ff8a1:8888/?token=e51c0b5120f5cf76e06c0415896aee13b0849830a9c8c83f
171+
[I 15:29:32.107 NotebookApp] or http://127.0.0.1:8888/?token=e51c0b5120f5cf76e06c0415896aee13b0849830a9c8c83f
172+
[I 15:29:32.107 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
173+
[C 15:29:32.110 NotebookApp]
174+
175+
To access the notebook, open this file in a browser:
176+
file:///home/builder/.local/share/jupyter/runtime/nbserver-1-open.html
177+
Or copy and paste one of these URLs:
178+
http://d77e9e7ff8a1:8888/?token=e51c0b5120f5cf76e06c0415896aee13b0849830a9c8c83f
179+
or http://127.0.0.1:8888/?token=e51c0b5120f5cf76e06c0415896aee13b0849830a9c8c83f
180+
181+
Cut and paste one of the links given into your browser and use the various dialogs to select an available notebook
182+
or create a new notebook and follow current code in examples directory.
183+
Please note that security tokens will be required - either a JWT or Application ID and secret and these should be stored in files in the local credentials directory.
184+
185+
152186
File Story Runner
153187
=================
154188

@@ -200,7 +234,7 @@ Python
200234
Command Line
201235
------------
202236
203-
This functionality is also available with the CLI tool :code:`archivist_runner`, which is bundled with version v0.10 onwards of the :code:`jitsuin-archivist` PIP installation.
237+
This functionality is also available with the CLI tool :code:`archivist_runner`, which is bundled with version v0.10 onwards of the :code:`jitsuin-archivist`.
204238
205239
You can verify the installation by running the following:
206240

Taskfile.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ tasks:
7272
cmds:
7373
- ./scripts/builder.sh ./scripts/functests.sh
7474

75+
notebooks:
76+
desc: Run jupyter notebooks
77+
deps: [about]
78+
cmds:
79+
- ./scripts/notebooks.sh
80+
7581
publish:
7682
desc: publish wheel package (will require username and password)
7783
deps: [about]

archivist/archivist.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ def __str__(self) -> str:
154154
def __getattr__(self, value: str):
155155
"""Create endpoints on demand"""
156156
LOGGER.debug("getattr %s", value)
157-
LOGGER.debug("CLIENTS %s", self.CLIENTS)
158157
client = self.CLIENTS.get(value)
159158

160159
if client is None:

archivist/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@
6868

6969
SUBJECTS_SUBPATH = "iam/v1"
7070
SUBJECTS_LABEL = "subjects"
71+
SUBJECTS_SELF_ID = "subjects/00000000-0000-0000-0000-000000000000"

archivist/subjects.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ def create(
9090
),
9191
)
9292

93+
def import_subject(self, name: str, subject: Subject) -> Subject:
94+
"""Create subject from another subject usually
95+
from another organization.
96+
97+
Args:
98+
name (str): of the subject
99+
subject (Subject): Subject object
100+
101+
Returns:
102+
:class:`Subject` instance
103+
104+
"""
105+
return self.create(
106+
name,
107+
subject["wallet_pub_key"],
108+
subject["tessera_pub_key"],
109+
)
110+
93111
def create_from_data(self, data: Dict) -> Subject:
94112
"""Create subject
95113

docs/getting_started.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ See the examples and functests directories.
1111

1212
create_asset
1313
get_asset
14+
sharing_asset
15+
1416
get_publicasset
1517
get_publicevent
1618

docs/sharing_asset.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. _sharing_assetref:
2+
3+
Sharing Asset Example
4+
.....................
5+
6+
.. literalinclude:: ../examples/sharing_asset.py
7+
:language: python
8+
9+
10+

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ config
77
cyclonedx
88
dataclass
99
dataclasses
10+
dialogs
1011
dicts
1112
ef
1213
fd

examples/sharing_asset.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"""Create an asset for Archivist with token.
2+
3+
Create an access_policy that shares an asset when certain criteria are met.
4+
5+
Access the asset from another Archivist connection using a second token with different
6+
access rights.
7+
"""
8+
9+
from json import dumps as json_dumps
10+
from os import getenv
11+
12+
from archivist.archivist import Archivist
13+
from archivist.constants import ASSET_BEHAVIOURS, SUBJECTS_SELF_ID
14+
from archivist.logger import set_logger
15+
from archivist.proof_mechanism import ProofMechanism
16+
from archivist.utils import get_auth
17+
18+
19+
def create_example_asset(arch, label):
20+
"""Create an asset using Archivist Connection.
21+
22+
Args:
23+
arch: archivist connection.
24+
label: convenience label to easily distinguish the 2 organizations.
25+
26+
Returns:
27+
Asset: a new asset created.
28+
29+
"""
30+
attrs = {
31+
"arc_display_name": f"{label}_display_name", # Asset's display name
32+
"arc_description": f"{label}_display_description", # Asset's description
33+
"arc_display_type": f"{label}_display_type", # Arc_display_type is a free text field
34+
"ext_vendor_name": label,
35+
}
36+
37+
# Select the mechanism used to prove evidence for the asset. If the selected proof
38+
# mechanism is not enabled for your tenant then an error will occur.
39+
# If unspecified then SIMPLE_HASH is used.
40+
# proof_mechanism = ProofMechanism.KHIPU.name
41+
#
42+
props = {
43+
"proof_mechanism": ProofMechanism.KHIPU.name,
44+
}
45+
46+
# The first argument is the properties of the asset
47+
# The second argument is the attributes of the asset
48+
# The third argument is wait for confirmation:
49+
# If @confirm@ is True then this function will not
50+
# return until the asset is confirmed on the blockchain and ready
51+
# to accept events (or an error occurs)
52+
#
53+
return arch.assets.create(props=props, attrs=attrs, confirm=True)
54+
55+
56+
def create_archivist(label):
57+
"""Create connection to archivist"""
58+
# Get authorization token. The token grants certain rights and access permissions.
59+
# The token can represent the root principal or user in an organization. Different tokens
60+
# could indicate different users in the same organization or membership of different
61+
# organiastions.
62+
auth = get_auth(
63+
auth_token=getenv(f"ARCHIVIST_AUTHTOKEN_{label}"),
64+
)
65+
# Initialize connection to Archivist. max_time is the time to wait for confirmation
66+
# of an asset or event creation - the default is 1200 seconds but one can optionally
67+
# specify a different value here particularly when creating assets on SIMPLE_HASH
68+
# (rather than KHIPU) as confirmation times are much shorter in this case.
69+
return Archivist(
70+
"https://app.rkvst.io",
71+
auth,
72+
max_time=300,
73+
)
74+
75+
76+
def import_subject(acme, weyland):
77+
"""Add subjects record for weyland in acme's environment"""
78+
subject = acme.subjects.import_subject(
79+
"weyland",
80+
weyland.subjects.read(SUBJECTS_SELF_ID),
81+
)
82+
83+
# must wait for confirmation
84+
acme.subjects.wait_for_confirmation(subject["identity"])
85+
86+
return subject
87+
88+
89+
def create_example_access_policy(arch, label, subject):
90+
"""Create access policy"""
91+
# consists of a filter selection entry and a selection criteria to restrict/redact
92+
# values of the asset attributes available to the sharee.
93+
94+
# values pertaining to the access polcy itself.
95+
props = {
96+
"display_name": f"{label} access policy",
97+
"description": f"{label} Policy description",
98+
}
99+
100+
# Filtering - access will be allowed to any asset that contains both these
101+
# attributes that equal these values. This happens to match the asset created
102+
# previously.
103+
filters = [
104+
{
105+
"or": [
106+
f"attributes.arc_display_type={label}_display_type",
107+
]
108+
},
109+
{
110+
"or": [
111+
f"attributes.ext_vendor_name={label}",
112+
]
113+
},
114+
]
115+
116+
# one must be the subject to gain access and only those fields
117+
# specified in include_attributes will be emitted.
118+
access_permissions = [
119+
{
120+
"subjects": [
121+
subject["identity"],
122+
],
123+
"behaviours": ASSET_BEHAVIOURS,
124+
"include_attributes": [
125+
"arc_display_name",
126+
],
127+
},
128+
]
129+
130+
return arch.access_policies.create(
131+
props,
132+
filters,
133+
access_permissions,
134+
)
135+
136+
137+
def main():
138+
"""Main function of share-asset."""
139+
# optional call to set the logger level for all subsystems. The argument can
140+
# be either "INFO" or "DEBUG". For more sophisticated logging control see the
141+
# documentation.
142+
set_logger("INFO")
143+
144+
# For demonstration purposes we are going to assume that 2 organizations are
145+
# going to share an asset. The 2 organizations are ACME Corp and Weyland-Yutani
146+
# Corporation.
147+
acme = create_archivist("acme")
148+
weyland = create_archivist("weyland")
149+
150+
# set a subject for weyland in acme's environment. The identity will be used as a
151+
# filter in the access permissions of the access_policy.
152+
weyland_subject_on_acme = import_subject(acme, weyland)
153+
print("weyland_subject on acme", json_dumps(weyland_subject_on_acme, indent=4))
154+
155+
# acme creates an asset
156+
acme_asset = create_example_asset(acme, "acme")
157+
print("asset created in acme", json_dumps(acme_asset, indent=4))
158+
159+
# now we want acme to share this asset to weyland via an access policy.
160+
access_policy = create_example_access_policy(acme, "acme", weyland_subject_on_acme)
161+
print("access policy created in acme", json_dumps(access_policy, indent=4))
162+
163+
# display the asset as retrieved by weyland
164+
# NB: the attributes dict is redacted...
165+
weyland_asset = weyland.assets.read(acme_asset["identity"])
166+
print("asset read from weyland", json_dumps(weyland_asset, indent=4))
167+
168+
# list matching access policies
169+
access_policies = list(
170+
acme.access_policies.list_matching_access_policies(acme_asset["identity"])
171+
)
172+
print("access policies read from acme", json_dumps(access_policies, indent=4))
173+
174+
# delete all the access policies
175+
for access_policy in access_policies:
176+
acme.access_policies.delete(access_policy["identity"])
177+
178+
# list matching access policies
179+
access_policies = list(
180+
acme.access_policies.list_matching_access_policies(acme_asset["identity"])
181+
)
182+
print("access policies read from acme", json_dumps(access_policies, indent=4))
183+
184+
# display the asset as retrieved by the sharee
185+
# NB the asset is still shared even though there are no access policies
186+
weyland_asset = weyland.assets.read(acme_asset["identity"])
187+
print("asset read from weyland", json_dumps(weyland_asset, indent=4))
188+
189+
190+
if __name__ == "__main__":
191+
main()

0 commit comments

Comments
 (0)