Skip to content

Commit f73cc93

Browse files
Protocol to fetch Data Controller details.
Signed-off-by: George J Padayatti <[email protected]>
1 parent 06b527d commit f73cc93

8 files changed

+507
-5
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from aries_cloudagent.messaging.base_handler import BaseHandler, BaseResponder, RequestContext
2+
3+
from ..messages.data_controller_details import DataControllerDetailsMessage
4+
from ..manager import ADAManager
5+
6+
import json
7+
8+
9+
class DataControllerDetailsHandler(BaseHandler):
10+
"""Handle for data-controller/1.0/details message"""
11+
12+
async def handle(self, context: RequestContext, responder: BaseResponder):
13+
"""
14+
Message handler logic for data-controller/1.0/details message.
15+
"""
16+
17+
# Assert if received message is of type DataControllerDetailsMessage
18+
assert isinstance(context.message, DataControllerDetailsMessage)
19+
20+
self._logger.info(
21+
"Received data-controller/1.0/details message: \n%s",
22+
json.dumps(context.message.serialize(), indent=4)
23+
)
24+
25+
# Initialize ADA manager
26+
ada_manager = ADAManager(context)
27+
28+
# Call the function
29+
30+
await ada_manager.process_data_controller_details_message(
31+
data_controller_details_message=context.message,
32+
receipt=context.message_receipt,
33+
)
34+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from aries_cloudagent.messaging.base_handler import BaseHandler, BaseResponder, RequestContext
2+
3+
from ..messages.data_controller_details_response import DataControllerDetailsResponseMessage
4+
5+
import json
6+
7+
8+
class DataControllerDetailsResponseHandler(BaseHandler):
9+
"""Handle for data-controller/1.0/details-response message"""
10+
11+
async def handle(self, context: RequestContext, responder: BaseResponder):
12+
"""
13+
Message handler logic for data-controller/1.0/details-response message.
14+
"""
15+
16+
# Assert if received message is of type DataControllerDetailsResponseMessage
17+
assert isinstance(
18+
context.message, DataControllerDetailsResponseMessage)
19+
20+
self._logger.info(
21+
"Received data-controller/1.0/details-response message: \n%s",
22+
json.dumps(context.message.serialize(), indent=4)
23+
)

mydata_did/v1_0/manager.py

Lines changed: 184 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
from .messages.json_ld_problem_report import JSONLDProblemReport, JSONLDProblemReportReason
6868
from .messages.read_all_data_agreement_template import ReadAllDataAgreementTemplateMessage
6969
from .messages.read_all_data_agreement_template_response import ReadAllDataAgreementTemplateResponseMessage
70+
from .messages.data_controller_details import DataControllerDetailsMessage
71+
from .messages.data_controller_details_response import DataControllerDetailsResponseMessage
7072

7173
from .models.data_agreement_model import DATA_AGREEMENT_V1_SCHEMA_CONTEXT, DataAgreementEventSchema, DataAgreementV1, DataAgreementPersonalData, DataAgreementV1Schema
7274
from .models.read_data_agreement_model import ReadDataAgreementBody
@@ -85,6 +87,7 @@
8587
from .models.data_agreement_qr_code_initiate_model import DataAgreementQrCodeInitiateBody
8688
from .models.json_ld_processed_response_model import JSONLDProcessedResponseBody
8789
from .models.json_ld_processed_model import JSONLDProcessedBody
90+
from .models.data_controller_model import DataController, DataControllerSchema
8891

8992
from .utils.diddoc import DIDDoc
9093
from .utils.did.mydata_did import DIDMyData
@@ -140,6 +143,9 @@ class ADAManager:
140143
# Temporary record for keeping personal data of unpublished (or draft) data agreements
141144
RECORD_TYPE_TEMPORARY_DATA_AGREEMENT_PERSONAL_DATA = "temporary_data_agreement_personal_data"
142145

146+
# Record for data controller details
147+
RECORD_TYPE_DATA_CONTROLLER_DETAILS = "data_controller_details"
148+
143149
DATA_AGREEMENT_RECORD_TYPE = "dataagreement_record"
144150

