diff --git a/README.md b/README.md index f03da64..03b32db 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) +:heavy_exclamation_mark: **Important!** Before you proceed, please read +the [EUDI Wallet Reference Implementation project description](https://github.com/eu-digital-identity-wallet/.github/blob/main/profile/reference-implementation.md) + ### Overview @@ -47,6 +50,30 @@ This version of the EUDIW Issuer supports the [OpenId for Verifiable Credential You can use the EUDIW Issuer at https://issuer.eudiw.dev/, or install it locally. + +## :heavy_exclamation_mark: Disclaimer + +The released software is a initial development release version: + +- The initial development release is an early endeavor reflecting the efforts of a short timeboxed + period, and by no means can be considered as the final product. +- The initial development release may be changed substantially over time, might introduce new + features but also may change or remove existing ones, potentially breaking compatibility with your + existing code. +- The initial development release is limited in functional scope. +- The initial development release may contain errors or design flaws and other problems that could + cause system or other failures and data loss. +- The initial development release has reduced security, privacy, availability, and reliability + standards relative to future releases. This could make the software slower, less reliable, or more + vulnerable to attacks than mature software. +- The initial development release is not yet comprehensively documented. +- Users of the software must perform sufficient engineering and additional testing in order to + properly evaluate their application and determine whether any of the open-sourced components is + suitable for use in that application. +- We strongly recommend not putting this version of the software into production use. +- Only the latest version of the software will be supported + + ## 1. Installation Pre-requisites: @@ -61,10 +88,26 @@ Click [here](install.md) for detailed installation instructions. Click [here](install.md) for detailed instructions. +## 3. Frequently Asked Questions + +### A. How to make your local EUDIW Issuer available on the Internet? + +Please see detailed instructions in [install.md](install.md#4-make-your-local-eudiw-issuer-available-on-the-internet-optional). + +### B. How to add a new credential to the issuer ? + +Please see detailed instructions in [api_docs/add_credential.md](api_docs/add_credential.md). + +### C. Can I use my IACA certificate with the EUDIW Issuer? + +Yes. You must copy your IACA trusted certificate(s) (in PEM format) to the `trusted_CAs_path` folder. If you don't have an IACA certificate, we provide an example test IACA certificate for the country Utopia (UT). + +See more information in [api_docs/configuration.md](api_docs/configuration.md#1-service-configuration). + +### D. Can I use my Document Signer private key and certificate with the EUDIW Issuer? -## How to add a new credential to the issuer ? +Yes. Please follow the instructions in [api_docs/configuration.md](api_docs/configuration.md#2-configuration-of-countries). If you don't have Document Signer private key and certificate, we provide test private DS keys and certificates, for country Utopia (UT). -Please see detailed instructions in [api_docs/add_credential.md](api_docs/add_credential.md) ## How to contribute diff --git a/api_docs/configuration.md b/api_docs/configuration.md index abd2b18..0275a8f 100644 --- a/api_docs/configuration.md +++ b/api_docs/configuration.md @@ -39,3 +39,18 @@ For configuring the installed [idpy-oidc](https://github.com/IdentityPython/idpy - domain - port + +## 4. Metadata configuration + +The EUDIW Issuer OAuth2 metadata configuration files are located in ```app/metadata_config/metadata_config.json``` and ```app/metadata_config/openid-configuration.json``` + +You must change the base URL of the endpoints from ```https://issuer.eudiw.dev``` to a custom one or ``` https://localhost``` if installed locally + +Example: +```json +"credential_issuer": "{base_url}", +"credential_endpoint": "{base_url}/credential", +"batch_credential_endpoint": "{base_url}/batch_credential", +"notification_endpoint": "{base_url}/notification", +"deferred_credential_endpoint": "{base_url}/deferred_credential", +``` diff --git a/app/misc.py b/app/misc.py index 1178794..27a1395 100644 --- a/app/misc.py +++ b/app/misc.py @@ -207,11 +207,6 @@ def scope2details(scope): ) return configuration_ids - """ return authentication_error_redirect( - jws_token=request.args.get("token"), - error="invalid authentication", - error_description="Authorization details or scope not supported", - ) """ def credential_error_resp(error, desc): diff --git a/app/route_dynamic.py b/app/route_dynamic.py index 431d21f..465b508 100644 --- a/app/route_dynamic.py +++ b/app/route_dynamic.py @@ -336,7 +336,7 @@ def red(): country, jws_token = request.args.get("state").split(".") session["jws_token"] = jws_token - session["country"] == country + session["country"] = country (v, l) = validate_mandatory_args(request.args, ["code"]) if not v: # if not all arguments are available @@ -808,7 +808,11 @@ def preauthRed(): url = cfgserv.service_url + "pushed_authorizationv2" redirect_url = urllib.parse.quote(cfgserv.service_url) + "preauth-code" - payload = "response_type=code&state=af0ifjsldkj&client_id=ID&redirect_uri=" + redirect_url + "&code_challenge=-ciaVij0VMswVfqm3_GK758-_dAI0E9i97hu1SAOiFQ&code_challenge_method=S256&authorization_details=%5B%0A%20%20%7B%0A%20%20%20%20%22type%22%3A%20%22openid_credential%22%2C%0A%20%20%20%20%22credential_configuration_id%22%3A%20%22eu.europa.ec.eudi.loyalty_mdoc%22%0A%20%20%7D%0A%5D" + payload = ( + "response_type=code&state=af0ifjsldkj&client_id=ID&redirect_uri=" + + redirect_url + + "&code_challenge=-ciaVij0VMswVfqm3_GK758-_dAI0E9i97hu1SAOiFQ&code_challenge_method=S256&authorization_details=%5B%0A%20%20%7B%0A%20%20%20%20%22type%22%3A%20%22openid_credential%22%2C%0A%20%20%20%20%22credential_configuration_id%22%3A%20%22eu.europa.ec.eudi.loyalty_mdoc%22%0A%20%20%7D%0A%5D" + ) headers = {"Content-Type": "application/x-www-form-urlencoded"} response = requests.request("POST", url, headers=headers, data=payload) diff --git a/app/route_oidc.py b/app/route_oidc.py index 4be0f4f..595d0d7 100644 --- a/app/route_oidc.py +++ b/app/route_oidc.py @@ -33,7 +33,16 @@ import urllib.parse import segno -from flask import Blueprint, jsonify, Response, request, session, current_app, redirect, render_template +from flask import ( + Blueprint, + jsonify, + Response, + request, + session, + current_app, + redirect, + render_template, +) from flask.helpers import make_response, send_from_directory import os @@ -74,6 +83,7 @@ parRequests = {} deferredRequests = {} + def _add_cookie(resp: Response, cookie_spec: Union[dict, list]): kwargs = {k: v for k, v in cookie_spec.items() if k not in ("name",)} kwargs["path"] = "/" @@ -154,15 +164,19 @@ def verify(authn_method): auth_args = authn_method.unpack_token(request.args.get("jws_token")) except: - log.logger_error.error("Authorization verification: username or jws_token not found") + log.logger_error.error( + "Authorization verification: username or jws_token not found" + ) if "jws_token" in request.args: return authentication_error_redirect( - jws_token=request.args.get("jws_token"), - error="invalid_request", - error_description="Authentication verification Error", - ) + jws_token=request.args.get("jws_token"), + error="invalid_request", + error_description="Authentication verification Error", + ) else: - return render_template("misc/500.html", error="Authentication verification Error") + return render_template( + "misc/500.html", error="Authentication verification Error" + ) authz_request = AuthorizationRequest().from_urlencoded(auth_args["query"]) @@ -268,8 +282,16 @@ def authorization(): return service_endpoint(current_app.server.get_endpoint("authorization")) -#@oidc.route("/authorizationV2", methods=["GET"]) -def authorizationv2(client_id,redirect_uri, response_type,scope=None, code_challenge_method=None, code_challenge=None, authorization_details=None): +# @oidc.route("/authorizationV2", methods=["GET"]) +def authorizationv2( + client_id, + redirect_uri, + response_type, + scope=None, + code_challenge_method=None, + code_challenge=None, + authorization_details=None, +): client_secret = str(uuid.uuid4()) @@ -277,9 +299,10 @@ def authorizationv2(client_id,redirect_uri, response_type,scope=None, code_chall client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri ) - #return service_endpoint(current_app.server.get_endpoint("authorization")) + # return service_endpoint(current_app.server.get_endpoint("authorization")) url = ( - cfgservice.service_url + "authorization?redirect_uri=" + cfgservice.service_url + + "authorization?redirect_uri=" + redirect_uri + "&response_type=" + response_type @@ -295,19 +318,17 @@ def authorizationv2(client_id,redirect_uri, response_type,scope=None, code_chall if code_challenge and code_challenge_method: url = url + "&code_challenge=" - + code_challenge - + "&code_challenge_method=" - + code_challenge_method + +code_challenge + +"&code_challenge_method=" + +code_challenge_method payload = {} headers = {} - response = requests.request( - "GET", url, headers=headers, data=payload - ) + response = requests.request("GET", url, headers=headers, data=payload) if response.status_code != 200: log.logger_error.error("Authorization endpoint invalid request") - return auth_error_redirect(redirect_uri,"invalid_request") + return auth_error_redirect(redirect_uri, "invalid_request") response = response.json() @@ -332,12 +353,17 @@ def authorizationv2(client_id,redirect_uri, response_type,scope=None, code_chall return redirect(response["url"]) + @oidc.route("/authorizationV3", methods=["GET"]) def authorizationV3(): - log.logger_info.info("Authorization request Data: " + str(request.args) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Authorization request Data: " + + str(request.args) + + " | Headers: " + + str(dict(request.headers)) ) - + if "request_uri" not in request.args: try: client_id = request.args.get("client_id") @@ -349,9 +375,15 @@ def authorizationV3(): authorization_details = request.args.get("authorization_details") except: return make_response("Authorization v2 error", 400) - return authorizationv2(client_id,redirect_uri, response_type,scope, code_challenge_method, code_challenge, authorization_details) - - + return authorizationv2( + client_id, + redirect_uri, + response_type, + scope, + code_challenge_method, + code_challenge, + authorization_details, + ) try: request_uri = request.args.get("request_uri") @@ -369,9 +401,10 @@ def authorizationV3(): if "scope" not in par_args: par_args["scope"] = "openid" - + url = ( - cfgservice.service_url + "authorization?redirect_uri=" + cfgservice.service_url + + "authorization?redirect_uri=" + par_args["redirect_uri"] + "&response_type=" + par_args["response_type"] @@ -385,13 +418,11 @@ def authorizationV3(): payload = {} headers = {} - response = requests.request( - "GET", url, headers=headers, data=payload - ) + response = requests.request("GET", url, headers=headers, data=payload) if response.status_code != 200: log.logger_error.error("Authorization endpoint invalid request") - return auth_error_redirect(par_args["redirect_uri"],"invalid_request") + return auth_error_redirect(par_args["redirect_uri"], "invalid_request") response = response.json() @@ -416,6 +447,7 @@ def authorizationV3(): return redirect(response["url"]) + @oidc.route("/authorization-preauth", methods=["GET"]) def authorizationpre(): @@ -435,7 +467,8 @@ def authorizationpre(): par_args["scope"] = "openid" url = ( - cfgservice.service_url + "authorization?redirect_uri=" + cfgservice.service_url + + "authorization?redirect_uri=" + par_args["redirect_uri"] + "&response_type=" + par_args["response_type"] @@ -449,9 +482,7 @@ def authorizationpre(): payload = {} headers = {} - response = requests.request( - "GET", url, headers=headers, data=payload - ).json() + response = requests.request("GET", url, headers=headers, data=payload).json() print("\n-----before params\n") @@ -475,7 +506,7 @@ def authorizationpre(): print("\n-----Set auth params\n", session) - #return redirect(url_get(response["url"], params)) + # return redirect(url_get(response["url"], params)) return redirect(cfgservice.service_url + "dynamic/preauth-form") # return response.content @@ -491,63 +522,56 @@ def oid4vp(): "presentation_definition": { "id": "32f54163-7166-48f1-93d8-ff217bdb0653", "input_descriptors": [ - { - "id": "eu.europa.ec.eudi.pid.1", - "format": { - "mso_mdoc": { - "alg": [ - "ES256", - "ES384", - "ES512", - "EdDSA" - ] - } - }, - "name": "EUDI PID", - "purpose": "We need to verify your identity", - "constraints": { - "fields": [ - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['family_name']" - ], - "intent_to_retain": False - }, - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['given_name']" - ], - "intent_to_retain": False - }, - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['birth_date']" - ], - "intent_to_retain": False - }, - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['age_over_18']" - ], - "intent_to_retain": False + { + "id": "eu.europa.ec.eudi.pid.1", + "format": { + "mso_mdoc": {"alg": ["ES256", "ES384", "ES512", "EdDSA"]} }, - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['issuing_authority']" - ], - "intent_to_retain": False + "name": "EUDI PID", + "purpose": "We need to verify your identity", + "constraints": { + "fields": [ + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['family_name']" + ], + "intent_to_retain": False, + }, + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['given_name']" + ], + "intent_to_retain": False, + }, + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['birth_date']" + ], + "intent_to_retain": False, + }, + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['age_over_18']" + ], + "intent_to_retain": False, + }, + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['issuing_authority']" + ], + "intent_to_retain": False, + }, + { + "path": [ + "$['eu.europa.ec.eudi.pid.1']['issuing_country']" + ], + "intent_to_retain": False, + }, + ] }, - { - "path": [ - "$['eu.europa.ec.eudi.pid.1']['issuing_country']" - ], - "intent_to_retain": False - } - ] } - } - ] - } + ], + }, } ) @@ -570,7 +594,7 @@ def oid4vp(): qrcode = segno.make(deeplink_url) out = io.BytesIO() - qrcode.save(out, kind='png', scale=3) + qrcode.save(out, kind="png", scale=3) """ qrcode.to_artistic( background=cfgtest.qr_png, @@ -590,40 +614,50 @@ def oid4vp(): url_data=deeplink_url, qrcode=qr_img_base64, presentation_id=response["presentation_id"], - redirect_url= cfgservice.service_url + redirect_url=cfgservice.service_url, ) + @oidc.route("/pid_authorization") def pid_authorization_get(): - presentation_id= request.args.get("presentation_id") + presentation_id = request.args.get("presentation_id") - url = "https://dev.verifier-backend.eudiw.dev/ui/presentations/" + presentation_id + "?nonce=hiCV7lZi5qAeCy7NFzUWSR4iCfSmRb99HfIvCkPaCLc=" + url = ( + "https://dev.verifier-backend.eudiw.dev/ui/presentations/" + + presentation_id + + "?nonce=hiCV7lZi5qAeCy7NFzUWSR4iCfSmRb99HfIvCkPaCLc=" + ) headers = { - 'Content-Type': 'application/json', + "Content-Type": "application/json", } response = requests.request("GET", url, headers=headers) if response.status_code != 200: - error_msg= str(response.status_code) - return jsonify({"error": error_msg}),500 + error_msg = str(response.status_code) + return jsonify({"error": error_msg}), 500 else: data = {"message": "Sucess"} - return jsonify({"message": data}),200 - + return jsonify({"message": data}), 200 + @oidc.route("/auth_choice", methods=["GET"]) def auth_choice(): token = request.args.get("token") - supported_credencials=cfgservice.auth_method_supported_credencials - pid_auth=True - country_selection=True + supported_credencials = cfgservice.auth_method_supported_credencials + pid_auth = True + country_selection = True if "authorization_params" not in session: - log.logger_info.info("Authorization Params didn't exist in Authentication Choice") - return render_template("misc/500.html", error="Invalid Authentication. No authorization details or scope found.") - + log.logger_info.info( + "Authorization Params didn't exist in Authentication Choice" + ) + return render_template( + "misc/500.html", + error="Invalid Authentication. No authorization details or scope found.", + ) + authorization_params = session["authorization_params"] authorization_details = [] @@ -633,7 +667,6 @@ def auth_choice(): ) if "scope" in authorization_params: authorization_details.extend(scope2details(authorization_params["scope"])) - credentials_requested = [] for cred in authorization_details: @@ -644,72 +677,94 @@ def auth_choice(): if cred["vct"] not in credentials_requested: credentials_requested.append(cred["vct"]) - for cred in credentials_requested: - if cred in supported_credencials["PID_login"] and cred not in supported_credencials["country_selection"]: - country_selection=False - elif cred not in supported_credencials["PID_login"] and cred in supported_credencials["country_selection"]: - pid_auth=False + if ( + cred in supported_credencials["PID_login"] + and cred not in supported_credencials["country_selection"] + ): + country_selection = False + elif ( + cred not in supported_credencials["PID_login"] + and cred in supported_credencials["country_selection"] + ): + pid_auth = False error = "" if pid_auth == False and country_selection == False: - error="Combination of requested credentials is not valid!" - - return render_template("misc/auth_method.html",pid_auth=pid_auth,country_selection=country_selection, error=error, redirect_url= log.service_url) + error = "Combination of requested credentials is not valid!" + return render_template( + "misc/auth_method.html", + pid_auth=pid_auth, + country_selection=country_selection, + error=error, + redirect_url=log.service_url, + ) + + # return render_template("misc/auth_method.html") - #return render_template("misc/auth_method.html") @oidc.route("/token_service", methods=["POST"]) def token_service(): - #session_id = request.cookies.get("session") + # session_id = request.cookies.get("session") response = service_endpoint(current_app.server.get_endpoint("token")) return response + @oidc.route("/token", methods=["POST"]) def token(): - log.logger_info.info("Token request data: " + str(request.form.to_dict()) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Token request data: " + + str(request.form.to_dict()) + + " | Headers: " + + str(dict(request.headers)) ) - + req_args = dict([(k, v) for k, v in request.form.items()]) response = None if req_args["grant_type"] == "authorization_code": - + response = service_endpoint(current_app.server.get_endpoint("token")) - elif req_args["grant_type"] == "urn:ietf:params:oauth:grant-type:pre-authorized_code": + elif ( + req_args["grant_type"] == "urn:ietf:params:oauth:grant-type:pre-authorized_code" + ): if "pre-authorized_code" not in req_args: return make_response("invalid_request", 400) - + code = req_args["pre-authorized_code"] url = cfgservice.service_url + "token_service" redirect_url = urllib.parse.quote(cfgservice.service_url) + "preauth-code" - payload = 'grant_type=authorization_code&code=' + code + '&redirect_uri=' + redirect_url + '&client_id=ID&state=vFs5DfvJqoyHj7_dZs2JbdklePg6pMLsUHHmVIfobRw&code_verifier=FnWCRIhpJtl6IYwVVYB8gZkQsmvBVLfU4HQiABPopYQ6gvIZBwMrXg' - headers = { - 'Content-Type': 'application/x-www-form-urlencoded' - } - + payload = ( + "grant_type=authorization_code&code=" + + code + + "&redirect_uri=" + + redirect_url + + "&client_id=ID&state=vFs5DfvJqoyHj7_dZs2JbdklePg6pMLsUHHmVIfobRw&code_verifier=FnWCRIhpJtl6IYwVVYB8gZkQsmvBVLfU4HQiABPopYQ6gvIZBwMrXg" + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + response = requests.request("POST", url, headers=headers, data=payload) if response.status_code != 200: return make_response("invalid_request", 400) - - #response = response.json() + + # response = response.json() log.logger_info.info("Token response: " + str(response.json())) return response.json() - - else: + + else: return make_response("invalid_request", 400) - + log.logger_info.info("Token response: " + str(json.loads(response.get_data()))) return response @@ -738,7 +793,11 @@ def par_endpoint(): @oidc.route("/pushed_authorizationv2", methods=["POST"]) def par_endpointv2(): - log.logger_info.info("Recieved Pushed Authorization request. Data: " + str(request.form.to_dict()) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Recieved Pushed Authorization request. Data: " + + str(request.form.to_dict()) + + " | Headers: " + + str(dict(request.headers)) ) redirect_uri = None @@ -749,7 +808,9 @@ def par_endpointv2(): except: log.logger_error.error("PAR: client_id or redirect_uri not found") if redirect_uri: - return auth_error_redirect(redirect_uri, "invalid_request", "invalid parameters") + return auth_error_redirect( + redirect_uri, "invalid_request", "invalid parameters" + ) else: return make_response("PARv2 error", 400) @@ -761,8 +822,13 @@ def par_endpointv2(): response = service_endpoint(current_app.server.get_endpoint("pushed_authorization")) - log.logger_info.info("PAR response for client_id " + client_id + " : " + str(json.loads(response.get_data()))) - + log.logger_info.info( + "PAR response for client_id " + + client_id + + " : " + + str(json.loads(response.get_data())) + ) + return response @@ -770,24 +836,42 @@ def par_endpointv2(): def credential(): if request.data: - log.logger_info.info("Credential request data: " + str(json.loads(request.data)) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Credential request data: " + + str(json.loads(request.data)) + + " | Headers: " + + str(dict(request.headers)) ) _response = service_endpoint(current_app.server.get_endpoint("credential")) - - if isinstance(_response,Response): - log.logger_info.info("Credential response " + str(json.loads(_response.get_data()))) + + if isinstance(_response, Response): + log.logger_info.info( + "Credential response " + str(json.loads(_response.get_data())) + ) return _response - if "transaction_id" in _response and _response["transaction_id"] not in deferredRequests: + if ( + "transaction_id" in _response + and _response["transaction_id"] not in deferredRequests + ): request_data = request.data request_headers = dict(request.headers) - deferredRequests.update({_response["transaction_id"]: {"data":request_data, "headers":request_headers, "expires":datetime.now() + timedelta(minutes=cfgservice.deffered_expiry)}}) - + deferredRequests.update( + { + _response["transaction_id"]: { + "data": request_data, + "headers": request_headers, + "expires": datetime.now() + + timedelta(minutes=cfgservice.deffered_expiry), + } + } + ) + log.logger_info.info("Credential response " + str(_response)) - return make_response(jsonify(_response),202) + return make_response(jsonify(_response), 202) log.logger_info.info("Credential response " + str(_response)) return _response @@ -795,53 +879,79 @@ def credential(): @oidc.route("/batch_credential", methods=["POST"]) def batchCredential(): - log.logger_info.info("Batch credential request data: " + str(json.loads(request.data)) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Batch credential request data: " + + str(json.loads(request.data)) + + " | Headers: " + + str(dict(request.headers)) ) _response = service_endpoint(current_app.server.get_endpoint("credential")) - if isinstance(_response,Response): - log.logger_info.info("Batch Credential response " + str(json.loads(_response.get_data()))) + if isinstance(_response, Response): + log.logger_info.info( + "Batch Credential response " + str(json.loads(_response.get_data())) + ) return _response - - if "transaction_id" in _response and _response["transaction_id"] not in deferredRequests: + + if ( + "transaction_id" in _response + and _response["transaction_id"] not in deferredRequests + ): request_data = request.data request_headers = dict(request.headers) - deferredRequests.update({_response["transaction_id"]: {"data":request_data, "headers":request_headers, "expires":datetime.now() + timedelta(minutes=cfgservice.deffered_expiry)}}) - + deferredRequests.update( + { + _response["transaction_id"]: { + "data": request_data, + "headers": request_headers, + "expires": datetime.now() + + timedelta(minutes=cfgservice.deffered_expiry), + } + } + ) + log.logger_info.info("Batch credential response " + str(_response)) - return make_response(jsonify(_response),202) - + return make_response(jsonify(_response), 202) log.logger_info.info("Batch credential response " + str(_response)) return _response + @oidc.route("/notification", methods=["POST"]) def notification(): - log.logger_info.info("Notification request data: " + str(json.loads(request.data)) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Notification request data: " + + str(json.loads(request.data)) + + " | Headers: " + + str(dict(request.headers)) ) _resp = service_endpoint(current_app.server.get_endpoint("notification")) - if isinstance(_resp,Response): + if isinstance(_resp, Response): log.logger_info.info("Notification response " + str(_resp)) return _resp - log.logger_info.info("Notification response " + str(_resp)) - + return _resp + @oidc.route("/deferred_credential", methods=["POST"]) def deferred_credential(): - log.logger_info.info("Deferred request data: " + str(json.loads(request.data)) + " | Headers: " + str(dict(request.headers)) + log.logger_info.info( + "Deferred request data: " + + str(json.loads(request.data)) + + " | Headers: " + + str(dict(request.headers)) ) - + _resp = service_endpoint(current_app.server.get_endpoint("deferred_credential")) - if isinstance(_resp,Response): + if isinstance(_resp, Response): log.logger_info.info("Deferred response " + str(json.loads(_resp.get_data()))) return _resp @@ -849,6 +959,7 @@ def deferred_credential(): return _resp + @oidc.route("credential_offer_choice", methods=["GET"]) def credential_offer(): """Page for selecting credentials @@ -863,9 +974,9 @@ def credential_offer(): credential = credentialsSupported[cred] if credential["format"] == "vc+sd-jwt": - #if credential["scope"] == "eu.europa.ec.eudiw.pid.1": + # if credential["scope"] == "eu.europa.ec.eudiw.pid.1": credentials["sd-jwt vc format"].update( - #{"Personal Identification Data": cred} + # {"Personal Identification Data": cred} {cred: cred} ) @@ -875,16 +986,22 @@ def credential_offer(): ) """ if credential["format"] == "mso_mdoc": - #if credential["scope"] == "eu.europa.ec.eudiw.pid.1": + # if credential["scope"] == "eu.europa.ec.eudiw.pid.1": credentials["mdoc format"].update( {cred: cred} - #{"Personal Identification Data": cred} + # {"Personal Identification Data": cred} ) """ elif credential["scope"] == "org.iso.18013.5.1.mDL": credentials["mdoc format"].update({"Mobile Driver's Licence": cred}) """ - return render_template("openid/credential_offer.html", cred=credentials, redirect_url= cfgservice.service_url, credential_offer_URI="openid-credential-offer://") + return render_template( + "openid/credential_offer.html", + cred=credentials, + redirect_url=cfgservice.service_url, + credential_offer_URI="openid-credential-offer://", + ) + """ @oidc.route("/test_dump", methods=["GET", "POST"]) def dump_test(): @@ -916,7 +1033,7 @@ def credentialOffer(): credentialsSupported = oidc_metadata["credential_configurations_supported"] form_keys = request.form.keys() - credential_offer_URI=request.form.get("credential_offer_URI") + credential_offer_URI = request.form.get("credential_offer_URI") if "proceed" in form_keys: form = list(form_keys) @@ -930,16 +1047,15 @@ def credentialOffer(): credential_offer = { "credential_issuer": cfgservice.service_url[:-1], "credential_configuration_ids": credentials_id, - "grants" : { - "authorization_code" : {} - } + "grants": {"authorization_code": {}}, } # create URI json_string = json.dumps(credential_offer) - uri = f"{credential_offer_URI}credential_offer?credential_offer=" + urllib.parse.quote( - json_string, safe=":/" + uri = ( + f"{credential_offer_URI}credential_offer?credential_offer=" + + urllib.parse.quote(json_string, safe=":/") ) # Generate QR code @@ -948,7 +1064,7 @@ def credentialOffer(): qrcode = segno.make(uri) out = io.BytesIO() - qrcode.save(out, kind='png', scale=3) + qrcode.save(out, kind="png", scale=3) """ qrcode.to_artistic( background=cfgtest.qr_png, @@ -962,10 +1078,12 @@ def credentialOffer(): qr_img_base64 = "data:image/png;base64," + base64.b64encode( out.getvalue() ).decode("utf-8") - + return render_template( "openid/credential_offer_qr_code.html", - wallet_dev= "https://tester.issuer.eudiw.dev/credential_offer" + "?credential_offer=" + json.dumps(credential_offer), + wallet_dev="https://tester.issuer.eudiw.dev/credential_offer" + + "?credential_offer=" + + json.dumps(credential_offer), url_data=uri, qrcode=qr_img_base64, ) @@ -973,20 +1091,19 @@ def credentialOffer(): else: return redirect(cfgservice.service_url + "credential_offer_choice") + @oidc.route("/preauth-code", methods=["GET"]) def preauthCode(): - request - code = request.args.get("code") credential_offer = { "credential_issuer": cfgservice.service_url, "credential_configuration_ids": ["eu.europa.ec.eudi.loyalty_mdoc"], - "grants" : { - "urn:ietf:params:oauth:grant-type:pre-authorized_code" : { - "pre-authorized_code": code + "grants": { + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": code } - } + }, } # create URI @@ -1002,10 +1119,9 @@ def preauthCode(): qrcode = segno.make(uri) out = io.BytesIO() - qrcode.save(out, kind='png', scale=2) - + qrcode.save(out, kind="png", scale=2) - #print(qrcode.terminal(compact=True)) + # print(qrcode.terminal(compact=True)) """ qrcode.to_artistic( background=cfgtest.qr_png, @@ -1015,15 +1131,15 @@ def preauthCode(): ) """ # qrcode.terminal() - #qr_img_base64 = qrcode.png_data_uri(scale=4) + # qr_img_base64 = qrcode.png_data_uri(scale=4) - qr_img_base64 = "data:image/png;base64," + base64.b64encode( - out.getvalue() - ).decode("utf-8") + qr_img_base64 = "data:image/png;base64," + base64.b64encode(out.getvalue()).decode( + "utf-8" + ) return render_template( "openid/credential_offer_qr_code.html", - wallet_dev= "https://tester.issuer.eudiw.dev/redirect_preauth" + "?code=" + code, + wallet_dev="https://tester.issuer.eudiw.dev/redirect_preauth" + "?code=" + code, url_data=uri, qrcode=qr_img_base64, ) @@ -1087,7 +1203,7 @@ def service_endpoint(endpoint): error="invalid_request", error_description=str(err) ) return make_response(err_msg.to_json(), 400) - + if endpoint.name == "notification": try: accessToken = http_info["headers"]["authorization"][7:] @@ -1097,8 +1213,8 @@ def service_endpoint(endpoint): _resp = endpoint.process_request(req_args) if isinstance(_resp, ResponseMessage) and "error" in _resp: - _log.info("Error response: {}".format(_resp)) - _resp = make_response(_resp.to_json(), 400) + _log.info("Error response: {}".format(_resp)) + _resp = make_response(_resp.to_json(), 400) except Exception as err: _log.error(err) @@ -1131,7 +1247,7 @@ def service_endpoint(endpoint): else: response = do_response(endpoint, args, **args) return response - + except Exception as err: _log.error(err) return make_response( @@ -1198,7 +1314,7 @@ def service_endpoint(endpoint): err_msg = ResponseMessage(error="invalid_request", error_description=str(err)) return make_response(err_msg.to_json(), 400) - #_log.info("Response args: {}".format(args)) + # _log.info("Response args: {}".format(args)) # "pushed_authorization" if ( @@ -1227,7 +1343,6 @@ def handle_bad_request(e): return "bad request!", 400 - ################################################ ## To be moved to a file with scheduled jobs @@ -1262,22 +1377,30 @@ def clear_par(): ) for req in deferredRequests.copy(): - - if datetime.now() > deferredRequests[req]["expires"]: - print("\n-------Deferred expired-----\n") - deferredRequests.pop(req) - else: - request_data = json.loads(deferredRequests[req]["data"]) - request_data.update({"transaction_id": req}) - request_data = json.dumps(request_data) - request_headers = deferredRequests[req]["headers"] - response = requests.post(cfgservice.service_url+"credential", data=request_data, headers=request_headers) - response_data = response.json() + if datetime.now() > deferredRequests[req]["expires"]: + print("\n-------Deferred expired-----\n") + deferredRequests.pop(req) + else: + request_data = json.loads(deferredRequests[req]["data"]) + request_data.update({"transaction_id": req}) + request_data = json.dumps(request_data) + request_headers = deferredRequests[req]["headers"] + + response = requests.post( + cfgservice.service_url + "credential", + data=request_data, + headers=request_headers, + ) + response_data = response.json() + + if response.status_code == 200: + if ( + "credential" in response_data + or "credential_responses" in response_data + ): + deferredRequests.pop(req) - if response.status_code == 200: - if "credential" in response_data or "credential_responses" in response_data: - deferredRequests.pop(req) def run_scheduler(): print("Run scheduler.")