import uuid
from typing import Optional
from urllib.parse import quote_plus, urlencode
from pyeudiw.duckle_ql.utils import DUCKLE_PRESENTATION, DUCKLE_QUERY_KEY
from pyeudiw.satosa.backends.openid4vp.schemas.response import ResponseMode
from pyeudiw.tools.utils import exp_from_now, iat_now
[docs]
def build_authorization_request_url(scheme: str, params: dict) -> str:
"""
Build authorization request URL that let the wallet download the request
object. This is loosely realted to RFC9101 [JAR], section 5.2.1.
The scheme is either the scheme portion of a deeplink, such as "haip" or
"eudiw", while params is a dictitonary of query parameters not urlencoded.
"""
if "://" not in scheme:
scheme = scheme + "://"
query_params = urlencode(params, quote_via=quote_plus)
_sep = "" if "?" in scheme else "?"
return f"{scheme}{_sep}{query_params}"
[docs]
def build_authorization_request_claims(
client_id: str,
state: str,
response_uri: str,
default_claims: dict,
nonce: str = "",
client_metadata: Optional[dict] = None,
submission_data: Optional[dict] = None,
wallet_nonce: Optional[str] = None,
) -> dict:
"""
Primitive function to build the payload claims of the (JAR) authorization request.
:param client_id: the client identifier (who issues the JAR token)
:type client_id: str
:param state: request session identifier
:type state: str
:param response_uri: endpoint accepting authorization responses
:type response_uri: str
:param default_claims: a dictionary with the default claims to be used in the request object.
It must contain the following mandatory keys:
- "expiration_time": the expiration time in minutes of the request object
- "response_mode": the response mode to be used in the request object
- "auth_iss_id": the issuer identifier of the authorization server
- "aud": the audience of the request object
:type default_claims: dict
:param nonce: optional nonce to be inserted in the request object; if not
set, a new cryptographically safe uuid v4 nonce is generated.
:type nonce: str
:param client_metadata: optional client_metadata to be included in the request object
:type client_metadata: dict
:param submission_data: optional submission data, such as the duckle query,
to be included in the request object.
If this parameter is set, the duckle data is used to build the request object;
else the presentation definition retrocompatibility is used.
:type submission_data: dict
:param wallet_nonce: optional nonce to be used by the wallet.
:type wallet_nonce: str
:raises KeyError: if authorization_config misses mandatory configuration options
:returns: a dictionary with the *complete* set of JAR JWT payload claims
:rtype: dict
"""
nonce = nonce or str(uuid.uuid4())
if default_claims.get("auth_iss_id"):
_iss = default_claims["auth_iss_id"]
else:
_iss = client_id
claims = {
"client_id_scheme": "http", # that's federation.
"client_id": client_id,
"response_mode": default_claims.get(
"response_mode", ResponseMode.direct_post_jwt
),
"response_type": "vp_token",
"response_uri": response_uri,
"nonce": nonce,
"state": state,
"iss": _iss,
"iat": iat_now(),
"exp": exp_from_now(minutes=default_claims["expiration_time"]),
}
if _aud := default_claims.get("aud", "https://self-issued.me/v2"):
claims["aud"] = _aud
if submission_data and submission_data["typo"] == DUCKLE_PRESENTATION:
claims[DUCKLE_QUERY_KEY] = submission_data[DUCKLE_QUERY_KEY]
else:
if client_metadata:
claims["client_metadata"] = client_metadata
if default_claims.get("scopes"):
claims["scope"] = " ".join(default_claims["scopes"])
# backend configuration validation should check that at least PE or DCQL must be configured within the authz request conf
if default_claims.get("presentation_definition"):
claims["presentation_definition"] = default_claims[
"presentation_definition"
]
if wallet_nonce:
claims["wallet_nonce"] = wallet_nonce
return claims