145151
def __init__(self, context: InjectionContext) -> None:
@@ -3862,7 +3868,6 @@ async def create_invitation(
38623868
# Make request to iGrant.io organisation detail endpoint
38633869
async with aiohttp.ClientSession(headers=request_headers) as session:
38643870
async with session.get(igrantio_organisation_detail_url) as resp:
3865-
print(await resp.text())
38663871
if resp.status == 200:
38673872
jresp = await resp.json()
38683873

@@ -4093,3 +4098,181 @@ async def send_read_all_data_agreement_template_message(self, conn_id: str) -> N
40934098
# Send JSONLD Processed Message
40944099
if responder:
40954100
await responder.send_reply(read_all_data_agreement_template_message, connection_id=connection_record.connection_id)
4101+
4102+
4103+
async def fetch_org_details_from_igrantio(self)-> str:
4104+
"""
4105+
Fetch org details from iGrant.io.
4106+
"""
4107+
4108+
# fetch iGrant.io config from os environment
4109+
igrantio_config = await self.fetch_igrantio_config_from_os_environ()
4110+
4111+
# Construct iGrant.io organisation detail endpoint URL
4112+
igrantio_organisation_detail_url = f"{igrantio_config['igrantio_endpoint_url']}/v1/organizations/{igrantio_config['igrantio_org_id']}"
4113+
4114+
# Construct request headers
4115+
request_headers = {
4116+
"Authorization": f"ApiKey {igrantio_config['igrantio_org_api_key']}"
4117+
}
4118+
4119+
data_controller = json.dumps({})
4120+
4121+
async with aiohttp.ClientSession(headers=request_headers) as session:
4122+
async with session.get(igrantio_organisation_detail_url) as resp:
4123+
if resp.status == 200:
4124+
jresp = await resp.json()
4125+
4126+
if "Organization" in jresp:
4127+
organization_details = jresp["Organization"]
4128+
4129+
exclude_keys = [
4130+
"BillingInfo",
4131+
"Admins",
4132+
"HlcSupport",
4133+
"DataRetention",
4134+
"Enabled",
4135+
"Subs"
4136+
]
4137+
4138+
for exclude_key in exclude_keys:
4139+
organization_details.pop(exclude_key, None)
4140+
4141+
data_controller = DataController(
4142+
organisation_id=organization_details["ID"],
4143+
organisation_name=organization_details["Name"],
4144+
cover_image_url=organization_details["CoverImageURL"] + "/web",
4145+
logo_image_url=organization_details["LogoImageURL"] + "/web",
4146+
location=organization_details["Location"],
4147+
organisation_type=organization_details["Type"]["Type"],
4148+
description=organization_details["Description"],
4149+
policy_url=organization_details["PolicyURL"],
4150+
eula_url=organization_details["EulaURL"]
4151+
).to_json()
4152+
4153+
return data_controller
4154+
4155+
async def create_or_update_data_controller_details_in_wallet(self) -> StorageRecord:
4156+
"""Create or update data controller details in wallet."""
4157+
4158+
# Storage instance
4159+
storage: IndyStorage = await self.context.inject(BaseStorage)
4160+
4161+
result = json.dumps({})
4162+
4163+
# Retrieve data controller details from storage
4164+
try:
4165+
4166+
storage_record: StorageRecord = await storage.search_records(
4167+
self.RECORD_TYPE_DATA_CONTROLLER_DETAILS
4168+
).fetch_single()
4169+
4170+
4171+
data_controller = await self.fetch_org_details_from_igrantio()
4172+
4173+
await storage.update_record_value(storage_record, data_controller)
4174+
4175+
return data_controller
4176+
4177+
except StorageError as err:
4178+
4179+
try:
4180+
data_controller = await self.fetch_org_details_from_igrantio()
4181+
4182+
# Create data controller details record
4183+
storage_record = StorageRecord(
4184+
self.RECORD_TYPE_DATA_CONTROLLER_DETAILS,
4185+
data_controller,
4186+
)
4187+
4188+
await storage.add_record(storage_record)
4189+
4190+
return data_controller
4191+
except ADAManagerError as err:
4192+
# Create data controller details record
4193+
storage_record = StorageRecord(
4194+
self.RECORD_TYPE_DATA_CONTROLLER_DETAILS,
4195+
result,
4196+
)
4197+
4198+
4199+
await storage.add_record(storage_record)
4200+
4201+
return result
4202+
4203+
except ADAManagerError as err:
4204+
pass
4205+
4206+
return result
4207+
4208+
async def process_data_controller_details_message(self, data_controller_details_message: DataControllerDetailsMessage, receipt: MessageReceipt) -> None:
4209+
"""
4210+
Process data controller details message.
4211+
"""
4212+
4213+
# Responder instance
4214+
responder: DispatcherResponder = await self.context.inject(BaseResponder, required=False)
4215+
4216+
# From and To MyData DIDs
4217+
to_did: DIDMyData = DIDMyData.from_public_key_b58(
4218+
receipt.sender_verkey, key_type=KeyType.ED25519)
4219+
from_did: DIDMyData = DIDMyData.from_public_key_b58(
4220+
receipt.recipient_verkey, key_type=KeyType.ED25519)
4221+
4222+
# Query data_controller_details record
4223+
data_controller_details: str = await self.create_or_update_data_controller_details_in_wallet()
4224+
4225+
# Construct Data Controller model class
4226+
data_controller: DataController = DataControllerSchema().load(json.loads(data_controller_details))
4227+
4228+
# Construct DataControllerDetailsResponseMessage
4229+
data_controller_details_response_message = DataControllerDetailsResponseMessage(
4230+
from_did=from_did.did,
4231+
to_did=to_did.did,
4232+
created_time=round(time.time() * 1000),
4233+
body=data_controller
4234+
)
4235+
4236+
# Send DataControllerDetailsResponseMessage to the requester.
4237+
4238+
if responder:
4239+
await responder.send_reply(data_controller_details_response_message, connection_id=self.context.connection_record.connection_id)
4240+
4241+
async def send_data_controller_details_message(self, conn_id: str) -> None:
4242+
"""Send data controller details message."""
4243+
4244+
# Responder instance
4245+
responder: DispatcherResponder = await self.context.inject(BaseResponder, required=False)
4246+
4247+
try:
4248+
4249+
# Retrieve connection record by id
4250+
connection_record: ConnectionRecord = await ConnectionRecord.retrieve_by_id(
4251+
self.context,
4252+
conn_id
4253+
)
4254+
4255+
except StorageError as err:
4256+
4257+
raise ADAManagerError(
4258+
f"Failed to retrieve connection record: {err}"
4259+
)
4260+
4261+
# From and to mydata dids
4262+
from_did: DIDMyData = DIDMyData.from_public_key_b58(
4263+
connection_record.my_did, key_type=KeyType.ED25519
4264+
)
4265+
to_did: DIDMyData = DIDMyData.from_public_key_b58(
4266+
connection_record.their_did, key_type=KeyType.ED25519
4267+
)
4268+
4269+
# Construct DataControllerDetailsMessage Message
4270+
data_controller_details_message = DataControllerDetailsMessage(
4271+
from_did=from_did.did,
4272+
to_did=to_did.did,
4273+
created_time=round(time.time() * 1000)
4274+
)
4275+
4276+
# Send message
4277+
if responder:
4278+
await responder.send_reply(data_controller_details_message, connection_id=connection_record.connection_id)

mydata_did/v1_0/message_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
JSON_LD_PROCESSED_RESPONSE_DATA = f"json-ld/1.0/processed-data-response"
5959
JSON_LD_PROBLEM_REPORT = f"json-ld/1.0/problem-report"
6060

61+
# Data controller protocol
62+
DATA_CONTROLLER_DETAILS = f"data-controller/1.0/details"
63+
DATA_CONTROLLER_DETAILS_RESPONSE = f"data-controller/1.0/details-response"
64+
6165
# Protocol package path
6266
PROTOCOL_PACKAGE = "mydata_did.v1_0"
6367

@@ -136,5 +140,11 @@
136140
READ_ALL_DATA_AGREEMENT_TEMPLATE_RESPONSE: (
137141
f"{PROTOCOL_PACKAGE}.messages.read_all_data_agreement_template_response.ReadAllDataAgreementTemplateResponseMessage"
138142
),
143+
DATA_CONTROLLER_DETAILS: (
144+
f"{PROTOCOL_PACKAGE}.messages.data_controller_details.DataControllerDetailsMessage"
145+
),
146+
DATA_CONTROLLER_DETAILS_RESPONSE: (
147+
f"{PROTOCOL_PACKAGE}.messages.data_controller_details_response.DataControllerDetailsResponseMessage"
148+
),
139149
},
140150
)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from aries_cloudagent.messaging.agent_message import AgentMessage, AgentMessageSchema
2+
from marshmallow import EXCLUDE, fields
3+
4+
from ..message_types import DATA_CONTROLLER_DETAILS, PROTOCOL_PACKAGE
5+
from ..utils.regex import MYDATA_DID
6+
7+
# Handler class for data controller details
8+
HANDLER_CLASS = (
9+
f"{PROTOCOL_PACKAGE}.handlers"
10+
".data_controller_details_handler.DataControllerDetailsHandler"
11+
)
12+
13+
class DataControllerDetailsMessage(AgentMessage):
14+
"""
15+
Message class for data controller details message.
16+
"""
17+
18+
class Meta:
19+
# Handler class that can handle this message
20+
handler_class = HANDLER_CLASS
21+
22+
# Message type
23+
message_type = DATA_CONTROLLER_DETAILS
24+
25+
# Message schema class
26+
schema_class = "DataControllerDetailsMessageSchema"
27+
28+
def __init__(
29+
self,
30+
*,
31+
from_did,
32+
to_did,
33+
created_time,
34+
**kwargs
35+
):
36+
"""
37+
Initialize a DataControllerDetailsMessage message instance.
38+
"""
39+
super().__init__(**kwargs)
40+
41+
# Sender DID
42+
self.from_did = from_did
43+
44+
# Recipient DID
45+
self.to_did = to_did
46+
47+
# The time the message was created
48+
self.created_time = created_time
49+
50+
51+
class DataControllerDetailsMessageSchema(AgentMessageSchema):
52+
"""
53+
Schema class for data controller details message
54+
"""
55+
56+
class Meta:
57+
# The message class that this schema is for
58+
model_class = DataControllerDetailsMessage
59+
60+
# Unknown fields to exclude from the schema (handled by marshmallow)
61+
unknown = EXCLUDE
62+
63+
# From DID
64+
from_did = fields.Str(data_key="from", **MYDATA_DID)
65+
66+
# To DID
67+
to_did = fields.Str(data_key="to", **MYDATA_DID)
68+
69+
# Created time
70+
created_time = fields.Str(data_key="created_time")

0 commit comments

Comments
 (0)