Skip to content

Commit

Permalink
Fix: Support credential issuance without data agreement and using dis…
Browse files Browse the repository at this point in the history
…closure mapping

Signed-off-by: George J Padayatti <[email protected]>
  • Loading branch information
georgepadayatti committed Apr 30, 2024
1 parent bf52b41 commit 2d9168b
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 199 deletions.
38 changes: 38 additions & 0 deletions alembic/versions/4b55f8ee9ea5_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""empty message
Revision ID: 4b55f8ee9ea5
Revises: 052e160bdd61
Create Date: 2024-04-30 16:49:03.453431
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = '4b55f8ee9ea5'
down_revision: Union[str, None] = '052e160bdd61'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('issue_credential_record', sa.Column('credential', postgresql.JSON(astext_type=sa.Text()), nullable=True))
op.add_column('issue_credential_record', sa.Column('disclosureMapping', postgresql.JSON(astext_type=sa.Text()), nullable=True))
op.alter_column('issue_credential_record', 'dataAgreementId',
existing_type=sa.UUID(),
nullable=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('issue_credential_record', 'dataAgreementId',
existing_type=sa.UUID(),
nullable=False)
op.drop_column('issue_credential_record', 'disclosureMapping')
op.drop_column('issue_credential_record', 'credential')
# ### end Alembic commands ###
34 changes: 34 additions & 0 deletions alembic/versions/792325ac96d1_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message
Revision ID: 792325ac96d1
Revises: 4b55f8ee9ea5
Create Date: 2024-04-30 17:25:32.284977
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = '792325ac96d1'
down_revision: Union[str, None] = '4b55f8ee9ea5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('issue_credential_record', 'dataAttributeValues',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('issue_credential_record', 'dataAttributeValues',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=False)
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions eudi_wallet/ebsi/entry_points/server/middlewares.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from aiohttp import web
import traceback

from eudi_wallet.ebsi.entry_points.server.utils import get_app_context

Expand Down Expand Up @@ -57,6 +58,7 @@ async def middleware_handler(request):
# Re-raise the exception so aiohttp can handle it
raise
except Exception as ex:
logger.debug(f"Stacktrace: {str(traceback.format_exc())}")
# Log the exception
logger.debug(f"Caught exception: {type(ex).__name__}: {ex}")
# Return a HTTP 500 error to the client
Expand Down
112 changes: 64 additions & 48 deletions eudi_wallet/ebsi/entry_points/server/routes/v2/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ class RegisterOrganisationReq(BaseModel):
webhookUrl: Optional[HttpUrl] = None


@config_routes.post("/config/organisation", name="handle_config_post_register_organisation") # type: ignore
@config_routes.post(
"/config/organisation", name="handle_config_post_register_organisation"
) # type: ignore
@v2_inject_request_context(
raise_exception_if_legal_entity_not_found=False,
raise_exception_if_not_legal_entity_path_param=False,
Expand Down Expand Up @@ -453,8 +455,10 @@ class CreateDataSourceIssueCredentialReq(BaseModel):
userPin: Optional[
constr(min_length=4, max_length=4, pattern="^[0-9]{4}$", strip_whitespace=True) # type: ignore
] = None
dataAgreementId: constr(min_length=3, strip_whitespace=True) # type: ignore
dataAgreementId: Optional[constr(min_length=3, strip_whitespace=True)] = None # type: ignore
limitedDisclosure: Optional[bool] = None
credential: Optional[dict] = None
disclosureMapping: Optional[dict] = None


@config_routes.post(
Expand All @@ -473,58 +477,73 @@ async def handle_post_issue_credential(request: Request, context: V2RequestConte
data = await request.json()
issue_credential_req = CreateDataSourceIssueCredentialReq(**data)
data_agreement_id = issue_credential_req.dataAgreementId
credential = issue_credential_req.credential
disclosure_mapping = issue_credential_req.disclosureMapping

try:
with context.data_agreement_repository as repo:
data_agreement_model = repo.get_by_id_and_organisation_id(
organisation_id=organisation_id, id=data_agreement_id
if credential:
credential_offer = await context.legal_entity_service.issue_credential_with_disclosure_mapping(
issuance_mode=issue_credential_req.issuanceMode,
is_pre_authorised=issue_credential_req.isPreAuthorised,
user_pin=issue_credential_req.userPin,
organisation_id=organisation_id,
credential=credential,
disclosureMapping=disclosure_mapping,
)
if not data_agreement_model:
raise IssueCredentialError(
f"Credential schema with id {data_agreement_id} not found"
credentialExchangeId = credential_offer["id"]
issuer_domain = context.legal_entity_service.issuer_domain
openid_credential_offer_uri = f"openid-credential-offer://?credential_offer_uri={issuer_domain}/organisation/{organisation_id}/service/credential-offer/{credentialExchangeId}"
credential_offer["credentialOffer"] = openid_credential_offer_uri
else:
with context.data_agreement_repository as repo:
data_agreement_model = repo.get_by_id_and_organisation_id(
organisation_id=organisation_id, id=data_agreement_id
)
try:
if issue_credential_req.dataAttributeValues:
is_valid_data_attribute_values = (
validate_data_attribute_schema_against_data_attribute_values(
if not data_agreement_model:
raise IssueCredentialError(
f"Credential schema with id {data_agreement_id} not found"
)
try:
if issue_credential_req.dataAttributeValues:
is_valid_data_attribute_values = validate_data_attribute_schema_against_data_attribute_values(
data_agreement_model.dataAttributes,
issue_credential_req.dataAttributeValues,
)
except ValueError as e:
error_message = str(e)
return web.json_response({"error": error_message}, status=400)
if (
data_agreement_model.methodOfUse
== DataAgreementExchangeModes.DataSource.value
):
credential_offer = (
await context.legal_entity_service.issue_credential_record(
data_agreement_id=data_agreement_id,
data_attribute_values=(
issue_credential_req.dataAttributeValues
if issue_credential_req.dataAttributeValues
else None
),
issuance_mode=issue_credential_req.issuanceMode,
is_pre_authorised=issue_credential_req.isPreAuthorised,
user_pin=issue_credential_req.userPin,
organisation_id=organisation_id,
limited_disclosure=(
issue_credential_req.limitedDisclosure
if issue_credential_req.limitedDisclosure
else False
),
)
except ValueError as e:
error_message = str(e)
return web.json_response({"error": error_message}, status=400)
if (
data_agreement_model.methodOfUse
== DataAgreementExchangeModes.DataSource.value
):

credential_offer = (
await context.legal_entity_service.issue_credential_record(
data_agreement_id=data_agreement_id,
data_attribute_values=(
issue_credential_req.dataAttributeValues
if issue_credential_req.dataAttributeValues
else None
),
issuance_mode=issue_credential_req.issuanceMode,
is_pre_authorised=issue_credential_req.isPreAuthorised,
user_pin=issue_credential_req.userPin,
organisation_id=organisation_id,
limited_disclosure=(
issue_credential_req.limitedDisclosure
if issue_credential_req.limitedDisclosure
else False
),
)
)
credentialExchangeId = credential_offer["id"]
issuer_domain = context.legal_entity_service.issuer_domain
openid_credential_offer_uri = f"openid-credential-offer://?credential_offer_uri={issuer_domain}/organisation/{organisation_id}/service/credential-offer/{credentialExchangeId}"
credential_offer["credentialOffer"] = openid_credential_offer_uri

else:
raise ValidationError(f"Data agreement method of use is not data source")
credentialExchangeId = credential_offer["id"]
issuer_domain = context.legal_entity_service.issuer_domain
openid_credential_offer_uri = f"openid-credential-offer://?credential_offer_uri={issuer_domain}/organisation/{organisation_id}/service/credential-offer/{credentialExchangeId}"
credential_offer["credentialOffer"] = openid_credential_offer_uri

else:
raise ValidationError(
f"Data agreement method of use is not data source"
)

return web.json_response(credential_offer, status=201)
except ValidateDataAttributeValuesAgainstDataAttributesError as e:
Expand Down Expand Up @@ -617,7 +636,6 @@ async def handle_service_put_update_credential_offer(
async def handle_config_get_credential_offer_by_id_and_credential_schema_id(
request: Request, context: V2RequestContext
):

organisation_id = request.match_info.get("organisationId")
if organisation_id is None:
raise web.HTTPBadRequest(reason="Invalid organisation id")
Expand Down Expand Up @@ -656,7 +674,6 @@ async def handle_config_get_all_credential_offers(
data_agreement_id=data_agreement_id
)
else:

credential_offers = await context.legal_entity_service.get_all_credential_offers_by_organisation_id(
organisation_id=organisation_id
)
Expand All @@ -676,7 +693,6 @@ async def handle_config_get_all_credential_offers(
async def handle_config_delete_credential_offer(
request: Request, context: V2RequestContext
):

organisation_id = request.match_info.get("organisationId")
if organisation_id is None:
raise web.HTTPBadRequest(reason="Invalid organisation id")
Expand Down
6 changes: 4 additions & 2 deletions eudi_wallet/ebsi/models/v2/issue_credential_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class IssueCredentialRecordModel(Base):
dataAgreementId = Column(
UUID(as_uuid=True),
ForeignKey("v2_data_agreement.id"),
nullable=False,
nullable=True,
)
dataAgreement = relationship(
"V2DataAgreementModel", back_populates="issue_credential_record"
Expand All @@ -33,7 +33,9 @@ class IssueCredentialRecordModel(Base):
nullable=False,
)

dataAttributeValues = Column(JSON, nullable=False)
dataAttributeValues = Column(JSON, nullable=True, default="{}")
credential = Column(JSON, nullable=True, default="{}")
disclosureMapping = Column(JSON, nullable=True, default="{}")

issuanceMode = Column(String, nullable=False)
isPreAuthorised = Column(Boolean, default=False)
Expand Down
35 changes: 34 additions & 1 deletion eudi_wallet/ebsi/repositories/v2/issue_credential_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,39 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.session = None
return True

def create_without_data_agreement(
self,
organisation_id: str,
issuance_mode: str,
status: str,
is_pre_authorised: Optional[bool] = False,
credential_status: Optional[str] = None,
acceptance_token: Optional[str] = None,
authorisation_code: Optional[str] = None,
pre_authorised_code: Optional[str] = None,
user_pin: Optional[str] = None,
**kwargs,
) -> IssueCredentialRecordModel:
assert self.session is not None
id = str(uuid.uuid4())
credential_offer = IssueCredentialRecordModel(
id=id,
organisationId=organisation_id,
issuanceMode=issuance_mode,
isPreAuthorised=is_pre_authorised,
credentialStatus=credential_status,
acceptanceToken=acceptance_token,
authorisationCode=authorisation_code,
preAuthorisedCode=pre_authorised_code,
userPin=user_pin,
status=status,
**kwargs,
)
self.session.add(credential_offer)
self.session.commit()
self.session.refresh(credential_offer)
return credential_offer

def create(
self,
data_agreement_id: str,
Expand Down Expand Up @@ -63,7 +96,7 @@ def create(
preAuthorisedCode=pre_authorised_code,
userPin=user_pin,
status=status,
**kwargs
**kwargs,
)
self.session.add(credential_offer)
self.session.commit()
Expand Down
Loading

0 comments on commit 2d9168b

Please sign in to comment.