From 47faedbb5930721f5d43831766295f79f9d4b47f Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Mon, 20 Nov 2023 11:48:02 -0800 Subject: [PATCH 1/7] Uniquely generate a subject identifier based on the proof-req claims Signed-off-by: Gavin Jaeger-Freeborn --- oidc-controller/api/routers/oidc.py | 12 +++++++++--- oidc-controller/requirements.txt | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/oidc-controller/api/routers/oidc.py b/oidc-controller/api/routers/oidc.py index 22034c6e..f35c75eb 100644 --- a/oidc-controller/api/routers/oidc.py +++ b/oidc-controller/api/routers/oidc.py @@ -1,6 +1,7 @@ import base64 import io import uuid +import canonicaljson from datetime import datetime from urllib.parse import urlencode @@ -20,7 +21,7 @@ from ..core.config import settings from ..core.logger_util import log_debug from ..core.oidc import provider -from ..core.oidc.issue_token_service import Token +from ..core.oidc.issue_token_service import PROOF_CLAIMS_ATTRIBUTE_NAME, Token from ..db.session import get_db # Access to the websocket @@ -173,11 +174,16 @@ async def post_token(request: Request, db: Database = Depends(get_db)): # Replace auto-generated sub with one coming from proof, if available # The stateless storage uses a cypher, so a new item can be added and # the reference in the form needs to be updated with the new key value - if claims.get("sub"): + if claims.get("sub") and claims.get(PROOF_CLAIMS_ATTRIBUTE_NAME): authz_info = provider.provider.authz_state.authorization_codes[ form_dict["code"] ] - authz_info["sub"] = claims.pop("sub") + # Removed to avoid duplicate entries for "sub" + del claims["sub"] + # Generate the new sub based on the claims produced by the proof-request + authz_info["sub"] = canonicaljson.encode_canonical_json( + claims.get(PROOF_CLAIMS_ATTRIBUTE_NAME) + ).decode("utf-8") new_code = provider.provider.authz_state.authorization_codes.pack( authz_info ) diff --git a/oidc-controller/requirements.txt b/oidc-controller/requirements.txt index 000f64af..d4cab51e 100644 --- a/oidc-controller/requirements.txt +++ b/oidc-controller/requirements.txt @@ -8,3 +8,4 @@ qrcode[pil]==7.4.2 structlog==23.1.0 uvicorn[standard]==0.22.0 python-socketio==5.8.0 # required to run websockets +canonicaljson==2.0.0 # used to provide unique consistent user identifiers From 86338e1acb50600f7fbf0742629513c8487c01e3 Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Tue, 21 Nov 2023 13:36:59 -0800 Subject: [PATCH 2/7] Migrated sub generation based on proof_claims to get_claims Signed-off-by: Gavin Jaeger-Freeborn --- README.md | 1 + docs/README.md | 5 ++- .../api/core/oidc/issue_token_service.py | 40 +++++++++++++++---- oidc-controller/api/routers/oidc.py | 38 +++++++++--------- .../api/verificationConfigs/examples.py | 1 + .../api/verificationConfigs/models.py | 7 ++-- 6 files changed, 59 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 6346dce8..3adfc68a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ curl -X 'POST' \ -d '{ "ver_config_id": "verified-email", "subject_identifier": "email", + "generate_consistent_identifier": true, "proof_request": { "name": "BCGov Verified Email", "version": "1.0", diff --git a/docs/README.md b/docs/README.md index 302459c1..a5ee2da7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -138,6 +138,7 @@ A verifiable credential presentation request configuration, takes the following { "id": "", "subject_identifier": "", + "generate_consistent_identifier": , "proof_request": { "name": "Basic Proof", "version": "1.0", @@ -156,6 +157,7 @@ This data model is inspired by that is defined and used in the [Hyperledger Indy - `id` : The identifier for the presentation configuration. - `subject_identifier` : See [here](#subject-identifer-mapping) for further details on the purpose of this field. +- `generate_consistent_identifier` : Optional field defaulting to false. See [here](#subject-identifer-mapping) for more details. - `proof_request` : Contains the details on the presentation request, e.g which attributes are to be disclosed - `name` : The name that will accompany the presentation request - `version` : The version of the presentation request @@ -308,7 +310,7 @@ To quote from the OpenID Connect specification on [ID tokens](https://openid.net `sub : REQUIRED. Subject Identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 ASCII characters in length. The sub value is a case sensitive string` -When an OP is performing VC-AuthN, and the request has reached the point where the VC presentation has been generated and sent by the IW to the OP, the OP must now map contents of this VC presentation to an OpenID ID token. The question is then raised on what should populate this field. The two options available are: +When an OP is performing VC-AuthN, and the request has reached the point where the VC presentation has been generated and sent by the IW to the OP, the OP must now map contents of this VC presentation to an OpenID ID token. The question is then raised on what should populate this field. The three available are: 1. Nominate a disclosed attribute in the verifiable credential presentation that is used to populate the subject field. 2. Ephemeral generate an identifier for this field e.g a randomly generated GUID. @@ -317,6 +319,7 @@ When an OP is performing VC-AuthN, and the request has reached the point where t **Note:** - In option 2. this prevents the often desirable property of cross session correlation of an authenticated user, which will effect the ability for many integrating IAM solutions being able to conduct effective auditing. - In option 3. this method should be assessed and used with caution, as the chance of collisions for users holding credentials with exact same values is possible (e.g.: a proof-request using only `first_name` and `last_name`, would generate the same identifier for people with same first and last name). +- In order to enable option 3 the _presentation request configuration_ must have `generate_consistent_identifier` #### UserInfo Endpoint diff --git a/oidc-controller/api/core/oidc/issue_token_service.py b/oidc-controller/api/core/oidc/issue_token_service.py index 5983e09f..eea4d815 100644 --- a/oidc-controller/api/core/oidc/issue_token_service.py +++ b/oidc-controller/api/core/oidc/issue_token_service.py @@ -1,15 +1,15 @@ +import canonicaljson import dataclasses import json from datetime import datetime -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, TypedDict import structlog from oic.oic.message import OpenIDSchema from pydantic import BaseModel from ...authSessions.models import AuthSession -from ...verificationConfigs.models import ReqAttr, VerificationConfig -from ..models import RevealedAttribute +from ...verificationConfigs.models import AttributeFilter, VerificationConfig logger = structlog.getLogger(__name__) @@ -22,6 +22,18 @@ class Claim(BaseModel): value: str +# Used as a work around since these are represented as dictionaries in this case +class ReqAttrDict(TypedDict, total=False): + names: List[str] + label: Optional[str] + restrictions: List[AttributeFilter] + + +class RevealedAttributeDict(TypedDict, total=False): + sub_proof_index: int + values: dict + + class Token(BaseModel): creation_time: datetime = datetime.now() issuer: str @@ -55,7 +67,7 @@ def get_claims( ) referent: str - requested_attr: ReqAttr + requested_attr: ReqAttrDict try: for referent, requested_attr in auth_session.presentation_exchange[ "presentation_request" @@ -64,7 +76,7 @@ def get_claims( f"Processing referent: {referent}, requested_attr: {requested_attr}" ) revealed_attrs: Dict[ - str, RevealedAttribute + str, RevealedAttributeDict ] = auth_session.presentation_exchange["presentation"][ "requested_proof" ][ @@ -85,6 +97,9 @@ def get_claims( ) raise RuntimeError(err) + proof_claims = json.dumps( + {c.type: c.value for c in presentation_claims.values()} + ) # look at all presentation_claims for one # matching the configured subject_identifier, if any sub_id_claim = presentation_claims.get(ver_config.subject_identifier) @@ -92,11 +107,20 @@ def get_claims( if sub_id_claim: # add sub and append presentation_claims oidc_claims.append(Claim(type="sub", value=sub_id_claim.value)) + elif ver_config.generate_consistent_identifier: + # Do not create a sub based on the proof claims if the + # user requests a generated identifier + oidc_claims.append( + Claim( + type="sub", + value=canonicaljson.encode_canonical_json(proof_claims).decode( + "utf-8" + ), + ) + ) result = {c.type: c.value for c in oidc_claims} - result[PROOF_CLAIMS_ATTRIBUTE_NAME] = json.dumps( - {c.type: c.value for c in presentation_claims.values()} - ) + result[PROOF_CLAIMS_ATTRIBUTE_NAME] = proof_claims # TODO: Remove after full transistion to v2.0 # Add the presentation claims to the result as keys for backwards compatibility [v1.0] diff --git a/oidc-controller/api/routers/oidc.py b/oidc-controller/api/routers/oidc.py index f35c75eb..a9bdffcb 100644 --- a/oidc-controller/api/routers/oidc.py +++ b/oidc-controller/api/routers/oidc.py @@ -1,7 +1,7 @@ import base64 import io +from typing import cast import uuid -import canonicaljson from datetime import datetime from urllib.parse import urlencode @@ -21,7 +21,7 @@ from ..core.config import settings from ..core.logger_util import log_debug from ..core.oidc import provider -from ..core.oidc.issue_token_service import PROOF_CLAIMS_ATTRIBUTE_NAME, Token +from ..core.oidc.issue_token_service import Token from ..db.session import get_db # Access to the websocket @@ -159,35 +159,33 @@ async def get_authorize_callback(pid: str, db: Database = Depends(get_db)): return RedirectResponse(url) +AuthCode = str + + +def gen_auth_codes_for_user(original_code: AuthCode, identifier: str) -> AuthCode: + authz_info = provider.provider.authz_state.authorization_codes[original_code] + authz_info["sub"] = identifier + logger.warn(f"sub is {authz_info['sub']}") + new_code = provider.provider.authz_state.authorization_codes.pack(authz_info) + return new_code + + @log_debug @router.post(VerifiedCredentialTokenUri, response_class=JSONResponse) async def post_token(request: Request, db: Database = Depends(get_db)): """Called by oidc platform to retrieve token contents""" async with request.form() as form: - form_dict = form._dict + logger.warn(f"post_token: form was {form}") + form_dict = cast(dict[str, str], form._dict) auth_session = await AuthSessionCRUD(db).get_by_pyop_auth_code( form_dict["code"] ) ver_config = await VerificationConfigCRUD(db).get(auth_session.ver_config_id) claims = Token.get_claims(auth_session, ver_config) - # Replace auto-generated sub with one coming from proof, if available - # The stateless storage uses a cypher, so a new item can be added and - # the reference in the form needs to be updated with the new key value - if claims.get("sub") and claims.get(PROOF_CLAIMS_ATTRIBUTE_NAME): - authz_info = provider.provider.authz_state.authorization_codes[ - form_dict["code"] - ] - # Removed to avoid duplicate entries for "sub" - del claims["sub"] - # Generate the new sub based on the claims produced by the proof-request - authz_info["sub"] = canonicaljson.encode_canonical_json( - claims.get(PROOF_CLAIMS_ATTRIBUTE_NAME) - ).decode("utf-8") - new_code = provider.provider.authz_state.authorization_codes.pack( - authz_info - ) - form_dict["code"] = new_code + form_dict["code"] = gen_auth_codes_for_user( + form_dict["code"], claims.pop("sub") + ) # convert form data to what library expects, Flask.app.request.get_data() data = urlencode(form_dict) diff --git a/oidc-controller/api/verificationConfigs/examples.py b/oidc-controller/api/verificationConfigs/examples.py index ed612324..da9f9399 100644 --- a/oidc-controller/api/verificationConfigs/examples.py +++ b/oidc-controller/api/verificationConfigs/examples.py @@ -1,6 +1,7 @@ ex_ver_config = { "ver_config_id": "test-request-config", "include_v1_attributes": False, + "generate_consistent_identifier": False, "subject_identifier": "first_name", "proof_request": { "name": "Basic Proof", diff --git a/oidc-controller/api/verificationConfigs/models.py b/oidc-controller/api/verificationConfigs/models.py index 60c41a88..cfb318d2 100644 --- a/oidc-controller/api/verificationConfigs/models.py +++ b/oidc-controller/api/verificationConfigs/models.py @@ -41,6 +41,7 @@ class VerificationProofRequest(BaseModel): class VerificationConfigBase(BaseModel): subject_identifier: str = Field() proof_request: VerificationProofRequest = Field() + generate_consistent_identifier: Optional[bool] = Field(default=False) include_v1_attributes: Optional[bool] = Field(default=False) def generate_proof_request(self): @@ -52,8 +53,7 @@ def generate_proof_request(self): } for i, req_attr in enumerate(self.proof_request.requested_attributes): label = req_attr.label or "req_attr_" + str(i) - result["requested_attributes"][label] = req_attr.dict( - exclude_none=True) + result["requested_attributes"][label] = req_attr.dict(exclude_none=True) if settings.SET_NON_REVOKED: result["requested_attributes"][label]["non_revoked"] = { "from": int(time.time()), @@ -62,8 +62,7 @@ def generate_proof_request(self): # TODO add I indexing for req_pred in self.proof_request.requested_predicates: label = req_pred.label or "req_pred_" + str(i) - result["requested_predicates"][label] = req_pred.dict( - exclude_none=True) + result["requested_predicates"][label] = req_pred.dict(exclude_none=True) if settings.SET_NON_REVOKED: result["requested_attributes"][label]["non_revoked"] = { "from": int(time.time()), From a0fb01b2c3a7b029318948778b424ea58e8e2cd3 Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Tue, 21 Nov 2023 13:37:54 -0800 Subject: [PATCH 3/7] New tests for the multiple states of generate_consistent_identifier Signed-off-by: Gavin Jaeger-Freeborn --- .../oidc/tests/test_issue_token_service.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/oidc-controller/api/core/oidc/tests/test_issue_token_service.py b/oidc-controller/api/core/oidc/tests/test_issue_token_service.py index ca9c9444..f5a234e4 100644 --- a/oidc-controller/api/core/oidc/tests/test_issue_token_service.py +++ b/oidc-controller/api/core/oidc/tests/test_issue_token_service.py @@ -219,7 +219,7 @@ async def test_valid_presentation_with_matching_subject_identifier_has_identifie @pytest.mark.asyncio -async def test_valid_presentation_with_non_matching_subject_identifier_and_has_no_sub(): +async def test_valid_presentation_with_non_matching_subject_identifier_and_generate_consistent_identifier_is_missing_and_has_no_sub(): presentation["presentation_request"][ "requested_attributes" ] = basic_valid_requested_attributes @@ -229,4 +229,35 @@ async def test_valid_presentation_with_non_matching_subject_identifier_and_has_n with mock.patch.object(AuthSession, "presentation_exchange", presentation): ver_config.subject_identifier = "not-email" claims = Token.get_claims(auth_session, ver_config) + assert not ver_config.generate_consistent_identifier assert "sub" not in claims + + +@pytest.mark.asyncio +async def test_valid_presentation_with_non_matching_subject_identifier_and_generate_consistent_identifier_false_and_has_no_sub(): + presentation["presentation_request"][ + "requested_attributes" + ] = basic_valid_requested_attributes + presentation["presentation"]["requested_proof"][ + "revealed_attr_groups" + ] = basic_valid_revealed_attr_groups + with mock.patch.object(AuthSession, "presentation_exchange", presentation): + ver_config.subject_identifier = "not-email" + ver_config.generate_consistent_identifier = False + claims = Token.get_claims(auth_session, ver_config) + assert "sub" not in claims + + +@pytest.mark.asyncio +async def test_valid_presentation_with_non_matching_subject_identifier_and_generate_consistent_identifier_true_and_has_sub(): + presentation["presentation_request"][ + "requested_attributes" + ] = basic_valid_requested_attributes + presentation["presentation"]["requested_proof"][ + "revealed_attr_groups" + ] = basic_valid_revealed_attr_groups + with mock.patch.object(AuthSession, "presentation_exchange", presentation): + ver_config.subject_identifier = "not-email" + ver_config.generate_consistent_identifier = True + claims = Token.get_claims(auth_session, ver_config) + assert "sub" in claims From 61ca4d15f7d9aa06480441303521e022e2614595 Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Tue, 21 Nov 2023 13:41:58 -0800 Subject: [PATCH 4/7] Revert sub updates to the original implementations Signed-off-by: Gavin Jaeger-Freeborn --- oidc-controller/api/routers/oidc.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/oidc-controller/api/routers/oidc.py b/oidc-controller/api/routers/oidc.py index a9bdffcb..7c7d044d 100644 --- a/oidc-controller/api/routers/oidc.py +++ b/oidc-controller/api/routers/oidc.py @@ -159,17 +159,6 @@ async def get_authorize_callback(pid: str, db: Database = Depends(get_db)): return RedirectResponse(url) -AuthCode = str - - -def gen_auth_codes_for_user(original_code: AuthCode, identifier: str) -> AuthCode: - authz_info = provider.provider.authz_state.authorization_codes[original_code] - authz_info["sub"] = identifier - logger.warn(f"sub is {authz_info['sub']}") - new_code = provider.provider.authz_state.authorization_codes.pack(authz_info) - return new_code - - @log_debug @router.post(VerifiedCredentialTokenUri, response_class=JSONResponse) async def post_token(request: Request, db: Database = Depends(get_db)): @@ -183,9 +172,18 @@ async def post_token(request: Request, db: Database = Depends(get_db)): ver_config = await VerificationConfigCRUD(db).get(auth_session.ver_config_id) claims = Token.get_claims(auth_session, ver_config) - form_dict["code"] = gen_auth_codes_for_user( - form_dict["code"], claims.pop("sub") - ) + # Replace auto-generated sub with one coming from proof, if available + # The stateless storage uses a cypher, so a new item can be added and + # the reference in the form needs to be updated with the new key value + if claims.get("sub"): + authz_info = provider.provider.authz_state.authorization_codes[ + form_dict["code"] + ] + authz_info["sub"] = claims.pop("sub") + new_code = provider.provider.authz_state.authorization_codes.pack( + authz_info + ) + form_dict["code"] = new_code # convert form data to what library expects, Flask.app.request.get_data() data = urlencode(form_dict) From 6f86597571e47ebf386829d9991730944418bf54 Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Tue, 21 Nov 2023 13:44:26 -0800 Subject: [PATCH 5/7] Corrected mistakes in README grammar Signed-off-by: Gavin Jaeger-Freeborn --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index a5ee2da7..aef7b0e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -310,7 +310,7 @@ To quote from the OpenID Connect specification on [ID tokens](https://openid.net `sub : REQUIRED. Subject Identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 ASCII characters in length. The sub value is a case sensitive string` -When an OP is performing VC-AuthN, and the request has reached the point where the VC presentation has been generated and sent by the IW to the OP, the OP must now map contents of this VC presentation to an OpenID ID token. The question is then raised on what should populate this field. The three available are: +When an OP is performing VC-AuthN, and the request has reached the point where the VC presentation has been generated and sent by the IW to the OP, the OP must now map contents of this VC presentation to an OpenID ID token. The question is then raised on what should populate this field. The three options available are: 1. Nominate a disclosed attribute in the verifiable credential presentation that is used to populate the subject field. 2. Ephemeral generate an identifier for this field e.g a randomly generated GUID. From 68fef1140def6d4c325554a359c39bbba4155adf Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Tue, 21 Nov 2023 13:54:38 -0800 Subject: [PATCH 6/7] Ensure that sub is both consistent and not using the subject_identifier Signed-off-by: Gavin Jaeger-Freeborn --- .../api/core/oidc/tests/test_issue_token_service.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/oidc-controller/api/core/oidc/tests/test_issue_token_service.py b/oidc-controller/api/core/oidc/tests/test_issue_token_service.py index f5a234e4..533a89d3 100644 --- a/oidc-controller/api/core/oidc/tests/test_issue_token_service.py +++ b/oidc-controller/api/core/oidc/tests/test_issue_token_service.py @@ -261,3 +261,15 @@ async def test_valid_presentation_with_non_matching_subject_identifier_and_gener ver_config.generate_consistent_identifier = True claims = Token.get_claims(auth_session, ver_config) assert "sub" in claims + + # Ensure that this sub is not using the ver_config.subject_identifier + ver_config.subject_identifier = "email" + ver_config.generate_consistent_identifier = False + claims_subject_identifier = Token.get_claims(auth_session, ver_config) + assert claims["sub"] != claims_subject_identifier["sub"] + + # Ensure that sub is consistent + ver_config.subject_identifier = "not-email" + ver_config.generate_consistent_identifier = True + claims_duplicate = Token.get_claims(auth_session, ver_config) + assert claims["sub"] == claims_duplicate["sub"] From 3637c60ce19d674b6dc74c23c145bd25528222bd Mon Sep 17 00:00:00 2001 From: Gavin Jaeger-Freeborn Date: Wed, 22 Nov 2023 12:32:03 -0800 Subject: [PATCH 7/7] Removed the need for dedicated Dict instances of RevealedAttribute and ReqAttrDict Signed-off-by: Gavin Jaeger-Freeborn --- oidc-controller/api/core/models.py | 6 ++++- .../api/core/oidc/issue_token_service.py | 26 ++++++------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/oidc-controller/api/core/models.py b/oidc-controller/api/core/models.py index 62055385..519d2456 100644 --- a/oidc-controller/api/core/models.py +++ b/oidc-controller/api/core/models.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import TypedDict from bson import ObjectId from pydantic import BaseModel, Field @@ -48,7 +49,10 @@ class GenericErrorMessage(BaseModel): detail: str -class RevealedAttribute(BaseModel): +# Currently used as a TypedDict since it can be used as a part of a +# Pydantic class but a Pydantic class can not inherit from TypedDict +# and and BaseModel +class RevealedAttribute(TypedDict, total=False): sub_proof_index: int values: dict diff --git a/oidc-controller/api/core/oidc/issue_token_service.py b/oidc-controller/api/core/oidc/issue_token_service.py index eea4d815..cb912359 100644 --- a/oidc-controller/api/core/oidc/issue_token_service.py +++ b/oidc-controller/api/core/oidc/issue_token_service.py @@ -2,14 +2,15 @@ import dataclasses import json from datetime import datetime -from typing import Any, Dict, List, Optional, TypedDict +from typing import Any, Dict, List import structlog from oic.oic.message import OpenIDSchema from pydantic import BaseModel from ...authSessions.models import AuthSession -from ...verificationConfigs.models import AttributeFilter, VerificationConfig +from ...verificationConfigs.models import ReqAttr, VerificationConfig +from ...core.models import RevealedAttribute logger = structlog.getLogger(__name__) @@ -22,18 +23,6 @@ class Claim(BaseModel): value: str -# Used as a work around since these are represented as dictionaries in this case -class ReqAttrDict(TypedDict, total=False): - names: List[str] - label: Optional[str] - restrictions: List[AttributeFilter] - - -class RevealedAttributeDict(TypedDict, total=False): - sub_proof_index: int - values: dict - - class Token(BaseModel): creation_time: datetime = datetime.now() issuer: str @@ -67,16 +56,17 @@ def get_claims( ) referent: str - requested_attr: ReqAttrDict + requested_attr: ReqAttr try: - for referent, requested_attr in auth_session.presentation_exchange[ + for referent, requested_attrdict in auth_session.presentation_exchange[ "presentation_request" ]["requested_attributes"].items(): + requested_attr = ReqAttr(**requested_attrdict) logger.debug( f"Processing referent: {referent}, requested_attr: {requested_attr}" ) revealed_attrs: Dict[ - str, RevealedAttributeDict + str, RevealedAttribute ] = auth_session.presentation_exchange["presentation"][ "requested_proof" ][ @@ -84,7 +74,7 @@ def get_claims( ] logger.debug(f"revealed_attrs: {revealed_attrs}") # loop through each value and put it in token as a claim - for attr_name in requested_attr["names"]: + for attr_name in requested_attr.names: logger.debug(f"AttrName: {attr_name}") presentation_claims[attr_name] = Claim( type=attr_name,